-
Notifications
You must be signed in to change notification settings - Fork 181
Expand file tree
/
Copy pathsoroban_bls_signature_with_contract_client.py
More file actions
107 lines (81 loc) · 3.37 KB
/
Copy pathsoroban_bls_signature_with_contract_client.py
File metadata and controls
107 lines (81 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# pyright: reportMissingImports=false
"""Invoke the Soroban BLS signature custom account example with ContractClient.
This script follows the BLS example contract from:
https://github.com/stellar/soroban-examples/tree/main/bls_signature
Install the extra dependency first:
pip install py-ecc
The deployed contract must be initialized with the aggregate G1 public key
derived from the three BLS secret scalars in ``SKS`` below. If the contract's
``agg_pk`` was created from different secret scalars, ``__check_auth`` will
reject the signatures generated by this script.
"""
from hashlib import sha256
from py_ecc.bls.hash_to_curve import hash_to_G2 # type: ignore[import-not-found]
from py_ecc.optimized_bls12_381.optimized_curve import ( # type: ignore[import-not-found,import-untyped]
curve_order,
multiply,
normalize,
)
from stellar_sdk import Keypair, Network, scval
from stellar_sdk import xdr as stellar_xdr
from stellar_sdk.auth import authorization_payload_hash
from stellar_sdk.contract import ContractClient
# The upstream example contract is both the custom account and the counter.
BLS_CONTRACT = "CBO4WJR2UG7N7JK2CK2YJCLXEK5T6FV4DM3MXWQS5YITZ5RTOTUFWWBO"
COUNTER_CONTRACT = "CBO4WJR2UG7N7JK2CK2YJCLXEK5T6FV4DM3MXWQS5YITZ5RTOTUFWWBO"
SOURCE_SK = "SAAPYAPTTRZMCUZFPG3G66V4ZMHTK4TWA6NS7U4F7Z3IMUD52EK4DDEV"
RPC = "https://soroban-testnet.stellar.org"
NETWORK = Network.TESTNET_NETWORK_PASSPHRASE
DST = b"BLSSIG-V01-CS01-with-BLS12381G2_XMD:SHA-256_SSWU_RO_"
# These are the three BLS secret scalars used to initialize the example
# contract's aggregate public key. Keep them in sync with the deployed
# contract's constructor input.
SKS = [
int.from_bytes(bytes.fromhex(secret), "big")
for secret in [
"7197d39d834199eb903efad5fa7cec3085d13e3af73e2ae978b294fb1e26fead",
"628347027e5246340041b81c88ddb1aa854be4ffffc13378897961ac91de1589",
"1c9043bdd86eb1157fc9fecb0d599c5ac88d94d510508fb0b1b7f2e64d1afa20",
]
]
SK_SUM = sum(SKS) % curve_order
def fp_to_bytes(value):
return int(value).to_bytes(48, "big")
def fp2_to_bytes(value):
c0, c1 = value.coeffs
# Soroban encodes BLS12-381 Fp2 values as c1 || c0.
return fp_to_bytes(c1) + fp_to_bytes(c0)
def g2_uncompressed(point):
x, y = normalize(point)
return fp2_to_bytes(x) + fp2_to_bytes(y)
def bls_sign(payload32: bytes) -> bytes:
message_point = hash_to_G2(payload32, DST, sha256) # type: ignore
signature = g2_uncompressed(multiply(message_point, SK_SUM))
if len(signature) != 192:
raise RuntimeError(f"Expected a 192-byte BLS signature, got {len(signature)}.")
return signature
def bls_signer(preimage: stellar_xdr.HashIDPreimage) -> stellar_xdr.SCVal:
payload = authorization_payload_hash(preimage)
return scval.to_bytes(bls_sign(payload))
src = Keypair.from_secret(SOURCE_SK)
assembled = ContractClient(
COUNTER_CONTRACT,
RPC,
NETWORK,
).invoke(
"increment",
[],
source=src.public_key,
signer=src,
base_fee=100,
parse_result_xdr_fn=scval.from_uint32,
)
# authorize() signs only the auth entries for BLS_CONTRACT. Because this is a
# custom account contract, the callable returns the contract-specific SCVal
# signature shape (BytesN<192> in the Rust contract).
result = assembled.authorize(
address=BLS_CONTRACT,
signer=bls_signer,
valid_for_ledger_count=20,
).sign_and_submit()
print(f"counter = {result}")