Skip to content

Commit

Permalink
restructure + python example
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr committed Oct 1, 2024
1 parent f4e9932 commit f257ddc
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/bls-multisig/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv/
19 changes: 19 additions & 0 deletions examples/bls-multisig/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Python Alphanet Multisig

This example demonstrates an integration of [BlsMultisig](../../../src/BLSMultisig.sol) with Python.

## Running the example

To run the example, you will need to install the required dependencies:

```shell
pip install web3 py_ecc
```

Then, you can run the example by executing the following command:

```shell
python multisig.py
```

This will spin up an Anvil instance in Alphanet mode, deploy the multisig contract and execute a simple operation signed by random BLS keys.
105 changes: 105 additions & 0 deletions examples/bls-multisig/python/multisig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from web3 import AsyncWeb3
import pathlib
import asyncio
import json
import subprocess
import random
from py_ecc.bls import G2Basic
from py_ecc.bls import g2_primitives
import eth_abi

Fp = tuple[int, int]
Fp2 = tuple[Fp, Fp]
G1Point = tuple[Fp, Fp]
G2Point = tuple[Fp2, Fp2]
Operation = tuple[str, str, int, int]


def fp_from_int(x: int) -> Fp:
b = x.to_bytes(64, "big")
return (int.from_bytes(b[:32], "big"), int.from_bytes(b[32:], "big"))


def generate_keys(num: int) -> list[tuple[G1Point, int]]:
keypairs = []
for _ in range(num):
sk = random.randint(0, 10**30)
pk_point = g2_primitives.pubkey_to_G1(G2Basic.SkToPk(sk))

pk = (fp_from_int(int(pk_point[0])), fp_from_int(int(pk_point[1])))

keypairs.append((pk, sk))

keypairs.sort()

return keypairs


def sign_operation(sks: list[int], operation: Operation) -> G2Point:
encoded = eth_abi.encode(["(address,bytes,uint256,uint256)"], [operation])

signatures = []
for sk in sks:
signatures.append(G2Basic.Sign(sk, encoded))

aggregated = g2_primitives.signature_to_G2(G2Basic.Aggregate(signatures))

signature = (
(fp_from_int(aggregated[0].coeffs[0]), fp_from_int(aggregated[0].coeffs[1])),
(fp_from_int(aggregated[1].coeffs[0]), fp_from_int(aggregated[1].coeffs[1])),
)

return signature


async def main():
bls_multisig_artifact = json.load(
open(pathlib.Path(__file__).parent.parent.parent.parent / "out/BLSMultisig.sol/BLSMultisig.json")
)

web3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider("http://localhost:8545"))

bytecode = bls_multisig_artifact["bytecode"]["object"]
abi = bls_multisig_artifact["abi"]
BlsMultisig = web3.eth.contract(abi=abi, bytecode=bytecode)

signer = (await web3.eth.accounts)[0]

# generate 100 BLS keys
keypairs = generate_keys(100)
pks = list(map(lambda x: x[0], keypairs))

# deploy the multisig contract with generated signers and threshold of 50
tx = await BlsMultisig.constructor(pks, 50).transact({"from": signer})
receipt = await web3.eth.wait_for_transaction_receipt(tx)
multisig = BlsMultisig(receipt.contractAddress)

# fund the multisig
hash = await web3.eth.send_transaction({"from": signer, "to": multisig.address, "value": 10**18})
await web3.eth.wait_for_transaction_receipt(hash)

# create an operation transferring 1 eth to zero address
operation: Operation = ("0x0000000000000000000000000000000000000000", bytes(), 10**18, 0)

# choose 50 random signers that will sign the operation
signers_subset = sorted(random.sample(keypairs, 50))

pks = list(map(lambda x: x[0], signers_subset))
sks = list(map(lambda x: x[1], signers_subset))

# create aggregated signature for operation
signature = sign_operation(sks, operation)

# execute the operation
tx = await multisig.functions.verifyAndExecute((operation, pks, signature)).transact({"from": signer})
receipt = await web3.eth.wait_for_transaction_receipt(tx)

assert receipt.status == 1


if __name__ == "__main__":
try:
anvil = subprocess.Popen(["anvil", "--alphanet"], stdout=subprocess.PIPE)
asyncio.run(main())
finally:
anvil.terminate()
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sol! {
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[sol(rpc)]
BLSMultisig,
"../../out/BLSMultisig.sol/BLSMultisig.json"
"../../../out/BLSMultisig.sol/BLSMultisig.json"
}

impl From<[u8; 96]> for BLS::G1Point {
Expand Down
2 changes: 2 additions & 0 deletions src/BLSMultisig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,6 @@ contract BLSMultisig {
return false;
}
}

receive() external payable {}
}

0 comments on commit f257ddc

Please sign in to comment.