From 1293950003eb8b96c8494bc7a1a4a88624477e16 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 10 Oct 2024 16:03:44 +0400 Subject: [PATCH 1/2] add solidity tests and fix links --- chapter1/bls-multisig/README.md | 2 +- chapter1/bls-multisig/python/README.md | 2 +- chapter1/bls-multisig/rust/README.md | 2 +- chapter1/bls-multisig/rust/src/main.rs | 2 +- .../contracts/src/SimpleDelegateContract.sol | 4 +- chapter1/contracts/test/BLS.t.sol | 94 +++++++++++++++++++ .../test/SimpleDelegateContract.t.sol | 23 +++++ chapter1/simple-7702/README.md | 18 ++-- 8 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 chapter1/contracts/test/BLS.t.sol create mode 100644 chapter1/contracts/test/SimpleDelegateContract.t.sol diff --git a/chapter1/bls-multisig/README.md b/chapter1/bls-multisig/README.md index 9912d18..8ea5949 100644 --- a/chapter1/bls-multisig/README.md +++ b/chapter1/bls-multisig/README.md @@ -8,7 +8,7 @@ EIP-2537 introduces a set of precompiled contracts enabling elliptic curve opera ## Implementation ### Contract -We demonstrate a simple multisignature contract [BLSMultisig](../contracts/BLSMultisig.sol) which keeps a list of signers public keys and allows executing arbitrary operations which are signed by a subset of signers. Both stored public keys and signatures can be aggregated, thus allowing for much better scalability for large numbers of signers vs ECDSA. Let's walk through the contract's code. +We demonstrate a simple multisignature contract [BLSMultisig](../contracts/src/BLSMultisig.sol) which keeps a list of signers public keys and allows executing arbitrary operations which are signed by a subset of signers. Both stored public keys and signatures can be aggregated, thus allowing for much better scalability for large numbers of signers vs ECDSA. Let's walk through the contract's code. BLS signing operates on two curves: G1 and G2. In our case we will store public keys on G1 while signatures and messages will be on G2. To sign or verify a message consisting of arbitrary bytes, we need to firstly map the message to a point on G2. There is a commonly used [algorithm](https://datatracker.ietf.org/doc/html/rfc9380#name-hashing-to-a-finite-field) for this mapping, we are using its implementation in Solidity: diff --git a/chapter1/bls-multisig/python/README.md b/chapter1/bls-multisig/python/README.md index c100c60..6ef4073 100644 --- a/chapter1/bls-multisig/python/README.md +++ b/chapter1/bls-multisig/python/README.md @@ -1,6 +1,6 @@ # Python BLS Multisig -This example demonstrates an integration of [BlsMultisig](../../contracts/BLSMultisig.sol) with Python. +This example demonstrates an integration of [BlsMultisig](../../contracts/src/BLSMultisig.sol) with Python. ## Running the example diff --git a/chapter1/bls-multisig/rust/README.md b/chapter1/bls-multisig/rust/README.md index b998388..c741e61 100644 --- a/chapter1/bls-multisig/rust/README.md +++ b/chapter1/bls-multisig/rust/README.md @@ -1,6 +1,6 @@ # Rust BLS Multisig -This example demonstrates an integration of [BlsMultisig](../../contracts/BLSMultisig.sol) with Rust. +This example demonstrates an integration of [BlsMultisig](../../contracts/src/BLSMultisig.sol) with Rust. ## Running the example diff --git a/chapter1/bls-multisig/rust/src/main.rs b/chapter1/bls-multisig/rust/src/main.rs index 25497e3..38f1778 100644 --- a/chapter1/bls-multisig/rust/src/main.rs +++ b/chapter1/bls-multisig/rust/src/main.rs @@ -12,7 +12,7 @@ alloy::sol! { #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[sol(rpc)] BLSMultisig, - "../../out/BLSMultisig.sol/BLSMultisig.json" + "../../contracts/out/BLSMultisig.sol/BLSMultisig.json" } /// Generates `num` BLS keys and returns them as a tuple of private and public keys diff --git a/chapter1/contracts/src/SimpleDelegateContract.sol b/chapter1/contracts/src/SimpleDelegateContract.sol index eb8f9c3..757bd66 100644 --- a/chapter1/contracts/src/SimpleDelegateContract.sol +++ b/chapter1/contracts/src/SimpleDelegateContract.sol @@ -13,11 +13,11 @@ contract SimpleDelegateContract { function execute(Call[] memory calls) external payable { for (uint256 i = 0; i < calls.length; i++) { Call memory call = calls[i]; - (bool success, ) = call.to.call{value: call.value}(call.data); + (bool success,) = call.to.call{value: call.value}(call.data); require(success, "Call failed"); emit Executed(call.to, call.value, call.data); } } receive() external payable {} -} \ No newline at end of file +} diff --git a/chapter1/contracts/test/BLS.t.sol b/chapter1/contracts/test/BLS.t.sol new file mode 100644 index 0000000..d66d1a8 --- /dev/null +++ b/chapter1/contracts/test/BLS.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console} from "forge-std/Test.sol"; +import {BLS} from "../src/libraries/BLS.sol"; + +/// @notice A simple test demonstrating BLS signature verification. +contract BLSTest is Test { + /// @notice The generator point in G1 (P1). + BLS.G1Point G1_GENERATOR = BLS.G1Point( + BLS.Fp( + 31827880280837800241567138048534752271, + 88385725958748408079899006800036250932223001591707578097800747617502997169851 + ), + BLS.Fp( + 11568204302792691131076548377920244452, + 114417265404584670498511149331300188430316142484413708742216858159411894806497 + ) + ); + + /// @notice The negated generator point in G1 (-P1). + BLS.G1Point NEGATED_G1_GENERATOR = BLS.G1Point( + BLS.Fp( + 31827880280837800241567138048534752271, + 88385725958748408079899006800036250932223001591707578097800747617502997169851 + ), + BLS.Fp( + 22997279242622214937712647648895181298, + 46816884707101390882112958134453447585552332943769894357249934112654335001290 + ) + ); + + /// @dev Demonstrates the signing and verification of a message. + function test() public { + // Obtain the private key as a random scalar. + uint256 privateKey = vm.randomUint(); + + // Public key is the generator point multiplied by the private key. + BLS.G1Point memory publicKey = BLS.G1Mul(G1_GENERATOR, privateKey); + + // Compute the message point by mapping message's keccak256 hash to a point in G2. + bytes memory message = "hello world"; + BLS.G2Point memory messagePoint = BLS.MapFp2ToG2(BLS.Fp2(BLS.Fp(0, 0), BLS.Fp(0, uint256(keccak256(message))))); + + // Obtain the signature by multiplying the message point by the private key. + BLS.G2Point memory signature = BLS.G2Mul(messagePoint, privateKey); + + // Invoke the pairing check to verify the signature. + BLS.G1Point[] memory g1Points = new BLS.G1Point[](2); + g1Points[0] = NEGATED_G1_GENERATOR; + g1Points[1] = publicKey; + + BLS.G2Point[] memory g2Points = new BLS.G2Point[](2); + g2Points[0] = signature; + g2Points[1] = messagePoint; + + assertTrue(BLS.Pairing(g1Points, g2Points)); + } + + /// @dev Demonstrates the aggregation and verification of two signatures. + function testAggregated() public { + // private keys + uint256 sk1 = vm.randomUint(); + uint256 sk2 = vm.randomUint(); + + // public keys + BLS.G1Point memory pk1 = BLS.G1Mul(G1_GENERATOR, sk1); + BLS.G1Point memory pk2 = BLS.G1Mul(G1_GENERATOR, sk2); + + // Compute the message point by mapping message's keccak256 hash to a point in G2. + bytes memory message = "hello world"; + BLS.G2Point memory messagePoint = BLS.MapFp2ToG2(BLS.Fp2(BLS.Fp(0, 0), BLS.Fp(0, uint256(keccak256(message))))); + + // signatures + BLS.G2Point memory sig1 = BLS.G2Mul(messagePoint, sk1); + BLS.G2Point memory sig2 = BLS.G2Mul(messagePoint, sk2); + + // aggregated signature + BLS.G2Point memory sig = BLS.G2Add(sig1, sig2); + + // Invoke the pairing check to verify the signature. + BLS.G1Point[] memory g1Points = new BLS.G1Point[](3); + g1Points[0] = NEGATED_G1_GENERATOR; + g1Points[1] = pk1; + g1Points[2] = pk2; + + BLS.G2Point[] memory g2Points = new BLS.G2Point[](3); + g2Points[0] = sig; + g2Points[1] = messagePoint; + g2Points[2] = messagePoint; + + assertTrue(BLS.Pairing(g1Points, g2Points)); + } +} diff --git a/chapter1/contracts/test/SimpleDelegateContract.t.sol b/chapter1/contracts/test/SimpleDelegateContract.t.sol new file mode 100644 index 0000000..fc400e1 --- /dev/null +++ b/chapter1/contracts/test/SimpleDelegateContract.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test, console} from "forge-std/Test.sol"; +import {SimpleDelegateContract} from "../src/SimpleDelegateContract.sol"; + +contract SimpleDelegateContractTest is Test { + function test() public { + address payable ALICE = payable(address(0xa11ce)); + SimpleDelegateContract delegation = new SimpleDelegateContract(); + + // let's inject EIP-7702 delegation designation into ALICE's code to + // make it forward all calls to the deployed contract + vm.etch(ALICE, bytes.concat(hex"ef0100", abi.encodePacked(delegation))); + + ALICE.call{value: 1e18}(""); + + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + calls[0] = SimpleDelegateContract.Call({data: "", to: address(0), value: 1e18}); + + SimpleDelegateContract(ALICE).execute(calls); + } +} diff --git a/chapter1/simple-7702/README.md b/chapter1/simple-7702/README.md index 2457246..d4c9a1a 100644 --- a/chapter1/simple-7702/README.md +++ b/chapter1/simple-7702/README.md @@ -67,17 +67,11 @@ Note that in this over-simplified example, you’ll already see some issues e.g. To test this delegation feature, you may use the `vm.etch` cheatcode in your tests as follows: -```bash -import {Test} from "forge-std/Test.sol"; -import {SimpleDelegateContract} from "../src/SimpleDelegateContract.sol"; - -contract DelegationTest is Test { - function test() public { - SimpleDelegateContract delegation = new SimpleDelegateContract(); - // this sets ALICE's EOA code to the deployed contract code - vm.etch(ALICE, address(delegation).code); - } -} +```solidity +// this will inject delegation designation into ALICE's code +vm.etch(ALICE, bytes.concat(hex"ef0100", abi.encodePacked(contractToDelegate))); ``` -This cheat code allows you to **simulate that ALICE's account is no longer a regular EOA but a contract**(like `P256Delegation`) and then test how delegations or transactions behave from that new "smart contract" EOA. \ No newline at end of file +This cheat code allows you to **simulate that ALICE's account is no longer a regular EOA but a contract**(like `P256Delegation`) and then test how delegations or transactions behave from that new "smart contract" EOA. + +You can check out complete example in [SimpleDelegateContract.t.sol](../contracts/test/SimpleDelegateContract.t.sol) \ No newline at end of file From 5aad5227beead78463c39e838adafef22c6a527e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 10 Oct 2024 16:05:56 +0400 Subject: [PATCH 2/2] update eof gas --- chapter1/eof/README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/chapter1/eof/README.md b/chapter1/eof/README.md index 77ecd98..b85c28a 100644 --- a/chapter1/eof/README.md +++ b/chapter1/eof/README.md @@ -101,24 +101,31 @@ Contracts compiled for EOF tend to consume less gas. You can verify this yoursel ```bash $ forge test -Ran 1 test for test/P256.t.sol:BLSTest +Ran 1 test for contracts/test/SimpleDelegateContract.t.sol:SimpleDelegateContractTest +[PASS] test() (gas: 301928) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.87ms (2.15ms CPU time) + +Ran 1 test for contracts/test/P256.t.sol:BLSTest [PASS] test() (gas: 8951) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.74ms (3.89ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.86ms (4.14ms CPU time) -Ran 2 tests for test/BLS.t.sol:BLSTest +Ran 2 tests for contracts/test/BLS.t.sol:BLSTest [PASS] test() (gas: 321781) [PASS] testAggregated() (gas: 439327) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.27ms (9.88ms CPU time) $ forge test --eof -Ran 1 test for test/P256.t.sol:BLSTest +Ran 1 test for contracts/test/SimpleDelegateContract.t.sol:SimpleDelegateContractTest +[PASS] test() (gas: 261751) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.37ms (2.47ms CPU time) + +Ran 1 test for contracts/test/P256.t.sol:BLSTest [PASS] test() (gas: 8191) -Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.62ms (4.74ms CPU time) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.44ms (4.54ms CPU time) -Ran 2 tests for test/BLS.t.sol:BLSTest +Ran 2 tests for contracts/test/BLS.t.sol:BLSTest [PASS] test() (gas: 314754) [PASS] testAggregated() (gas: 425091) -Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.04ms (9.69ms CPU time) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 12.74ms (11.11ms CPU time) ``` As you can see, EOF-compiled contracts are 5-10% more gas efficient.