Skip to content

Commit

Permalink
Merge branch 'main' into jenpaff/small-nits-7702
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr authored Oct 10, 2024
2 parents a5cc1f5 + 401de3f commit 2fee63f
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 36 deletions.
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
# Odyssey Examples

## Overview
This repository provides a step-by-step walk through for builders interested in the developer-preview features available on [Odyssey](https://www.ithaca.xyz/updates/introducing-ithaca), a L2 built for developers to innovate. We are rolling out each Chapter with new features for you to build on.

This repository provides a step-by-step walk through for builders interested in the developer-preview features available on [Odyssey](https://www.ithaca.xyz/updates/introducing-ithaca). Each chapter provides examples of new features added.

## Chapter 1
- [Simple Example for EIP-7702](./chapter1/simple-7702/): Basic example to showcase how EIP-7702 transactions work
- [Delegate Account to p256 key](./chapter1/delegate-p256/): Step-by-step walk-through of how EIP-7702+EIP-7212 provide the ability to sign a message with a P256 key
- [BLS Multisig](./chapter1/bls-multisig/): Examples in Python and Rust to showcase Multisig based on BLS signatures verified through precompiles from EIP-2537
### Chapter 1
- [Simple Example for EIP-7702](./chapter1/simple-7702/): Showcases how EIP-7702 transactions work
- [Delegate an account to a p256 key](./chapter1/delegate-p256/): Describes how EIP-7702+EIP-7212 provide the ability to sign a message with a P256 key
- [BLS Multisig](./chapter1/bls-multisig/): In-depth walk-through how to implement a Multisig based on BLS signatures verified through precompiles from EIP-2537
- [EOF](./chapter1/eof/): Instructions on how to deploy and inspect contracts in the new EOF format

## Build & Run
### Build & Test

Use foundry to build and run smart contracts in the repo
Use [foundry](https://github.com/foundry-rs/foundry) to build and run smart contracts in the repository:

```bash
# Make sure foundry is up to date
foundryup

# Compile contracts and run tests in chapter 1
cd chapter1
cd chapter1/
forge build
forge test
````
````
2 changes: 1 addition & 1 deletion chapter1/bls-multisig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion chapter1/bls-multisig/python/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion chapter1/bls-multisig/rust/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion chapter1/bls-multisig/rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions chapter1/contracts/src/SimpleDelegateContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
}
}
94 changes: 94 additions & 0 deletions chapter1/contracts/test/BLS.t.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
23 changes: 23 additions & 0 deletions chapter1/contracts/test/SimpleDelegateContract.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
23 changes: 15 additions & 8 deletions chapter1/eof/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 5 additions & 11 deletions chapter1/simple-7702/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,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.

You can check out complete example in [SimpleDelegateContract.t.sol](../contracts/test/SimpleDelegateContract.t.sol)

0 comments on commit 2fee63f

Please sign in to comment.