From fcb183936826e66613e905db8fc89c807902230d Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Tue, 7 May 2024 23:00:05 +0200 Subject: [PATCH 1/8] feat(linea): introduce `gnark-mimc`, `linea-zktrie`, `linea-verifier`, `linea-light-client` --- Cargo.lock | 140 ++++ Cargo.toml | 7 + flake.nix | 3 + generated/rust/protos/Cargo.toml | 2 + generated/rust/protos/src/lib.rs | 8 + .../src/union.ibc.lightclients.linea.v1.rs | 157 ++++ lib/gnark-mimc/Cargo.toml | 24 + lib/gnark-mimc/src/lib.rs | 292 +++++++ lib/linea-verifier/Cargo.toml | 28 + lib/linea-verifier/default.nix | 11 + lib/linea-verifier/src/lib.rs | 2 + lib/linea-verifier/src/verify.rs | 106 +++ lib/linea-zktrie/Cargo.toml | 19 + lib/linea-zktrie/default.nix | 11 + lib/linea-zktrie/src/lib.rs | 2 + lib/linea-zktrie/src/node.rs | 172 ++++ lib/linea-zktrie/src/verify.rs | 781 ++++++++++++++++++ lib/scroll-verifier/src/verify.rs | 8 +- lib/unionlabs/src/ibc/lightclients.rs | 1 + lib/unionlabs/src/ibc/lightclients/linea.rs | 3 + .../ibc/lightclients/linea/client_state.rs | 120 +++ .../ibc/lightclients/linea/consensus_state.rs | 44 + .../src/ibc/lightclients/linea/header.rs | 89 ++ lib/unionlabs/src/lib.rs | 6 + lib/unionlabs/src/linea.rs | 2 + lib/unionlabs/src/linea/account.rs | 84 ++ lib/unionlabs/src/linea/proof.rs | 175 ++++ light-clients/linea-light-client/Cargo.toml | 37 + .../linea-light-client/linea-light-client.nix | 19 + .../linea-light-client/src/client.rs | 350 ++++++++ .../linea-light-client/src/contract.rs | 54 ++ .../linea-light-client/src/errors.rs | 92 +++ .../linea-light-client/src/eth_encoding.rs | 15 + light-clients/linea-light-client/src/lib.rs | 4 + .../ibc/lightclients/linea/v1/linea.proto | 62 ++ 35 files changed, 2926 insertions(+), 4 deletions(-) create mode 100644 generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs create mode 100644 lib/gnark-mimc/Cargo.toml create mode 100644 lib/gnark-mimc/src/lib.rs create mode 100644 lib/linea-verifier/Cargo.toml create mode 100644 lib/linea-verifier/default.nix create mode 100644 lib/linea-verifier/src/lib.rs create mode 100644 lib/linea-verifier/src/verify.rs create mode 100644 lib/linea-zktrie/Cargo.toml create mode 100644 lib/linea-zktrie/default.nix create mode 100644 lib/linea-zktrie/src/lib.rs create mode 100644 lib/linea-zktrie/src/node.rs create mode 100644 lib/linea-zktrie/src/verify.rs create mode 100644 lib/unionlabs/src/ibc/lightclients/linea.rs create mode 100644 lib/unionlabs/src/ibc/lightclients/linea/client_state.rs create mode 100644 lib/unionlabs/src/ibc/lightclients/linea/consensus_state.rs create mode 100644 lib/unionlabs/src/ibc/lightclients/linea/header.rs create mode 100644 lib/unionlabs/src/linea.rs create mode 100644 lib/unionlabs/src/linea/account.rs create mode 100644 lib/unionlabs/src/linea/proof.rs create mode 100644 light-clients/linea-light-client/Cargo.toml create mode 100644 light-clients/linea-light-client/linea-light-client.nix create mode 100644 light-clients/linea-light-client/src/client.rs create mode 100644 light-clients/linea-light-client/src/contract.rs create mode 100644 light-clients/linea-light-client/src/errors.rs create mode 100644 light-clients/linea-light-client/src/eth_encoding.rs create mode 100644 light-clients/linea-light-client/src/lib.rs create mode 100644 uniond/proto/union/ibc/lightclients/linea/v1/linea.proto diff --git a/Cargo.lock b/Cargo.lock index 15316e73da..9535b5295b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,45 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556cabf32c649f5d6ccfbcc5bdf9f5a7cc4aa1eb5043cdabedff3fd49dd2cab9" +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -234,17 +273,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + [[package]] name = "ark-serialize" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ + "ark-serialize-derive", "ark-std", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -3154,6 +3218,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gnark-mimc" +version = "0.1.0" +dependencies = [ + "ark-bls12-377", + "ark-bn254", + "ark-ff", + "num-bigint", + "serde", + "serde-utils", + "serde_json", + "sha3", + "thiserror", + "tiny-keccak", + "unionlabs", +] + [[package]] name = "group" version = "0.13.0" @@ -3909,6 +3990,65 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linea-light-client" +version = "0.1.0" +dependencies = [ + "base64 0.21.7", + "cosmwasm-std 1.5.2", + "ethereum-verifier", + "ethers-core", + "gnark-mimc", + "hex", + "ics008-wasm-client", + "linea-verifier", + "linea-zktrie", + "protos", + "rlp", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "serde_json", + "sha3", + "thiserror", + "tiny-keccak", + "unionlabs", +] + +[[package]] +name = "linea-verifier" +version = "0.1.0" +dependencies = [ + "ethereum-verifier", + "ethers-core", + "gnark-mimc", + "hex", + "hex-literal", + "linea-zktrie", + "rlp", + "scroll-codec", + "serde", + "serde-utils", + "serde_json", + "sha3", + "thiserror", + "unionlabs", +] + +[[package]] +name = "linea-zktrie" +version = "0.1.0" +dependencies = [ + "gnark-mimc", + "hex", + "hex-literal", + "serde", + "serde-utils", + "serde_json", + "thiserror", + "unionlabs", +] + [[package]] name = "linux-raw-sys" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 8da971da72..e8bc553da6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,10 @@ members = [ "lib/block-message", "lib/chain-utils", "lib/gnark-key-parser", + "lib/gnark-mimc", "lib/ics-008-wasm-client", "lib/ics23", + "lib/linea-verifier", "lib/macros", "lib/pg-queue", "lib/poseidon-rs", @@ -53,6 +55,7 @@ members = [ "light-clients/ethereum-light-client", "light-clients/scroll-light-client", "light-clients/tendermint-light-client", + "light-clients/linea-light-client", "tools/generate-rust-sol-bindings", "tools/keygen", @@ -63,6 +66,7 @@ members = [ "unionvisor", "voyager", "zerg", + "lib/linea-zktrie", ] [workspace.package] @@ -89,8 +93,11 @@ cometbls-groth16-verifier = { path = "lib/cometbls-groth16-verifier", default-fe contracts = { path = "generated/rust/contracts", default-features = false } ethereum-verifier = { path = "lib/ethereum-verifier", default-features = false } gnark-key-parser = { path = "lib/gnark-key-parser", default-features = false } +gnark-mimc = { path = "lib/gnark-mimc", default-features = false } ics008-wasm-client = { path = "lib/ics-008-wasm-client", default-features = false } ics23 = { path = "lib/ics23", default-features = false } +linea-verifier = { path = "lib/linea-verifier", default-features = false } +linea-zktrie = { path = "lib/linea-zktrie", default-features = false } macros = { path = "lib/macros", default-features = false } pg-queue = { path = "lib/pg-queue", default-features = false } poseidon-rs = { path = "lib/poseidon-rs", default-features = false } diff --git a/flake.nix b/flake.nix index 5d1767af92..f5050788e9 100644 --- a/flake.nix +++ b/flake.nix @@ -213,7 +213,10 @@ ./light-clients/tendermint-light-client/tendermint-light-client.nix ./light-clients/scroll-light-client/scroll-light-client.nix ./light-clients/arbitrum-light-client/arbitrum-light-client.nix + ./light-clients/linea-light-client/linea-light-client.nix ./lib/cometbls-groth16-verifier/default.nix + ./lib/linea-verifier/default.nix + ./lib/linea-zktrie/default.nix ./cosmwasm/cosmwasm.nix ./evm/evm.nix ./tools/rust-proto.nix diff --git a/generated/rust/protos/Cargo.toml b/generated/rust/protos/Cargo.toml index 1a71093aa1..1b94b5a671 100644 --- a/generated/rust/protos/Cargo.toml +++ b/generated/rust/protos/Cargo.toml @@ -273,6 +273,7 @@ proto_full = [ "union+ibc+lightclients+arbitrum+v1", "union+ibc+lightclients+cometbls+v1", "union+ibc+lightclients+ethereum+v1", + "union+ibc+lightclients+linea+v1", "union+ibc+lightclients+scroll+v1", "union+ics23+v1", "union+staking+v1", @@ -307,6 +308,7 @@ proto_full = [ "ibc+core+commitment+v1", ] "union+ibc+lightclients+ethereum+v1" = ["ibc+core+client+v1", "ibc+lightclients+tendermint+v1"] +"union+ibc+lightclients+linea+v1" = ["ibc+core+client+v1", "union+ibc+lightclients+ethereum+v1"] "union+ibc+lightclients+scroll+v1" = ["ibc+core+client+v1", "union+ibc+lightclients+ethereum+v1"] "union+ics23+v1" = [] "union+staking+v1" = ["cosmos+staking+v1beta1"] diff --git a/generated/rust/protos/src/lib.rs b/generated/rust/protos/src/lib.rs index 15e53a498a..7a69698b8e 100644 --- a/generated/rust/protos/src/lib.rs +++ b/generated/rust/protos/src/lib.rs @@ -867,6 +867,14 @@ pub mod union { // @@protoc_insertion_point(union.ibc.lightclients.ethereum.v1) } } + pub mod linea { + #[cfg(feature = "union+ibc+lightclients+linea+v1")] + // @@protoc_insertion_point(attribute:union.ibc.lightclients.linea.v1) + pub mod v1 { + include!("union.ibc.lightclients.linea.v1.rs"); + // @@protoc_insertion_point(union.ibc.lightclients.linea.v1) + } + } pub mod scroll { #[cfg(feature = "union+ibc+lightclients+scroll+v1")] // @@protoc_insertion_point(attribute:union.ibc.lightclients.scroll.v1) diff --git a/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs new file mode 100644 index 0000000000..e062557c98 --- /dev/null +++ b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs @@ -0,0 +1,157 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClientState { + #[prost(string, tag = "1")] + pub chain_id: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub l1_latest_height: + ::core::option::Option, + #[prost(string, tag = "3")] + pub l1_client_id: ::prost::alloc::string::String, + #[prost(bytes = "vec", tag = "4")] + pub l1_rollup_contract_address: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "5")] + pub l1_rollup_current_l2_block_number_slot: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "6")] + pub l1_rollup_current_l2_timestamp_slot: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "7")] + pub l1_rollup_l2_state_root_hashes_slot: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "8")] + pub l2_ibc_contract_address: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "9")] + pub l2_ibc_contract_commitment_slot: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "10")] + pub frozen_height: + ::core::option::Option, +} +impl ::prost::Name for ClientState { + const NAME: &'static str = "ClientState"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConsensusState { + #[prost(bytes = "vec", tag = "1")] + pub ibc_storage_root: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub timestamp: u64, +} +impl ::prost::Name for ConsensusState { + const NAME: &'static str = "ConsensusState"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Header { + #[prost(message, optional, tag = "1")] + pub l1_height: + ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub l1_rollup_contract_proof: ::core::option::Option, + #[prost(uint64, tag = "3")] + pub l2_block_number: u64, + #[prost(message, optional, tag = "4")] + pub l2_block_number_proof: ::core::option::Option, + #[prost(bytes = "vec", tag = "5")] + pub l2_state_root: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "6")] + pub l2_state_root_proof: ::core::option::Option, + #[prost(uint64, tag = "7")] + pub l2_timestamp: u64, + #[prost(message, optional, tag = "8")] + pub l2_timestamp_proof: ::core::option::Option, + #[prost(message, optional, tag = "9")] + pub l2_ibc_contract_proof: ::core::option::Option, +} +impl ::prost::Name for Header { + const NAME: &'static str = "Header"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MerklePath { + #[prost(bytes = "vec", tag = "1")] + pub value: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", repeated, tag = "2")] + pub proof_related_nodes: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, +} +impl ::prost::Name for MerklePath { + const NAME: &'static str = "MerklePath"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InclusionProof { + #[prost(bytes = "vec", tag = "1")] + pub key: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub leaf_index: u64, + #[prost(message, optional, tag = "3")] + pub merkle_path: ::core::option::Option, +} +impl ::prost::Name for InclusionProof { + const NAME: &'static str = "InclusionProof"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NonInclusionProof { + #[prost(bytes = "vec", tag = "1")] + pub key: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub left_leaf_index: u64, + #[prost(message, optional, tag = "3")] + pub left_proof: ::core::option::Option, + #[prost(uint64, tag = "4")] + pub right_leaf_index: u64, + #[prost(message, optional, tag = "5")] + pub right_proof: ::core::option::Option, +} +impl ::prost::Name for NonInclusionProof { + const NAME: &'static str = "NonInclusionProof"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MerkleProof { + #[prost(oneof = "merkle_proof::Proof", tags = "1, 2")] + pub proof: ::core::option::Option, +} +/// Nested message and enum types in `MerkleProof`. +pub mod merkle_proof { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Proof { + #[prost(message, tag = "1")] + Inclusion(super::InclusionProof), + #[prost(message, tag = "2")] + Noninclusion(super::NonInclusionProof), + } +} +impl ::prost::Name for MerkleProof { + const NAME: &'static str = "MerkleProof"; + const PACKAGE: &'static str = "union.ibc.lightclients.linea.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.linea.v1.{}", Self::NAME) + } +} +// @@protoc_insertion_point(module) diff --git a/lib/gnark-mimc/Cargo.toml b/lib/gnark-mimc/Cargo.toml new file mode 100644 index 0000000000..c0c4b1fe94 --- /dev/null +++ b/lib/gnark-mimc/Cargo.toml @@ -0,0 +1,24 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "gnark-mimc" +repository.workspace = true +version = "0.1.0" + +[dependencies] +ark-bls12-377 = { version = "0.4", default-features = false, features = ["curve"] } +ark-bn254 = { version = "0.4", default-features = false, features = ["curve"] } +ark-ff = { version = "0.4", default-features = false } +num-bigint = { workspace = true } +sha3 = { workspace = true } +thiserror = { workspace = true } +tiny-keccak = { workspace = true, features = ["keccak"] } +unionlabs = { workspace = true } + +[dev-dependencies] +serde = { workspace = true } +serde-utils = { workspace = true } +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/lib/gnark-mimc/src/lib.rs b/lib/gnark-mimc/src/lib.rs new file mode 100644 index 0000000000..2f6ad1d594 --- /dev/null +++ b/lib/gnark-mimc/src/lib.rs @@ -0,0 +1,292 @@ +use std::marker::PhantomData; + +use ark_ff::PrimeField; +use num_bigint::BigUint; +use sha3::Digest; +use unionlabs::{ + errors::{ExpectedLength, InvalidLength}, + hash::H256, +}; + +// https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L31 +pub const GNARK_SEED: &[u8] = b"seed"; + +// https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L30 +pub const GNARK_BN254_ROUNDS: usize = 110; +// https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L158 +pub const GNARK_BN254_E: u64 = 5; + +// https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bls12-377/fr/mimc/mimc.go#L30C17-L30C19 +pub const GNARK_BLS12_377_ROUNDS: usize = 62; +// https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bls12-377/fr/mimc/mimc.go#L158 +pub const GNARK_BLS12_377_E: u64 = 17; + +pub type MiMCBn254Constants = MiMCConstants; +pub type MiMCBn254<'a> = MiMC<'a, ark_bn254::Fr, { GNARK_BN254_ROUNDS }, { GNARK_BN254_E }>; + +pub type MiMCBls12377Constants = MiMCConstants; +pub type MiMCBls12377<'a> = + MiMC<'a, ark_bls12_377::Fr, { GNARK_BLS12_377_ROUNDS }, { GNARK_BLS12_377_E }>; + +pub fn new_mimc_constants_bls12_377() -> MiMCBls12377Constants { + MiMCConstants::new(GNARK_SEED) +} + +pub fn new_mimc_bls12_377(constants: &MiMCBls12377Constants) -> MiMCBls12377 { + MiMC::new(&constants) +} + +pub fn mimc_sum_bl12377( + constants: &MiMCBls12377Constants, + elements: impl AsRef<[u8]>, +) -> Result { + Ok(new_mimc_bls12_377(constants) + .update(elements)? + .finalize() + .try_into() + .expect("impossible")) +} + +pub fn new_mimc_constants_bn254() -> MiMCConstants { + MiMCConstants::new(GNARK_SEED) +} + +pub fn new_mimc_bn254(constants: &MiMCBn254Constants) -> MiMCBn254 { + MiMC::new(&constants) +} + +pub fn mimc_sum_bn254( + constants: &MiMCBn254Constants, + elements: impl AsRef<[u8]>, +) -> Result { + Ok(new_mimc_bn254(constants) + .update(elements)? + .finalize() + .try_into() + .expect("impossible")) +} + +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +pub enum Error { + #[error("invalid length {0}")] + InvalidLength(InvalidLength), + #[error("invalid field element: {value:?}")] + InvalidFieldElement { value: Vec }, +} + +#[derive(Debug, PartialEq)] +pub struct MiMCConstants([F; K]); + +impl AsRef<[F; K]> for MiMCConstants { + fn as_ref(&self) -> &[F; K] { + &self.0 + } +} + +impl MiMCConstants { + // TODO: move this to build.rs as a constant preset + // https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L179 + pub fn new(seed: &[u8]) -> Self { + let keccak = |x: &[u8]| sha3::Keccak256::new().chain_update(x).finalize(); + let round_zero = keccak(seed); + let (_, constants) = + (0..K).fold((round_zero, [F::zero(); K]), |(round, mut constants), i| { + let constant = keccak(&round); + constants[i] = F::from_be_bytes_mod_order(&constant); + (constant, constants) + }); + Self(constants) + } +} + +pub struct MiMC<'a, F, const K: usize, const E: u64> { + constants: &'a MiMCConstants, + data: Vec, + _marker: PhantomData, +} + +impl<'a, F: PrimeField, const K: usize, const E: u64> MiMC<'a, F, K, E> { + pub const FIELD_ELEMENT_BYTES_LEN: usize = + (F::MODULUS_BIT_SIZE.next_power_of_two() / 8) as usize; + + pub fn new(constants: &'a MiMCConstants) -> Self { + Self { + _marker: PhantomData, + constants, + data: Vec::default(), + } + } + + // https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L105 + pub fn update(mut self, elements: impl AsRef<[u8]>) -> Result { + // Slight difference, we only accept a mutiple of the field. No hidden, implicit padding. + let elements = elements.as_ref(); + if elements.len() % Self::FIELD_ELEMENT_BYTES_LEN != 0 { + return Err(Error::InvalidLength(InvalidLength { + expected: ExpectedLength::Exact(0), + found: elements.len(), + })); + } + let nb_of_field_elements = elements.len() / Self::FIELD_ELEMENT_BYTES_LEN; + for i in 0..nb_of_field_elements { + self.data.push( + F::from_bigint( + BigUint::from_bytes_be( + &elements[i * Self::FIELD_ELEMENT_BYTES_LEN + ..i * Self::FIELD_ELEMENT_BYTES_LEN + Self::FIELD_ELEMENT_BYTES_LEN], + ) + .try_into() + .map_err(|_| Error::InvalidFieldElement { + value: elements.to_vec(), + })?, + ) + .ok_or(Error::InvalidFieldElement { + value: elements.to_vec(), + })?, + ) + } + Ok(self) + } + + // Inlined version of: + // https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L169 + pub fn finalize(&self) -> Vec { + let sum = self.data.iter().fold(F::zero(), |k_acc, &data_i| { + // encrypt + let r = self.constants.as_ref().iter().fold(data_i, |m_acc, c_i| { + // (m + k + c) ^ e + let m_k_c = m_acc + k_acc + c_i; + F::pow(&m_k_c, [E]) + }) + k_acc; + k_acc + r + data_i + }); + // Extremely ugly interface from arkworks, not gonna lie + let mut buffer = vec![0u8; Self::FIELD_ELEMENT_BYTES_LEN]; + sum.serialize_uncompressed(&mut buffer[..]) + .expect("impossible"); + // No way to provide endianness when serializing, ark is little, gnark is big + buffer.reverse(); + buffer + } + + pub fn reset(&mut self) { + self.data.clear(); + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use crate::{MiMC, MiMCConstants, GNARK_BN254_E, GNARK_BN254_ROUNDS, GNARK_SEED}; + + #[derive(Deserialize)] + struct Test { + #[serde(rename = "in")] + #[serde(with = "::serde_utils::hex_string_list")] + i: Vec>, + #[serde(rename = "out")] + #[serde(with = "::serde_utils::hex_string")] + o: Vec, + } + + // The hex strings have been padded to even number of characters + // https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/test_vectors/vectors.json + #[test] + fn test_bn254_gnark_vectors() { + let tests_vector = r#" + [ + { + "in": [ + "0x105afe02a0f7648bee1669b05bf7ae69a37dbb6c86ebbee325dffe97ac1f8e64" + ], + "out": "0x263b9e754e6c611d646e65b16c48f51ab7bc0abedfae9c6ea04e2814ed28daf4" + }, + { + "in": [ + "0x00bc35f0589078e34d9139a357175d0e74b843e3de2f56bb6a5f18032ff3f627" + ], + "out": "0x103e2c8f50dec5248fd68f9778429252364ff239b123977a697356082f524f25" + }, + { + "in": [ + "0x208f0b283064057cf912b65eaa51e2cb2b85fdbe2fd0b2841f4bca59321ef1bf", + "0x226bee7671296d05c998a5b5b4b1d25f478696d5997ba4f4be1a682c56a69e11" + ], + "out": "0x1476ada1433d73817a69e45c84c5d452ad858f2dfdb1f7e4da203d3c4fd42222" + }, + { + "in": [ + "0x00995d448ab1fc86dd4874ebcbc0a7eea41acbe2c76e300aa73a1a0e63d5bc1b", + "0x2190a93f59d9f8cbb4f6236c5b7bf511aec80e88bec71dad4f5bbba9346ff5e4" + ], + "out": "0x0cd4ef5556a9413b6bb98d12aba6ed9b937f0adce41ba618a212fdcb1629737a" + }, + { + "in": [ + "0x06680de43f6cf410d4a8ed2893e58a8b740bac14f9dbdadbc8623c06027418a1", + "0x059323b0ab7043f559674eba263da812eae9e933b0c1bad55f8118d0caaa7479", + "0x16b161c8de7184ccc6b1b6fcddb562789a68eeaec174376f1157dfb3db310787" + ], + "out": "0x118e5255aabe7a3b6a5dde6ca28de461d36f802653885c665745fc4e6ca0f709" + }, + { + "in": [ + "0x1ab45102976d9ec683b46e7e7b4163055d1ab768d6bbd56cf95f3bca15d58020", + "0x18ff125903dc8352ca63c7a436f0425b4b7ddf7e487fb9ffd30f151993571b57", + "0x2cbfaa412f4b612d611acaab79a9e1c06b7094d8754fdbc085db28f2e4dd09ab" + ], + "out": "0x025fa55a9896d91d9617d9512e061d754336816f748bf07566591ec5cf4680dd" + }, + { + "in": [ + "0x2eddc35df3778e61c6571bcad90ab41dbf3cb61f4fd203d1922eb4fafde99136", + "0x0905c2010ece23e26373b38b6fc8b3c932a59443af656fb164e22b2bcf940b5a", + "0x22e63a3eb565d13c42c7d520c7b6112534b1c666653452f743b80bcc2d878455", + "0x096dff377f354f792685a7e740e3024409c24a379425ff63e3ce320b1e9bc471" + ], + "out": "0x18ea2fd58b4f3274193f3e73bded685426f114a8e7bc373c1aee3e6f0125787b" + }, + { + "in": [ + "0x05f3e89a9418877cd586de7c5cb061e6701a1bd69074cc7bd97c7c39d8f955eb", + "0x20cdf81f33b895b442d47357bd80e1eca03f410d808324f6d151dc68ab354a1f", + "0x12f4c27e5a2e80dd67fb33928c4e6219a8bdc89b498ed32acb02d725cec90076", + "0x1d6b52c237f0f74f0c50755627eed2610608488b54b0a3941a4623b1d435232a" + ], + "out": "0x2f237dea4570779296e2866383740b8e9ccf59577f8ff729880dadb58ae34d47" + }, + { + "in": [ + "0x262c77f7fdef59c80e0a9d4ece6d18fb6d64ebaacfc21921f44c5adc19698c6a", + "0x087bb7a78b27d19c5a502fbb087e48785d2777cff15d7b493901a8e528b64ee0", + "0x2a8a0e2a793fdd5bc340857b355f2b4c00c2723cefdf8515bda5beef458fca2b", + "0x2d4232cb721888f71997377de5ca195a5ae03d3eb9c87d2c04ef3664759036da", + "0x2f623ee75518430e291d42e7aaa75f5291a1bbfed125426d39270046a26be35a" + ], + "out": "0x06246dee7e2d9560a074c50a06e6525e4a58395cea4a893c49d71e373f19b9d6" + }, + { + "in": [ + "0x14b09f9af90cafa8a4e508f5289a6868804f98d3a724162999193e6c4bf752ea", + "0x0727359808271f360a6136389a9e2d5b1bb6ff3e8c4125ca03005892446ac17d", + "0x2b4abbd9943b201c1f75754833684f9eb15728a2ba646c53c2614bea7c9b968b", + "0x08e0ddb80366c4c6c7dcb9090f4862d64ef40677d324a76a82e06ca33ad29a09", + "0x170e8c954ca7e6526b743e92f796488afe5083a9c549358f730659c3e1cdbafa" + ], + "out": "0x1a2e7cffb5183898a8f4f6d4699bc272665ebffbb9d095576d2e21c45f012358" + } + ] + "#; + let tests = serde_json::from_str::>(tests_vector).unwrap(); + let constants = MiMCConstants::::new(GNARK_SEED); + for test in tests { + let input = test.i.into_iter().flatten().collect::>(); + let sum = MiMC::<_, { GNARK_BN254_ROUNDS }, { GNARK_BN254_E }>::new(&constants) + .update(&input) + .unwrap() + .finalize(); + assert_eq!(sum, test.o) + } + } +} diff --git a/lib/linea-verifier/Cargo.toml b/lib/linea-verifier/Cargo.toml new file mode 100644 index 0000000000..cdd13b27d1 --- /dev/null +++ b/lib/linea-verifier/Cargo.toml @@ -0,0 +1,28 @@ +[package] +edition = { workspace = true } +license-file = { workspace = true } +name = "linea-verifier" +repository = { workspace = true } +version = "0.1.0" + +[lints] +workspace = true + +[package.metadata.crane] +test-include = ["lib/scroll-verifier/tests"] + +[dependencies] +ethereum-verifier = { workspace = true } +ethers-core.workspace = true +gnark-mimc = { workspace = true } +hex = { workspace = true } +hex-literal.workspace = true +linea-zktrie = { workspace = true } +rlp = { workspace = true } +scroll-codec.workspace = true +serde = { workspace = true } +serde-utils = { workspace = true } +serde_json = { workspace = true } +sha3 = { workspace = true } +thiserror = { workspace = true } +unionlabs = { workspace = true } diff --git a/lib/linea-verifier/default.nix b/lib/linea-verifier/default.nix new file mode 100644 index 0000000000..3cb31a55ff --- /dev/null +++ b/lib/linea-verifier/default.nix @@ -0,0 +1,11 @@ +{ ... }: { + perSystem = { self', pkgs, system, config, crane, stdenv, dbg, lib, ... }: + let + linea-verifier-all = (crane.buildWorkspaceMember { + crateDirFromRoot = "lib/linea-verifier"; + }); + in + { + inherit (linea-verifier-all) checks; + }; +} diff --git a/lib/linea-verifier/src/lib.rs b/lib/linea-verifier/src/lib.rs new file mode 100644 index 0000000000..aa376f796c --- /dev/null +++ b/lib/linea-verifier/src/lib.rs @@ -0,0 +1,2 @@ +pub mod verify; +pub use verify::*; diff --git a/lib/linea-verifier/src/verify.rs b/lib/linea-verifier/src/verify.rs new file mode 100644 index 0000000000..c5be5e2fb3 --- /dev/null +++ b/lib/linea-verifier/src/verify.rs @@ -0,0 +1,106 @@ +use core::fmt::Debug; + +use ethereum_verifier::{verify_account_storage_root, verify_storage_proof}; +use gnark_mimc::new_mimc_constants_bls12_377; +use sha3::Digest; +use unionlabs::{ + hash::H256, + ibc::lightclients::linea::{client_state::ClientState, header::Header}, + linea::account::ZkAccount, + uint::U256, +}; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { + #[error("invalid rollup contract proof {0}")] + InvalidRollupContractProof(ethereum_verifier::Error), + #[error("invalid l2 block number proof {0}")] + InvalidL2BlockNumberProof(ethereum_verifier::Error), + #[error("invalid l2 timestamp proof {0}")] + InvalidL2TimestampProof(ethereum_verifier::Error), + #[error("invalid l2 state root {0}")] + InvalidL2StateRootProof(ethereum_verifier::Error), + #[error("invalid l2 ibc contract proof {0}")] + InvalidL2IbcContractProof(linea_zktrie::verify::Error), + #[error("the l2 ibc contract proof must be an inclusion proof, verify the address?")] + L2IbcContractProofIsNotInclusion, + #[error("node value mismatch")] + ValueMismatch, +} + +/* +1. assert rootHash(rollup) in l1StateRoot +2. assert rollup.currentL2BlockNumber = l2BlockNumber +3. assert rollup.currentL2Timestamp = l2Timestamp +4. assert rollup.stateRootHashes[l2BlockNumber] = l2StateRoot +5. assert rootHash(l2IbcContract) in l2StateRoot + */ +pub fn verify_header( + client_state: ClientState, + header: Header, + l1_state_root: H256, +) -> Result<(), Error> { + // 1. + verify_account_storage_root( + l1_state_root, + &client_state.l1_rollup_contract_address, + &header.l1_rollup_contract_proof.proof, + &header.l1_rollup_contract_proof.storage_root, + ) + .map_err(Error::InvalidRollupContractProof)?; + + // 2. + verify_storage_proof( + header.l1_rollup_contract_proof.storage_root, + client_state.l1_rollup_current_l2_block_number_slot, + &rlp::encode(&header.l2_block_number), + &header.l2_block_number_proof.proofs[0].proof, + ) + .map_err(Error::InvalidL2BlockNumberProof)?; + + // 3. + verify_storage_proof( + header.l1_rollup_contract_proof.storage_root, + client_state.l1_rollup_current_l2_timestamp_slot, + &rlp::encode(&header.l2_timestamp), + &header.l2_timestamp_proof.proofs[0].proof, + ) + .map_err(Error::InvalidL2TimestampProof)?; + + // 4. + verify_storage_proof( + header.l1_rollup_contract_proof.storage_root, + state_root_hashes_mapping_key( + client_state.l1_rollup_l2_state_root_hashes_slot, + header.l2_block_number.into(), + ), + &rlp::encode(&U256::from_be_bytes(header.l2_state_root.into())), + &header.l2_state_root_proof.proofs[0].proof, + ) + .map_err(Error::InvalidL2StateRootProof)?; + + // 5. + // TODO: perhaps force the proof to be an actual inclusion proof off-chain + let account = linea_zktrie::verify::verify::( + &new_mimc_constants_bls12_377(), + &header.l2_ibc_contract_proof, + header.l2_state_root, + client_state.l2_ibc_contract_address, + ) + .map_err(Error::InvalidL2IbcContractProof)?; + + match account { + Some(_) => Ok(()), + None => Err(Error::L2IbcContractProofIsNotInclusion), + } +} + +pub fn state_root_hashes_mapping_key(slot: U256, l2_block_number: U256) -> U256 { + U256::from_be_bytes( + sha3::Keccak256::new() + .chain_update(l2_block_number.to_be_bytes()) + .chain_update(slot.to_be_bytes()) + .finalize() + .into(), + ) +} diff --git a/lib/linea-zktrie/Cargo.toml b/lib/linea-zktrie/Cargo.toml new file mode 100644 index 0000000000..fc573c97c1 --- /dev/null +++ b/lib/linea-zktrie/Cargo.toml @@ -0,0 +1,19 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "linea-zktrie" +repository.workspace = true +version = "0.1.0" + +[dependencies] +gnark-mimc = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } +serde = { workspace = true } +serde-utils = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +unionlabs = { workspace = true } + +[lints] +workspace = true diff --git a/lib/linea-zktrie/default.nix b/lib/linea-zktrie/default.nix new file mode 100644 index 0000000000..ea65664410 --- /dev/null +++ b/lib/linea-zktrie/default.nix @@ -0,0 +1,11 @@ +{ ... }: { + perSystem = { self', pkgs, system, config, crane, stdenv, dbg, lib, ... }: + let + linea-zktrie-all = (crane.buildWorkspaceMember { + crateDirFromRoot = "lib/linea-zktrie"; + }); + in + { + inherit (linea-zktrie-all) checks; + }; +} diff --git a/lib/linea-zktrie/src/lib.rs b/lib/linea-zktrie/src/lib.rs new file mode 100644 index 0000000000..f2bcdc7d85 --- /dev/null +++ b/lib/linea-zktrie/src/lib.rs @@ -0,0 +1,2 @@ +pub mod node; +pub mod verify; diff --git a/lib/linea-zktrie/src/node.rs b/lib/linea-zktrie/src/node.rs new file mode 100644 index 0000000000..9641d43636 --- /dev/null +++ b/lib/linea-zktrie/src/node.rs @@ -0,0 +1,172 @@ +use gnark_mimc::{mimc_sum_bl12377, MiMCBls12377Constants}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + errors::{ExpectedLength, InvalidLength}, + hash::H256, + ByteArrayExt, +}; + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L27 +pub const SUB_TRIE_ROOT_PATH: [u8; 1] = [1]; + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/LeafType.java#L21-L24 +pub const LEAF_TYPE_VALUE: u8 = 0x16; + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathGenerator.java#L34 +pub fn bytes_to_leaf_path(bytes: &[u8], terminator_path: u8) -> Vec { + let mut path = vec![0u8; bytes.len() * 2 + 1]; + let mut j = 0; + for i in j..bytes.len() { + let b = bytes[i]; + path[j] = b >> 4 & 15; + path[j + 1] = b & 15; + j += 2; + } + path[j] = terminator_path; + path +} + +// TODO: use bitvec +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L82 +pub fn node_index_to_bytes(trie_depth: usize, node_index: u64) -> Vec { + hex::decode(&format!("{node_index:0>trie_depth$b}")).unwrap() +} + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L71 +pub fn get_leaf_path(trie_depth: usize, node_index: u64) -> Vec { + [ + SUB_TRIE_ROOT_PATH.as_ref(), + &bytes_to_leaf_path( + &node_index_to_bytes(trie_depth, node_index), + LEAF_TYPE_VALUE, + ), + ] + .concat() +} + +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub struct EmptyLeafNode {} + +impl EmptyLeafNode { + // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/EmptyLeafNode.java#L80 + pub const HASH: H256 = H256([0u8; 32]); +} + +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub struct LeafNode { + pub previous: H256, + pub next: H256, + pub hashed_key: H256, + pub value: H256, +} + +impl LeafNode { + // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/LeafNode.java#L56 + pub fn hash(&self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377( + constants, + [ + self.previous.as_ref(), + self.next.as_ref(), + self.hashed_key.as_ref(), + self.value.as_ref(), + ] + .concat(), + ) + } +} + +impl TryFrom<&[u8]> for LeafNode { + type Error = InvalidLength; + fn try_from(value: &[u8]) -> Result { + let values = <[u8; 128]>::try_from(value).map_err(|_| InvalidLength { + expected: ExpectedLength::Exact(128), + found: value.len(), + })?; + Ok(Self { + previous: values.array_slice::<0, 32>().into(), + next: values.array_slice::<32, 32>().into(), + hashed_key: values.array_slice::<64, 32>().into(), + value: values.array_slice::<96, 32>().into(), + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub struct BranchNode { + pub left: H256, + pub right: H256, +} + +impl BranchNode { + // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/BranchNode.java#L82 + pub fn hash(&self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377( + constants, + [self.left.as_ref(), self.right.as_ref()].concat(), + ) + } +} + +impl TryFrom<&[u8]> for BranchNode { + type Error = InvalidLength; + fn try_from(value: &[u8]) -> Result { + let values = <[u8; 64]>::try_from(value).map_err(|_| InvalidLength { + expected: ExpectedLength::Exact(64), + found: value.len(), + })?; + Ok(Self { + left: values.array_slice::<0, 32>().into(), + right: values.array_slice::<32, 32>().into(), + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub struct RootNode { + pub next_free_node: H256, + pub child_hash: H256, +} + +impl RootNode { + // Same as branch + pub fn hash(&self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377( + constants, + [self.next_free_node.as_ref(), self.child_hash.as_ref()].concat(), + ) + } +} + +impl TryFrom<&[u8]> for RootNode { + type Error = InvalidLength; + fn try_from(value: &[u8]) -> Result { + let values = <[u8; 64]>::try_from(value).map_err(|_| InvalidLength { + expected: ExpectedLength::Exact(64), + found: value.len(), + })?; + Ok(Self { + next_free_node: values.array_slice::<0, 32>().into(), + child_hash: values.array_slice::<32, 32>().into(), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Node { + EmptyLeaf(EmptyLeafNode), + Leaf(LeafNode), + Branch(BranchNode), + Root(RootNode), +} + +impl Node { + pub fn hash(&self, constants: &MiMCBls12377Constants) -> Result { + match self { + Node::Leaf(node) => node.hash(constants), + Node::Branch(node) => node.hash(constants), + Node::Root(node) => node.hash(constants), + Node::EmptyLeaf(_) => return Ok(EmptyLeafNode::HASH), + } + } +} diff --git a/lib/linea-zktrie/src/verify.rs b/lib/linea-zktrie/src/verify.rs new file mode 100644 index 0000000000..81f8112e2b --- /dev/null +++ b/lib/linea-zktrie/src/verify.rs @@ -0,0 +1,781 @@ +use gnark_mimc::{mimc_sum_bl12377, MiMCBls12377, MiMCBls12377Constants}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + errors::InvalidLength, + hash::{H160, H256}, + linea::{ + account::{MimcSafeBytes, ZkAccount}, + proof::{MerklePath, MerkleProof, NonInclusionProof}, + }, + uint::U256, +}; + +use crate::node::{get_leaf_path, BranchNode, LeafNode, Node, RootNode}; + +pub const DIRECTION_LEFT: u8 = 0; +pub const DIRECTION_RIGHT: u8 = 1; + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/ZKTrie.java#L64C1-L65C47 +pub const ZK_TRIE_DEPTH: usize = 40; + +pub trait ZkKey { + fn hash(self, constants: &MiMCBls12377Constants) -> Result; +} + +pub trait ZkValue { + type Key: ZkKey; + + fn hash(self, constants: &MiMCBls12377Constants) -> Result; +} + +impl ZkKey for H160 { + fn hash(self, constants: &MiMCBls12377Constants) -> Result { + let mut padded_key = [0u8; 32]; + padded_key[12..32].copy_from_slice(self.as_ref()); + mimc_sum_bl12377(constants, &padded_key) + } +} + +impl ZkValue for ZkAccount { + type Key = H160; + + fn hash(self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377(constants, self.into_bytes()) + } +} + +impl ZkKey for H256 { + fn hash(self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377(constants, MimcSafeBytes::from(self.0).into_bytes()) + } +} + +impl ZkValue for H256 { + type Key = H256; + + fn hash(self, constants: &MiMCBls12377Constants) -> Result { + mimc_sum_bl12377(constants, MimcSafeBytes::from(self.0).into_bytes()) + } +} + +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +pub enum Error { + #[error("invalid direction {0}")] + InvalidDirection(u8), + #[error("missing root node")] + MissingRoot, + #[error("missing leaf node")] + MissingLeaf, + #[error("invalid field element length: {0}")] + InvalidLength(InvalidLength), + #[error("invalid mimc hashing: {0:?}")] + MimcError(gnark_mimc::Error), + #[error("could not decode leaf value")] + CouldntDecodeValue, + #[error("invalid trie root, actual: {actual}, expected: {expected}")] + RootMismatch { actual: H256, expected: H256 }, + #[error("invalid subtrie root, actual: {actual}, expected: {expected}")] + SubtrieRootMismatch { actual: H256, expected: H256 }, + #[error("key mismatch, actual: {actual}, expected: {expected}")] + KeyMismatch { actual: H256, expected: H256 }, + #[error("value mismatch, actual: {actual}, expected: {expected}")] + ValueMismatch { actual: H256, expected: H256 }, + #[error("non adjacent node, left: {left:?}, right: {right:?}")] + NonAdjacentNode { left: LeafNode, right: LeafNode }, + #[error("key not in center, left: {left}, key: {key} right: {right}")] + KeyNotInCenter { left: H256, key: H256, right: H256 }, +} + +impl From for Error { + fn from(value: InvalidLength) -> Self { + Self::InvalidLength(value) + } +} + +impl From for Error { + fn from(value: gnark_mimc::Error) -> Self { + Self::MimcError(value) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct VerifiablePath { + pub root: RootNode, + pub path: Vec, + pub leaf: LeafNode, +} + +impl TryFrom<&MerklePath> for VerifiablePath { + type Error = Error; + fn try_from(value: &MerklePath) -> Result { + let root = RootNode::try_from( + value + .proof_related_nodes + .first() + .ok_or(Error::MissingRoot)? + .as_ref(), + )?; + let leaf = LeafNode::try_from( + value + .proof_related_nodes + .last() + .ok_or(Error::MissingLeaf)? + .as_ref(), + )?; + // Minus root/leaf + let inner_path_len = value.proof_related_nodes.len() - 2; + let path = value + .proof_related_nodes + .iter() + .skip(1) + .take(inner_path_len) + .map(|node| { + if node.len() == MiMCBls12377::FIELD_ELEMENT_BYTES_LEN * 2 { + BranchNode::try_from(node.as_ref()).map(Node::Branch) + } else { + LeafNode::try_from(node.as_ref()).map(Node::Leaf) + } + }) + .collect::, _>>()?; + Ok(VerifiablePath { root, path, leaf }) + } +} + +pub fn verify TryFrom<&'a [u8]>>( + constants: &MiMCBls12377Constants, + proof: &MerkleProof, + root: H256, + key: V::Key, +) -> Result, Error> { + match proof { + MerkleProof::Inclusion(inclusion_proof) => verify_inclusion::( + constants, + inclusion_proof.leaf_index, + &inclusion_proof.proof, + root, + Some(key), + ) + .map(|(_, value)| Some(value)), + MerkleProof::NonInclusion(noninclusion_proof) => { + verify_noninclusion::(constants, noninclusion_proof, root, key).map(|_| None) + } + } +} + +pub fn verify_noninclusion TryFrom<&'a [u8]>>( + constants: &MiMCBls12377Constants, + noninclusion_proof: &NonInclusionProof, + root: H256, + key: V::Key, +) -> Result<(), Error> { + // left in root + let (left_path, _) = verify_inclusion::( + constants, + noninclusion_proof.left_leaf_index, + &noninclusion_proof.left_proof, + root, + None, + )?; + // right in root + let (right_path, _) = verify_inclusion::( + constants, + noninclusion_proof.right_leaf_index, + &noninclusion_proof.right_proof, + root, + None, + )?; + // N+.Prev == i- + if U256::from_be_bytes(right_path.leaf.previous.0) + != U256::from(noninclusion_proof.left_leaf_index) + { + return Err(Error::NonAdjacentNode { + left: left_path.leaf, + right: right_path.leaf, + }); + } + // N-.Next == i+ + if U256::from_be_bytes(left_path.leaf.next.0) != U256::from(noninclusion_proof.right_leaf_index) + { + return Err(Error::NonAdjacentNode { + left: left_path.leaf, + right: right_path.leaf, + }); + } + // HKey- < hash(k) < HKey+ + let recomputed_key = key.hash(constants)?; + if left_path.leaf.hashed_key >= recomputed_key || recomputed_key >= right_path.leaf.hashed_key { + return Err(Error::KeyNotInCenter { + left: left_path.leaf.hashed_key, + key: recomputed_key, + right: right_path.leaf.hashed_key, + }); + } + Ok(()) +} + +pub fn verify_inclusion TryFrom<&'a [u8]>>( + constants: &MiMCBls12377Constants, + leaf_index: u64, + merkle_path: &MerklePath, + root: H256, + key: Option, +) -> Result<(VerifiablePath, V), Error> { + let leaf_path = get_leaf_path(ZK_TRIE_DEPTH, leaf_index); + let verifiable_path = VerifiablePath::try_from(merkle_path)?; + // Verify the top root hash + let recomputed_root = verifiable_path.root.hash(constants)?; + if root != recomputed_root { + return Err(Error::RootMismatch { + actual: recomputed_root, + expected: root, + }); + } + match key { + Some(key) => { + // Verify that they leaf is related to our key + let recomputed_key = key.hash(constants)?; + if verifiable_path.leaf.hashed_key != recomputed_key { + return Err(Error::KeyMismatch { + actual: recomputed_root, + expected: verifiable_path.leaf.hashed_key, + }); + } + } + // For non inclusion proof, we don't know the key of the left/right + // nodes. We instead verify that the expected key is sandwitched after + // verifying inclusion. Hence, nothing is done there. + None => {} + } + // The value is decoded then hashed, the decoding is required as the value + // may need to be transformed before being hashed (ZkAccount keccak field + // that need to be split in two elements to fit in the scalar field for + // instane) + let value = V::try_from(merkle_path.value.as_ref()).map_err(|_| Error::CouldntDecodeValue)?; + // Verify that the value is related to the leaf + let recomputed_value = value.clone().hash(constants)?; + if verifiable_path.leaf.value != recomputed_value { + return Err(Error::ValueMismatch { + actual: recomputed_root, + expected: verifiable_path.leaf.value, + }); + } + // The algorithm we use is slightly more explicit, we actually extract both + // the root and leaf so the inner path must exclude both root leaf + // Minus root/leaf + let inner_path_len = leaf_path.len() - 2; + let inner_path = leaf_path.into_iter().skip(1).take(inner_path_len); + let leaf_hash = verifiable_path.leaf.hash(constants)?; + // Starts with the leaf hash, then recursively walk back to the tip of the + // tree. + let subtrie_root = verifiable_path + .path + .iter() + .zip(inner_path) + .rev() + .into_iter() + .try_fold( + leaf_hash, + |current_hash, (node, direction)| -> Result { + let node_hash = node.hash(constants)?; + let to_hash = match direction { + // We went on the left branch, we hash against the right sibling + DIRECTION_LEFT => [current_hash.as_ref(), node_hash.as_ref()].concat(), + // We went on the right branch, we hash against the left sibling + DIRECTION_RIGHT => [node_hash.as_ref(), current_hash.as_ref()].concat(), + d => return Err(Error::InvalidDirection(d)), + }; + Ok(mimc_sum_bl12377(constants, to_hash)?) + }, + )?; + // Verify the subtrie root + if verifiable_path.root.child_hash != subtrie_root { + return Err(Error::SubtrieRootMismatch { + actual: subtrie_root, + expected: verifiable_path.root.child_hash, + }); + } + Ok((verifiable_path, value)) +} + +#[cfg(test)] +mod tests { + use gnark_mimc::{new_mimc_bls12_377, new_mimc_constants_bls12_377}; + use hex_literal::hex; + use unionlabs::{ + hash::{H160, H256}, + linea::{account::ZkAccount, proof::GetProof}, + }; + + use super::verify; + + #[test] + fn test_account_and_storage_inclusion() { + let raw = r#" +{ + "accountProof": { + "key": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789", + "leafIndex": 65362, + "proof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000120fe0393507c456718a986386c7923fe68b87c29d83ac7f7ce1cdb49afc7e66a4771", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x09cbd26c486bc2217bce59337120283f655a7ba65075f98059249f471812d0480b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0c5c4d122720c4d6e7866d9b6bc6171c6259be90095976b665406ccf2dc6a8950305d7ebd7da4f82f061632eb7ec0c3060f51af848661d479bb64003f0fc5342", + "0x0c4762f6af9f09a529e70f0b34b7afafe2bba8944eccdcdb95cf13e0ff00ab2209d8b650f132967dba1764abe34c3d446311503ba7712d5f474a6e159b085b5f", + "0x0c3474a51e2654aca28b15add106ac676d92b9416ee788ac0b88873e77a009660176016fa85f1ba2375f784c72fae85763e12018e3e781c306f97ad9f826a22e", + "0x108459110262f154aef2d43fce77d314a7ec867f0068987716ff51582847e498009a6be6c408befa4eb7e6141fd427a2ce6489d50bf5f9de6bde9e100ada3482", + "0x118d3c53f9a3ea556029e867af93e9b4450cbacdf4dff29859e399ae16468e5102cefeff18d2980c8a9253c4609506472ba4764ea99efa6324dacf34740d9f05", + "0x005d88c799974510f99c04afdaba0f6b8f62edd55d8d89910009e148385a72c30a8fac91e2023660e8ac50ff082578361ba0901b16fe691f9b78044cbf6d1c4b", + "0x106f788c7d5990bec78f6c9cadd15604c99a8f1d56c875d324bb5ece63d83f3606694c69c43303aa1c614d60ed8fc66838f368b134cfc1ab00b6c83b2b5b3c8c", + "0x0ba8fdb8888982dde981f8e2cc9177c8c3ce0607661e113604e436951776de9c0b9ec8fec4b0696c73e04fd6bee4aa345633d23ef0c6bc4e4bbcf757af2677f0", + "0x0f5ae90881ea3398fd1a14fb83babc2335dfc4e6298aada1d827042d67dea48f0dee0a62e8ff86baddb091105d845c862089fe2f1963cd3798d636035da4d518", + "0x03d41bdb96726bf7f745784e42eef043c8b797f788d9720e36e460502e14c9fb0923e0e0228d2fe8619e30581e3e225d4e99e0daa011e15ac34c28fa30ea2989", + "0x11335bc4bf8a15d8c116cbdfe74242e80c7f60ac1a614d00f99fb9e1148126930502f7b7740708503e3858bc6df707cf4a1a751bcef3f2a5eb6eff9d8efa5cf8", + "0x0d60d90907794deaabe1e532a128e17ac94ec30339f3e367bc9ecd0aa40fd8b6009f71be21f99f29acd62b42787c99e5192646f808306fff0960ef5cd9a5ac16", + "0x0a6fd861ba25def420f5503fbbc4e0de2e54b4fbf0b22364e4a188eaf72ac58c02e49a2a28faca35409f471b4d981951aeabba2f091a427a2e88c53d1c7eeed3", + "0x0a93ecccd90368342584da9a8623e89a7a71d36f1da58d9874d50c045587138b0476d671e749bd2cd45fe416e1409caa22863f8cebdf926920a9f68b150d92d7", + "0x0821de61351452c22cf6bdafcd85be9a8cb3c2ad0af51f871d44221575785f9d12200803e31923cc68d6c9b906876643688e3a7ccb21264f933028b060564e4d", + "0x0000000000000000000000000000000000000000000000000000000000001c1e000000000000000000000000000000000000000000000000000000000000a354000036e661469dd70081ada16334d16a4049a124e261cd93def5fff88f85afda01b285fb7d6e0c7e05505a348777221f3c9fb491bbbbea4853e62e93f415efe7", + "0x000000000000000000000000000000000000000000000000000000000000894100000000000000000000000000000000000000000000000000000000000002db104a10331d6a854148a10b11c19cf2abae0412c9909ecefca54adc135ee57a950481fe75941093272afb1f8f76353afad6b89b1c19c383b07730c6f160b59243" + ], + "value": "0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000017d7498da306d2911280c3481d8b1510e16062ffaa631812c3ca53639329c1577354f0e4cd850dde1255c33de5e8c499e72ca1f49352847124c0dbfc30d0374d4d5d5e7cddb83c7ac93c806e738300b5357ecdc2e971d6438d34d8e4e17b99b758b1f9cac91c8e700000000000000000000000000000000000000000000000000000000000005c89" + } + }, + "storageProofs": [ + { + "key": "0x975227e2a924779fb36829b74e9ab66f8d906444c0efb23059aaf437a9254f64", + "leafIndex": 138, + "proof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000003a90c382f6158633dfaf5ea90b4b6aef05e0171d9c5e97a2f3aa41c3944e2d08f7c", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x0b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f840b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d", + "0x08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd", + "0x10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b", + "0x09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef", + "0x0b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c110b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c11", + "0x0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd", + "0x0d052b80abb809f9120c6b9884fffd52dd230a8dea0e503ee37a657412f956e4124085568263d79db22e8138cdfcddb82217762c26573f47a99464a1891998c0", + "0x05e61cac7ebd2c56b6e841e2437573d262652dab2a93cf5c87ae6c77ea6e29620b2e2ac6538353a780d865eb117c6a15c9ce5482df3f82de22341ff53ff603bb", + "0x081d406e2e7c445affbd6879217ba8ef422de57833bfd2117c67132b7c136b80041dc4f76e0dcec4e22f176ab6a40e8cfa6f15fd3be71dffc508c7d1e49a095f", + "0x08f74df1f6c448f34dbebc04442406cccf4e59336dbdeb8820d056584f8e5c2e000d8662808f22994b99a5a7c5888e053462f631bd6ccc1bb5cfc409c6496e29", + "0x01535de3a78232579c22be9a44bacd4ab197dcab60c15cad6ee5783a87e8fe3e035ca4181a3a2b7660a12b44b972a9b13751b7765a87b943690afae72084dc70", + "0x0dc279f3ab0113621f49cce7fcd58b620db8940fa536685b0f085062ef5804500f809df436769c9dca43efa53adf5d802e5d9a164cd2a43aec2dedf4109131b1", + "0x009a05037883da4556d1eb804b43c05fba7d961bbf77d48b06d4fd4b986159f6095ab3af585bcb3df9060b1651da2360891a221de0d9325c04a49d8caa0cd800", + "0x06cdca5c9cced457b657b1af944d068a8ab962ef5fe08550778921a429c5bb2f106bd517f2778b534d455f1d780e8d823d918499b35488788a81546c22a2b257", + "0x04c7934d9f58f8f85be28784af049898b132dc5e80f4e96d294dcfc883736c430c2a97661da9e1fcd930e97c6184379e9a8c99ba72bb3f941a542df11ca481d6", + "0x00000000000000000000000000000000000000000000000000000000000002b400000000000000000000000000000000000000000000000000000000000002a00324558eb3216bfae60f436ae4f80653125d6783123282af0eaa3766492ac1c012023ca7988684c6679a91abc62dbf0a5f49f4a4468e7c4c2e6de9bedce00864", + "0x000000000000000000000000000000000000000000000000000000000000004d0000000000000000000000000000000000000000000000000000000000000277034ca60d4657a94b25f98d458f8c879b4a67d24bb34650b9ade6cb0e0a4b6847043d8792aabcc5507963792b5efd5949aa034b1f784272b07eecfa5cc8b1b1d8" + ], + "value": "0x0000000000000000000000000000000000000000000000000000000000000183" + } + } + ] +} + "#; + + let proof = serde_json::from_str::(raw).unwrap(); + let account = verify::( + &new_mimc_constants_bls12_377(), + &proof.account_proof, + H256(hex!( + "0C76548458CC04A5AA09BFFA092B32C912AEE635C1C44364EBB911286A10263D" + )), + H160(hex!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")), + ) + .unwrap() + .unwrap(); + let value = verify::( + &new_mimc_constants_bls12_377(), + &proof.storage_proofs[0], + account.storage_root, + H256(hex!( + "975227e2a924779fb36829b74e9ab66f8d906444c0efb23059aaf437a9254f64" + )), + ) + .unwrap() + .unwrap(); + assert_eq!( + value, + hex!("0000000000000000000000000000000000000000000000000000000000000183").into() + ); + } + + #[test] + fn test_storage_noninclusion() { + let raw = r#" +{ + "accountProof": { + "key": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789", + "leafIndex": 65362, + "proof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000120fe0393507c456718a986386c7923fe68b87c29d83ac7f7ce1cdb49afc7e66a4771", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x09cbd26c486bc2217bce59337120283f655a7ba65075f98059249f471812d0480b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0c5c4d122720c4d6e7866d9b6bc6171c6259be90095976b665406ccf2dc6a8950305d7ebd7da4f82f061632eb7ec0c3060f51af848661d479bb64003f0fc5342", + "0x0c4762f6af9f09a529e70f0b34b7afafe2bba8944eccdcdb95cf13e0ff00ab2209d8b650f132967dba1764abe34c3d446311503ba7712d5f474a6e159b085b5f", + "0x0c3474a51e2654aca28b15add106ac676d92b9416ee788ac0b88873e77a009660176016fa85f1ba2375f784c72fae85763e12018e3e781c306f97ad9f826a22e", + "0x108459110262f154aef2d43fce77d314a7ec867f0068987716ff51582847e498009a6be6c408befa4eb7e6141fd427a2ce6489d50bf5f9de6bde9e100ada3482", + "0x118d3c53f9a3ea556029e867af93e9b4450cbacdf4dff29859e399ae16468e5102cefeff18d2980c8a9253c4609506472ba4764ea99efa6324dacf34740d9f05", + "0x005d88c799974510f99c04afdaba0f6b8f62edd55d8d89910009e148385a72c30a8fac91e2023660e8ac50ff082578361ba0901b16fe691f9b78044cbf6d1c4b", + "0x106f788c7d5990bec78f6c9cadd15604c99a8f1d56c875d324bb5ece63d83f3606694c69c43303aa1c614d60ed8fc66838f368b134cfc1ab00b6c83b2b5b3c8c", + "0x0ba8fdb8888982dde981f8e2cc9177c8c3ce0607661e113604e436951776de9c0b9ec8fec4b0696c73e04fd6bee4aa345633d23ef0c6bc4e4bbcf757af2677f0", + "0x0f5ae90881ea3398fd1a14fb83babc2335dfc4e6298aada1d827042d67dea48f0dee0a62e8ff86baddb091105d845c862089fe2f1963cd3798d636035da4d518", + "0x03d41bdb96726bf7f745784e42eef043c8b797f788d9720e36e460502e14c9fb0923e0e0228d2fe8619e30581e3e225d4e99e0daa011e15ac34c28fa30ea2989", + "0x11335bc4bf8a15d8c116cbdfe74242e80c7f60ac1a614d00f99fb9e1148126930502f7b7740708503e3858bc6df707cf4a1a751bcef3f2a5eb6eff9d8efa5cf8", + "0x0d60d90907794deaabe1e532a128e17ac94ec30339f3e367bc9ecd0aa40fd8b6009f71be21f99f29acd62b42787c99e5192646f808306fff0960ef5cd9a5ac16", + "0x0a6fd861ba25def420f5503fbbc4e0de2e54b4fbf0b22364e4a188eaf72ac58c02e49a2a28faca35409f471b4d981951aeabba2f091a427a2e88c53d1c7eeed3", + "0x0a93ecccd90368342584da9a8623e89a7a71d36f1da58d9874d50c045587138b0476d671e749bd2cd45fe416e1409caa22863f8cebdf926920a9f68b150d92d7", + "0x0821de61351452c22cf6bdafcd85be9a8cb3c2ad0af51f871d44221575785f9d12200803e31923cc68d6c9b906876643688e3a7ccb21264f933028b060564e4d", + "0x0000000000000000000000000000000000000000000000000000000000001c1e000000000000000000000000000000000000000000000000000000000000a354000036e661469dd70081ada16334d16a4049a124e261cd93def5fff88f85afda01b285fb7d6e0c7e05505a348777221f3c9fb491bbbbea4853e62e93f415efe7", + "0x000000000000000000000000000000000000000000000000000000000000894100000000000000000000000000000000000000000000000000000000000002db104a10331d6a854148a10b11c19cf2abae0412c9909ecefca54adc135ee57a950481fe75941093272afb1f8f76353afad6b89b1c19c383b07730c6f160b59243" + ], + "value": "0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000017d7498da306d2911280c3481d8b1510e16062ffaa631812c3ca53639329c1577354f0e4cd850dde1255c33de5e8c499e72ca1f49352847124c0dbfc30d0374d4d5d5e7cddb83c7ac93c806e738300b5357ecdc2e971d6438d34d8e4e17b99b758b1f9cac91c8e700000000000000000000000000000000000000000000000000000000000005c89" + } + }, + "storageProofs": [ + { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "leftLeafIndex": 611, + "leftProof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000003a90c382f6158633dfaf5ea90b4b6aef05e0171d9c5e97a2f3aa41c3944e2d08f7c", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x0b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f840b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d", + "0x08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd", + "0x10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b", + "0x09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef", + "0x0b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c110b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c11", + "0x0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd", + "0x0f6516c2cee4cfd3c3453717d360474888ae7a5e3fbe9434c0434650b44c39200b019e3375b0a9280488dc8154335e83fcceb92a3bfb90fe1188c6a5c2723683", + "0x0222fa2cc1728b6faff164e31b8b0c778f8b7c98046f625475e5d6ecd888e6e70cae760c7b8690d543c0558b3913c59e22748013099c0942e3960bb41d9078b8", + "0x0fdfeacb22084128e246e1408b7975e3de40182d76d2e3b13e73a455231b6690010cdcdd77f54be0e2bb237ca3acbf30ce20fc53d03e1d377fe07a2657c6a452", + "0x0eb38bc6c6d2dc3fc678880bad7bec0061cecaad838094521d352a0727944a5b049c14259c25252cb097d973dadae2e9645731f101d159115ce8dd6a6bc8d57c", + "0x0e40da89d95f318c0e1f985a6554ed305c5f27f7cae7de3e07f41151e5a311f70ea08c7b543f2257955fa4e937c498ce21ce3f2a5bab5d4245638922c6cce06b", + "0x04a3b8e7b06e29a06e335bf80be7a2908997f3294a9472be89cd95b1288e70c709cc26dc8c3b431c560c3847fde0ba114ac1bf58e3894738b7a35f72bf53a7b5", + "0x005f762408388dc791d8064731ec0a4e6a256c69737f331b53f54d55308c87df0c7945adb1ac77e84bf94603a7e00de2dfc3c44d64d2ef5e5be63ae079fc15cc", + "0x0254769f3f328564163e0be11c364aa2b4b651a975397c18c4608206a00998a00d3527a52738cd568ff1954312aeefda2bca64e4d91eb964fc3a0da5dd1c2b46", + "0x0565ad1253bbaa5388ddb68dd52adb83a99c90a7a01f7037011c03727a2e1d3f0ba2a9a0599fad5ffb5fde06fc3368457d786ad2eabeb372cea6ed6481868094", + "0x00000000000000000000000000000000000000000000000000000000000001ba00000000000000000000000000000000000000000000000000000000000002f1068da887b74fd30ff2e365193ddecf201afd9bd0181ffbc282939c943f0085d1120f168117038a271fcc94f9746eab5a6c682fa1efa41f23f6bacadedbc7b518", + "0x00000000000000000000000000000000000000000000000000000000000000a100000000000000000000000000000000000000000000000000000000000001ff0226bb24dc7fb5f8356e291c3ca45555a828a0e02bcb822a2878eadc51a11f1f066ef290cc3e13cdddabed678d6e64d13941dd6a0c6ed789f5774a99b90921f2" + ], + "value": "0x0000000000000000000000000000000000000000000000000000375911dbcbbc" + }, + "rightLeafIndex": 511, + "rightProof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000003a90c382f6158633dfaf5ea90b4b6aef05e0171d9c5e97a2f3aa41c3944e2d08f7c", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x0b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f840b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d0f3f9cf1e5ba6bdbb6daafc405bcceac97270fe89265b6a0faa2ba4bfd5cbf5d", + "0x08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd08b60393196453ee74fdf240449d9aa2569875b43596ea2621eecda8d8909acd", + "0x10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b10c439d656480d21a08c068717556fb8104a7a76e26f60e393ce4e36ae21e07b", + "0x09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef09ea86c5cd59ac4bfca4e46e7b50bb37c8327350888ba71112ecf3f5093baaef", + "0x0b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c110b971345bfa43e192ca2fb1c9ddd19f2dddf461243b1a54fdd5a4d581f850c11", + "0x0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd0edd0129edd35191a183ecd28cbcab2a48ad381215d8544acf35248639835dcd", + "0x0d052b80abb809f9120c6b9884fffd52dd230a8dea0e503ee37a657412f956e4124085568263d79db22e8138cdfcddb82217762c26573f47a99464a1891998c0", + "0x057a6e9128039b33095b2f3a29a0a4fd749c71bbdf024304b2979061ef696fda02609e7431e6a45621b7f5a2208d7b2fb036e64f1302666b157de4b082633069", + "0x037b1a185403907b8636d653feaa7b9ad7a5d84a510bda3c0a12bc9472a4e5720c08e5c3f0f216918297ea9b1d23483c6c013f440f0068b25302c6d49fbf446a", + "0x0d3421780fcce001fa7b4b27f5e39c220f05d0ffc491d8db6c6b5bcb4bcfb6a90298723fb96997c79f8bd6801f7d331234a09d6516ece5efba060a4ae6bcbf48", + "0x0c276aa23e1ae6fdc99c3cc16f5ca012b50c616fb0684853e3bdad0379bc05fd0e65e9874dec3ac06a76fb1975a6758236c27541abb8806cf7e461e39dcae2b9", + "0x0e7f129f0ed133acf079202b62b76e9c1f090c38369877039a41f389cfb28f04120861f2c2857a0ae1efbb7c5bbdf831a16e3648c3173bc35164ede64dfbf264", + "0x00132e22083f4c5cd6faf3ed72a15db28e32d9ca043a0af318c4761b9fff9b8506f131ac26d2cba71ff6f3a4690468563bb280e426224b29627006c92b30a4a1", + "0x07ef4499f02012217d7ba74661d161165ba8ce341eb8feedc6fb91c9d3daf6ea022140824283e42f43fc52f0fa23057ce4869a8c5ca5a263b08257011df91fbc", + "0x10d1e689f780d25322ea063b70d009e4ecf8d2e24044e61849b526e5c92045780f386aa614b62d8f207b4aa98af1f28d4ed491ac4e371aa51aa2bb6bb7b9d2b9", + "0x000000000000000000000000000000000000000000000000000000000000032c000000000000000000000000000000000000000000000000000000000000019d128c9ea07e20e3771e0d5c074ab0d250cf02502f2fcf253c3e627819053d8062063bfbb79af7fd4d7834a275cad3d9ccef2d5ade138040201e4b0533f5360ad6", + "0x000000000000000000000000000000000000000000000000000000000000026300000000000000000000000000000000000000000000000000000000000001840233297165af3cab341e7e30b38dc8bf19d538e0ab6c6a842b2acbf536027b150e43b52047962596b0ecab1ed42e1774bd419bd21323899e4afd25bb6635bd52" + ], + "value": "0x000000000000000000000000000000000000000000000000000000000000007d" + } + } + ] +} + "#; + + let proof = serde_json::from_str::(raw).unwrap(); + let account = verify::( + &new_mimc_constants_bls12_377(), + &proof.account_proof, + H256(hex!( + "0C76548458CC04A5AA09BFFA092B32C912AEE635C1C44364EBB911286A10263D" + )), + H160(hex!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789")), + ) + .unwrap() + .unwrap(); + let value = verify::( + &new_mimc_constants_bls12_377(), + &proof.storage_proofs[0], + account.storage_root, + H256::default(), + ) + .unwrap(); + assert_eq!(value, None); + } + + #[test] + fn test_account_noninclusion() { + let raw = r#" +{ + "accountProof": { + "key": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2780", + "leftLeafIndex": 50915, + "leftProof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000120fe0393507c456718a986386c7923fe68b87c29d83ac7f7ce1cdb49afc7e66a4771", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x09cbd26c486bc2217bce59337120283f655a7ba65075f98059249f471812d0480b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0c5c4d122720c4d6e7866d9b6bc6171c6259be90095976b665406ccf2dc6a8950305d7ebd7da4f82f061632eb7ec0c3060f51af848661d479bb64003f0fc5342", + "0x0c4762f6af9f09a529e70f0b34b7afafe2bba8944eccdcdb95cf13e0ff00ab2209d8b650f132967dba1764abe34c3d446311503ba7712d5f474a6e159b085b5f", + "0x125e0b74c9f944431f57100ababee80095eff47b54bcbeb59790ccab72af8a8d0ad6bd567eb864b9e5ce168bf3a7c87ee3963bc68577cb40d2c5118e69b02449", + "0x0ced56af58b33a68e2b2491f0e45eb9aeccf95cdee3692dcc6d22055ffc07c67034599ba21f7c7c190d1a3df45d61faf51eba99d40671bafb8a9f0987c1379a0", + "0x0cf419b794abebf2631eb9b71850e4720d5832926808fb04811b468cc34059e7023a96fe63cb2a1237ab59f91c368dba466d9670461b9f6177099aaaaab77479", + "0x0651d5368092407b9dcd8f50f62c8e2d7a3ceac68c83bfb4c1c6a72ddf919519126f610f1b0dcc4004d4e591f227faff14211aea80ff74e65e197c8c276f3dd3", + "0x0349cf762365c0272e1bbcdc42e189fb98f0bb81949c8f74af78d54734b393dd0898977dd60d5255f6d179d9c118a9715d9b8f179a76c92cfa4b50583086d174", + "0x04ad89f67466c52ab58ce2b607f1b8444feec75c301f43f0c9a20b792059e48f02d966dd372e7021d97187b428f14de7d8f9b016c6082ed9c032f504b934de7c", + "0x0402473cd6b64ef02e23ffde16a97698dd9f6e79c3922bb06169504cb13d259302e3e785b26b76f90b834588317d7efe81c21f8f6e127fc341e13f9173d2d33e", + "0x0bfaf269c21ddbe687cf0a56120f9da606cff565c418ca081d9523cefa5ee4900d219e305070c0a5ffda3b2d20799b3876e4f899f21e790c5e61607e0a33cda1", + "0x08eb97abb2df2a1affcd757cdd20fd0777e53470a38818e8febdae4236744efd0c3bee7aa429be1836b93f6871cff32c80888251602cb6782d7efbcfa96187f7", + "0x0d147eadaed99af1df14632babd90117d3b6927af8f22902ffec8c7e0c81ac7310ee5b6b5b5b359bdb76271dcbf28e006f4c93c024175751fd3628dd1e6f1be9", + "0x1130fa8251e74fc2eb80e836e988bdcd156d4bca6b3a8c118ed5e17ac2b926220883f050c04011d5437f4bf78454f01e24974a3acde10dab94ba8a7ee09c5cbd", + "0x0043381a4fb2920cae78e46cd412a078e4905f2ae436b3233aa162b15b3a6f2c12252dfbb99f31c4713a63abff2141b357f20eef57cbdb1c7687c1d3f64878c6", + "0x0a064a4646cb7f9fd9b3dcecb1fd8cbd1f2b859d97b56486ea002d285477346d03569d58aa002050c477fb037ca9564212f71476982ea550693529862d31579f", + "0x0000000000000000000000000000000000000000000000000000000000005d44000000000000000000000000000000000000000000000000000000000000ce6212214e893b51fb4bff3f919f49f5e3d98f9e90a0744098760f05ea5c0ad37e0b02ada81e47c1223c7d15ba45be0b16ee69bd6a5e2c26de3f2facb725db2a2563", + "0x00000000000000000000000000000000000000000000000000000000000088190000000000000000000000000000000000000000000000000000000000008cf50213dedcc29d8a1353bafac93de02055eb9fa839071f1a6257c8c1ed608ec01010a5194238ff342de60302f1918116fe22fd005173786d19a588d90a7bc82278" + ], + "value": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000007977874126658098c066972282d4c85f230520af3847e297fe7524f976873e50134373b65f439c874734ff51ea349327c140cde2e47a933146e6f9f2ad8eb17c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000" + }, + "rightLeafIndex": 36085, + "rightProof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000120fe0393507c456718a986386c7923fe68b87c29d83ac7f7ce1cdb49afc7e66a4771", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x09cbd26c486bc2217bce59337120283f655a7ba65075f98059249f471812d0480b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0c5c4d122720c4d6e7866d9b6bc6171c6259be90095976b665406ccf2dc6a8950305d7ebd7da4f82f061632eb7ec0c3060f51af848661d479bb64003f0fc5342", + "0x0bd67fb37adfe69856936db64ba99e14abd6a73124332d622937c0e27697c6ce023c0ebe791b9fedd67c5d6bcb60b7e8f1b34a8bc3212626520e7ef92de490d2", + "0x115b79d9b04cd131a492cba8892fe3768ffc4d7e6ba4883648bb735cdc98231a11db4ddd2d019276c2bf4b5a63b25d69e43ade93e229401fe636c8d2b0abfcd2", + "0x036a0c38dc37c9dd30b3dd485a7a61e9c55cc6db0ab664122d93f566b40b642f0f71a7ca8aea7890f183d07c6bba517ac6132f7bb188f07ec32ca33cda9ddb9d", + "0x126841c8020009187e5b49a59787a2a297227db1e11b5a78a1cace93cc4ce09c116ee798f5d5acf0b5df94de55ecb6ca1d638228746d2cdeed3c5c187fc3bd4e", + "0x05d430417e93bbd414de5f2c529407b97eb843cc083d8105a2a6ec0868abc27004f133f13f993f98f15c3cf3e1eff101113834c763234bc8e1896ec3861063b2", + "0x10530f986ec1a17af8fef6d2fbbb901accfd9d60828c6c0f6a6da1e926f948790f05a301b210f45406769f0ac2c45748da6dccce762d4adcc5dc85de4990f2c1", + "0x0284a0134f5ac9cd8acfa1c79ca9f035f9728e5de39373e9ddca53625bd1ed280aa5ae2f615ab38828700102da3413de7a96d8878f5f097d09063fe6e7f0a12b", + "0x0f7e46da1c787eb19b7788a82848f478e82bc2ac3575f90426d75c72f479c7ec1268704b94d6bd2a1a6d8ffbf27f433a36aca5a5fa661c6187e2e4b8cbc848a9", + "0x06c9e4b29458dde1213ff22ed7452f39b8c70b7ef314c5a7949838837a48ea4808291f2d7f0133f66b8b23b06e8a5f6c4a292cea5bb4dcf4081c49cabd7d6c66", + "0x10ab620750e513b9bc4a14270f8e7ea8e3e7d2187bbf37af175ae65a69b2b3a80584348d8fe95db0d10ec360e4d5e09f789f3021fc250e9e24977413fb500333", + "0x0c185bc72eb982991d63be5c18a36298fccea38d8ad1363e54e01ae41ef6885909a48e907f9e6574b0d2f29a8e35ef2bccfca4316a03b666539b6f795b8ec08f", + "0x0a42d55940760dbd55a4bf2e987f202b63291dc230115362fae168c00027b6ec0b838ec78059b519c8d17d9b4748fef69886684d209d3583e29261e6f9d7a7b7", + "0x0f909e7857d476f194af529bcc51ef515960148352cae9b4e2a4292bd207bcf01218b51c9c9be2c5f66829c972eeae0d473d16770955718d50add1ed7ac1d4f2", + "0x127c1c643468d9bc6fed375cfce594c169c5855b2bc19bd70818ee4d1e4ac6d812831b628352e7c7064f6d3440f9e2d03fa3336702edba27aea748ddff0478f9", + "0x0000000000000000000000000000000000000000000000000000000000008de60000000000000000000000000000000000000000000000000000000000004ff211d6fa740eb934a32b9e49dd7f2f655eb5c8a8014b36ad55b0cd66bae1b7b3a810a5194238ff342de60302f1918116fe22fd005173786d19a588d90a7bc82278", + "0x000000000000000000000000000000000000000000000000000000000000c6e3000000000000000000000000000000000000000000000000000000000000cf420213ef7ebf37eca24c05838ef0597f9aac9e9ba9dd49e75012ab573c8b8c3f3910a5194238ff342de60302f1918116fe22fd005173786d19a588d90a7bc82278" + ], + "value": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000007977874126658098c066972282d4c85f230520af3847e297fe7524f976873e50134373b65f439c874734ff51ea349327c140cde2e47a933146e6f9f2ad8eb17c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000000" + } + }, + "storageProofs": [] +} + "#; + + let proof = serde_json::from_str::(raw).unwrap(); + let account = verify::( + &new_mimc_constants_bls12_377(), + &proof.account_proof, + H256(hex!( + "0C76548458CC04A5AA09BFFA092B32C912AEE635C1C44364EBB911286A10263D" + )), + H160(hex!("5ff137d4b0fdcd49dca30c7cf57e578a026d2780")), + ) + .unwrap(); + assert_eq!(account, None); + } + + // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/core/src/test/java/net/consensys/shomei/ZkAccountTest.java#L34 + #[test] + fn test_hash_zeroaccount() { + let mimc_constants = new_mimc_constants_bls12_377(); + let mimc = new_mimc_bls12_377(&mimc_constants); + let hash = mimc + .update(&ZkAccount::default().into_bytes()) + .unwrap() + .finalize(); + assert_eq!( + hash, + hex!("0f170eaef9275fd6098a06790c63a141e206e0520738a4cf5cf5081d495e8682") + ); + } + + #[test] + fn test_hashed_key() { + let mimc_constants = new_mimc_constants_bls12_377(); + let mimc = new_mimc_bls12_377(&mimc_constants); + let hash = mimc + .update(hex!( + "0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d2789" + )) + .unwrap() + .finalize(); + assert_eq!( + H256(hex!( + "104a10331d6a854148a10b11c19cf2abae0412c9909ecefca54adc135ee57a95" + )), + hash.try_into().unwrap(), + ); + } +} diff --git a/lib/scroll-verifier/src/verify.rs b/lib/scroll-verifier/src/verify.rs index 57acce8ba4..f61a3f8ff4 100644 --- a/lib/scroll-verifier/src/verify.rs +++ b/lib/scroll-verifier/src/verify.rs @@ -212,7 +212,7 @@ mod tests { let proof: Proof = serde_json::from_str(&std::fs::read_to_string("tests/scroll_proof.json").unwrap()) .unwrap(); - assert!(matches!( + assert_eq!( verify_zktrie_storage_proof( H256(hex!( "1b52888cae05bdba27f8470293a7d2bc3b9a9c822d96affe05ef243e0dfd44a0" @@ -222,7 +222,7 @@ mod tests { &proof.proof ), Ok(()) - )) + ) } #[test] @@ -230,7 +230,7 @@ mod tests { let proof: Proof = serde_json::from_str(&std::fs::read_to_string("tests/scroll_absent.json").unwrap()) .unwrap(); - assert!(matches!( + assert_eq!( verify_zktrie_storage_absence( H256(hex!( "1b52888cae05bdba27f8470293a7d2bc3b9a9c822d96affe05ef243e0dfd44a0" @@ -239,6 +239,6 @@ mod tests { &proof.proof ), Ok(()) - )) + ) } } diff --git a/lib/unionlabs/src/ibc/lightclients.rs b/lib/unionlabs/src/ibc/lightclients.rs index 5efa8408b1..b68f73f3ab 100644 --- a/lib/unionlabs/src/ibc/lightclients.rs +++ b/lib/unionlabs/src/ibc/lightclients.rs @@ -1,6 +1,7 @@ pub mod arbitrum; pub mod cometbls; pub mod ethereum; +pub mod linea; pub mod scroll; pub mod tendermint; pub mod wasm; diff --git a/lib/unionlabs/src/ibc/lightclients/linea.rs b/lib/unionlabs/src/ibc/lightclients/linea.rs new file mode 100644 index 0000000000..1e80278bf9 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/linea.rs @@ -0,0 +1,3 @@ +pub mod client_state; +pub mod consensus_state; +pub mod header; diff --git a/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs b/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs new file mode 100644 index 0000000000..69bc6df877 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs @@ -0,0 +1,120 @@ +use core::{fmt::Debug, str::FromStr}; + +use macros::model; +use uint::FromDecStrErr; + +use crate::{ + errors::{required, InvalidLength, MissingField}, + hash::H160, + ibc::core::client::height::Height, + uint::U256, +}; + +#[model(proto( + raw(protos::union::ibc::lightclients::linea::v1::ClientState), + into, + from +))] +pub struct ClientState { + pub chain_id: U256, + // TODO: This should be ClientId + pub l1_client_id: String, + pub l1_latest_height: Height, + pub l1_rollup_contract_address: H160, + pub l1_rollup_current_l2_block_number_slot: U256, + pub l1_rollup_current_l2_timestamp_slot: U256, + pub l1_rollup_l2_state_root_hashes_slot: U256, + pub l2_ibc_contract_address: H160, + pub l2_ibc_contract_commitment_slot: U256, + pub frozen_height: Height, +} + +impl From for protos::union::ibc::lightclients::linea::v1::ClientState { + fn from(value: ClientState) -> Self { + Self { + chain_id: value.chain_id.to_string(), + l1_client_id: value.l1_client_id, + l1_latest_height: Some(value.l1_latest_height.into()), + l1_rollup_contract_address: value.l1_rollup_contract_address.into(), + l1_rollup_current_l2_block_number_slot: value + .l1_rollup_current_l2_block_number_slot + .to_be_bytes() + .to_vec(), + l1_rollup_current_l2_timestamp_slot: value + .l1_rollup_current_l2_timestamp_slot + .to_be_bytes() + .to_vec(), + l1_rollup_l2_state_root_hashes_slot: value + .l1_rollup_l2_state_root_hashes_slot + .to_be_bytes() + .to_vec(), + l2_ibc_contract_address: value.l2_ibc_contract_address.into(), + l2_ibc_contract_commitment_slot: value + .l2_ibc_contract_commitment_slot + .to_be_bytes() + .into(), + frozen_height: Some(value.frozen_height.into()), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromClientStateError { + #[error("unable to parse chain id")] + ChainId(#[source] FromDecStrErr), + #[error(transparent)] + MissingField(MissingField), + #[error("invalid l1 latest height")] + L1LatestHeight, + #[error("invalid rollup contract address")] + L1RollupContractAddress(#[source] InvalidLength), + #[error("invalid rollup current_l2_block_number slot")] + L1RollupCurrentL2BlockNumberSlot(#[source] InvalidLength), + #[error("invalid rollup current_l2_timestamp slot")] + L1RollupCurrentL2TimestampSlot(#[source] InvalidLength), + #[error("invalid rollup l2_state_roots mapping slot")] + L1RollupL2StateRootHashesSlot(#[source] InvalidLength), + #[error("invalid l2 ibc contract address")] + L2IbcContractAddress(#[source] InvalidLength), + #[error("invalid l2 ibc commitment slot")] + L2IbcContractCommitmentSlot(#[source] InvalidLength), +} + +impl TryFrom for ClientState { + type Error = TryFromClientStateError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::ClientState, + ) -> Result { + Ok(Self { + l1_client_id: value.l1_client_id, + chain_id: U256::from_str(&value.chain_id).map_err(TryFromClientStateError::ChainId)?, + l1_latest_height: required!(value.l1_latest_height)?.into(), + l1_rollup_contract_address: value + .l1_rollup_contract_address + .try_into() + .map_err(TryFromClientStateError::L1RollupContractAddress)?, + l1_rollup_current_l2_block_number_slot: U256::try_from_be_bytes( + &value.l1_rollup_current_l2_block_number_slot, + ) + .map_err(TryFromClientStateError::L1RollupCurrentL2BlockNumberSlot)?, + l1_rollup_current_l2_timestamp_slot: U256::try_from_be_bytes( + &value.l1_rollup_current_l2_timestamp_slot, + ) + .map_err(TryFromClientStateError::L1RollupCurrentL2TimestampSlot)?, + l1_rollup_l2_state_root_hashes_slot: U256::try_from_be_bytes( + &value.l1_rollup_l2_state_root_hashes_slot, + ) + .map_err(TryFromClientStateError::L1RollupL2StateRootHashesSlot)?, + l2_ibc_contract_address: value + .l2_ibc_contract_address + .try_into() + .map_err(TryFromClientStateError::L2IbcContractAddress)?, + l2_ibc_contract_commitment_slot: U256::try_from_be_bytes( + &value.l2_ibc_contract_commitment_slot, + ) + .map_err(TryFromClientStateError::L2IbcContractCommitmentSlot)?, + frozen_height: value.frozen_height.unwrap_or_default().into(), + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/linea/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/linea/consensus_state.rs new file mode 100644 index 0000000000..864f6c12e3 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/linea/consensus_state.rs @@ -0,0 +1,44 @@ +use macros::model; + +use crate::{errors::InvalidLength, hash::H256}; + +#[model(proto( + raw(protos::union::ibc::lightclients::linea::v1::ConsensusState), + into, + from +))] +pub struct ConsensusState { + pub ibc_storage_root: H256, + pub timestamp: u64, +} + +impl From for protos::union::ibc::lightclients::linea::v1::ConsensusState { + fn from(value: ConsensusState) -> Self { + Self { + ibc_storage_root: value.ibc_storage_root.into(), + timestamp: value.timestamp, + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromConsensusStateError { + #[error("invalid ibc storage root")] + IbcStorageRoot(#[source] InvalidLength), +} + +impl TryFrom for ConsensusState { + type Error = TryFromConsensusStateError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::ConsensusState, + ) -> Result { + Ok(Self { + ibc_storage_root: value + .ibc_storage_root + .try_into() + .map_err(TryFromConsensusStateError::IbcStorageRoot)?, + timestamp: value.timestamp, + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/linea/header.rs b/lib/unionlabs/src/ibc/lightclients/linea/header.rs new file mode 100644 index 0000000000..eef401f872 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/linea/header.rs @@ -0,0 +1,89 @@ +use macros::model; + +use crate::{ + errors::{required, InvalidLength, MissingField}, + hash::H256, + ibc::{ + core::client::height::Height, + lightclients::ethereum::{ + account_proof::{AccountProof, TryFromAccountProofError}, + storage_proof::{StorageProof, TryFromStorageProofError}, + }, + }, + linea::proof::{MerkleProof, TryFromMerkleProofError}, +}; + +#[model(proto(raw(protos::union::ibc::lightclients::linea::v1::Header), into, from))] +pub struct Header { + pub l1_height: Height, + pub l1_rollup_contract_proof: AccountProof, + pub l2_block_number: u64, + pub l2_block_number_proof: StorageProof, + pub l2_state_root: H256, + pub l2_state_root_proof: StorageProof, + pub l2_timestamp: u64, + pub l2_timestamp_proof: StorageProof, + pub l2_ibc_contract_proof: MerkleProof, +} + +impl From
for protos::union::ibc::lightclients::linea::v1::Header { + fn from(value: Header) -> Self { + Self { + l1_height: Some(value.l1_height.into()), + l1_rollup_contract_proof: Some(value.l1_rollup_contract_proof.into()), + l2_block_number: value.l2_block_number, + l2_block_number_proof: Some(value.l2_block_number_proof.into()), + l2_state_root: value.l2_state_root.into(), + l2_state_root_proof: Some(value.l2_state_root_proof.into()), + l2_timestamp: value.l2_timestamp, + l2_timestamp_proof: Some(value.l2_timestamp_proof.into()), + l2_ibc_contract_proof: Some(value.l2_ibc_contract_proof.into()), + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TryFromHeaderError { + MissingField(MissingField), + L1RollupContractProof(TryFromAccountProofError), + L2BlockNumber(InvalidLength), + L2BlockNumberProof(TryFromStorageProofError), + L2StateRoot(InvalidLength), + L2StateRootProof(TryFromStorageProofError), + L2Timestamp(InvalidLength), + L2TimestampProof(TryFromStorageProofError), + L2IbcContractProof(TryFromMerkleProofError), +} + +impl TryFrom for Header { + type Error = TryFromHeaderError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::Header, + ) -> Result { + Ok(Self { + l1_height: required!(value.l1_height)?.into(), + l1_rollup_contract_proof: required!(value.l1_rollup_contract_proof)? + .try_into() + .map_err(TryFromHeaderError::L1RollupContractProof)?, + l2_block_number: value.l2_block_number, + l2_block_number_proof: required!(value.l2_block_number_proof)? + .try_into() + .map_err(TryFromHeaderError::L2BlockNumberProof)?, + l2_state_root: value + .l2_state_root + .try_into() + .map_err(TryFromHeaderError::L2StateRoot)?, + l2_state_root_proof: required!(value.l2_state_root_proof)? + .try_into() + .map_err(TryFromHeaderError::L2StateRootProof)?, + l2_timestamp: value.l2_timestamp, + l2_timestamp_proof: required!(value.l2_timestamp_proof)? + .try_into() + .map_err(TryFromHeaderError::L2TimestampProof)?, + l2_ibc_contract_proof: required!(value.l2_ibc_contract_proof)? + .try_into() + .map_err(TryFromHeaderError::L2IbcContractProof)?, + }) + } +} diff --git a/lib/unionlabs/src/lib.rs b/lib/unionlabs/src/lib.rs index f58fdea16b..a2215a976d 100644 --- a/lib/unionlabs/src/lib.rs +++ b/lib/unionlabs/src/lib.rs @@ -53,6 +53,9 @@ pub mod union; /// Types specific to the scroll protocol. pub mod scroll; +/// Types specific to the linea protocol. +pub mod linea; + /// Wrapper types around [`milagro_bls`] types, providing more conversions and a simpler signing interface. pub mod bls; @@ -142,6 +145,7 @@ pub enum WasmClientType { Tendermint, Scroll, Arbitrum, + Linea, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -174,6 +178,7 @@ impl FromStr for WasmClientType { "Tendermint" => Ok(WasmClientType::Tendermint), "Scroll" => Ok(WasmClientType::Scroll), "Arbitrum" => Ok(WasmClientType::Arbitrum), + "Linea" => Ok(WasmClientType::Linea), _ => Err(WasmClientTypeParseError::UnknownType(s.to_string())), } } @@ -188,6 +193,7 @@ impl Display for WasmClientType { Self::Tendermint => write!(f, "Tendermint"), Self::Scroll => write!(f, "Scroll"), Self::Arbitrum => write!(f, "Arbitrum"), + Self::Linea => write!(f, "Linea"), } } } diff --git a/lib/unionlabs/src/linea.rs b/lib/unionlabs/src/linea.rs new file mode 100644 index 0000000000..3f90d3e748 --- /dev/null +++ b/lib/unionlabs/src/linea.rs @@ -0,0 +1,2 @@ +pub mod account; +pub mod proof; diff --git a/lib/unionlabs/src/linea/account.rs b/lib/unionlabs/src/linea/account.rs new file mode 100644 index 0000000000..4bacacbb79 --- /dev/null +++ b/lib/unionlabs/src/linea/account.rs @@ -0,0 +1,84 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::{ExpectedLength, InvalidLength}, + hash::H256, + uint::U256, + ByteArrayExt, +}; + +pub const ZKACCOUNT_BYTES_LEN: usize = 32 * 6; + +// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/core/src/main/java/net/consensys/shomei/ZkAccount.java +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct ZkAccount { + pub nonce: U256, + pub balance: U256, + pub storage_root: H256, + pub mimc_code_hash: H256, + pub keccak_code_hash: MimcSafeBytes, + pub code_size: U256, +} + +impl ZkAccount { + pub fn into_bytes(self) -> Vec { + self.into() + } +} + +impl Into> for ZkAccount { + fn into(self) -> Vec { + [ + self.nonce.to_be_bytes().as_ref(), + self.balance.to_be_bytes().as_ref(), + &self.storage_root.into_bytes(), + &self.mimc_code_hash.into_bytes(), + &self.keccak_code_hash.into_bytes(), + self.code_size.to_be_bytes().as_ref(), + ] + .concat() + } +} + +impl TryFrom<&[u8]> for ZkAccount { + type Error = InvalidLength; + fn try_from(value: &[u8]) -> Result { + let value = <[u8; ZKACCOUNT_BYTES_LEN]>::try_from(value).map_err(|_| InvalidLength { + expected: ExpectedLength::Exact(ZKACCOUNT_BYTES_LEN), + found: value.len(), + })?; + Ok(ZkAccount { + nonce: U256::from_be_bytes(value.array_slice::<0, 32>()), + balance: U256::from_be_bytes(value.array_slice::<32, 32>()), + storage_root: value.array_slice::<64, 32>().into(), + mimc_code_hash: value.array_slice::<96, 32>().into(), + keccak_code_hash: value.array_slice::<128, 32>().into(), + code_size: U256::from_be_bytes(value.array_slice::<160, 32>()), + }) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct MimcSafeBytes { + pub lsb: H256, + pub msb: H256, +} + +impl MimcSafeBytes { + pub fn into_bytes(self) -> Vec { + [self.lsb.0, self.msb.0].concat() + } +} + +impl From<[u8; 32]> for MimcSafeBytes { + fn from(value: [u8; 32]) -> Self { + let mut lsb = [0u8; 32]; + let mut msb = [0u8; 32]; + lsb[16..32].copy_from_slice(&value.array_slice::<16, 16>()); + msb[16..32].copy_from_slice(&value.array_slice::<0, 16>()); + Self { + lsb: lsb.into(), + msb: msb.into(), + } + } +} diff --git a/lib/unionlabs/src/linea/proof.rs b/lib/unionlabs/src/linea/proof.rs new file mode 100644 index 0000000000..927ff635d4 --- /dev/null +++ b/lib/unionlabs/src/linea/proof.rs @@ -0,0 +1,175 @@ +use macros::model; +use serde::{Deserialize, Serialize}; + +use crate::errors::{required, MissingField}; + +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +pub enum TryFromMerkleProofError { + #[error(transparent)] + MissingField(#[from] MissingField), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MerklePath { + #[serde(with = "::serde_utils::hex_string")] + pub value: Vec, + #[serde(with = "::serde_utils::hex_string_list")] + pub proof_related_nodes: Vec>, +} + +impl From for protos::union::ibc::lightclients::linea::v1::MerklePath { + fn from(value: MerklePath) -> Self { + Self { + value: value.value, + proof_related_nodes: value.proof_related_nodes, + } + } +} + +impl From for MerklePath { + fn from(value: protos::union::ibc::lightclients::linea::v1::MerklePath) -> Self { + Self { + value: value.value, + proof_related_nodes: value.proof_related_nodes, + } + } +} + +#[model( + proto( + raw(protos::union::ibc::lightclients::linea::v1::InclusionProof), + into, + from + ), + no_serde +)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InclusionProof { + #[serde(with = "::serde_utils::hex_string")] + pub key: Vec, + pub leaf_index: u64, + pub proof: MerklePath, +} + +impl From for protos::union::ibc::lightclients::linea::v1::InclusionProof { + fn from(value: InclusionProof) -> Self { + Self { + key: value.key, + leaf_index: value.leaf_index, + merkle_path: Some(value.proof.into()), + } + } +} + +impl TryFrom for InclusionProof { + type Error = TryFromMerkleProofError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::InclusionProof, + ) -> Result { + Ok(Self { + key: value.key, + leaf_index: value.leaf_index, + proof: required!(value.merkle_path)?.into(), + }) + } +} + +#[model( + proto( + raw(protos::union::ibc::lightclients::linea::v1::NonInclusionProof), + into, + from + ), + no_serde +)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NonInclusionProof { + #[serde(with = "::serde_utils::hex_string")] + pub key: Vec, + pub left_leaf_index: u64, + pub left_proof: MerklePath, + pub right_leaf_index: u64, + pub right_proof: MerklePath, +} + +impl From for protos::union::ibc::lightclients::linea::v1::NonInclusionProof { + fn from(value: NonInclusionProof) -> Self { + Self { + key: value.key, + left_leaf_index: value.left_leaf_index, + left_proof: Some(value.left_proof.into()), + right_leaf_index: value.right_leaf_index, + right_proof: Some(value.right_proof.into()), + } + } +} + +impl TryFrom for NonInclusionProof { + type Error = TryFromMerkleProofError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::NonInclusionProof, + ) -> Result { + Ok(Self { + key: value.key, + left_leaf_index: value.left_leaf_index, + left_proof: required!(value.left_proof)?.into(), + right_leaf_index: value.right_leaf_index, + right_proof: required!(value.right_proof)?.into(), + }) + } +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum MerkleProof { + Inclusion(InclusionProof), + NonInclusion(NonInclusionProof), +} + +impl From for protos::union::ibc::lightclients::linea::v1::MerkleProof { + fn from(value: MerkleProof) -> Self { + Self { + proof: Some(match value { + MerkleProof::Inclusion(inclusion) => { + protos::union::ibc::lightclients::linea::v1::merkle_proof::Proof::Inclusion( + inclusion.into(), + ) + } + MerkleProof::NonInclusion(noninclusion) => { + protos::union::ibc::lightclients::linea::v1::merkle_proof::Proof::Noninclusion( + noninclusion.into(), + ) + } + }), + } + } +} + +impl TryFrom for MerkleProof { + type Error = TryFromMerkleProofError; + + fn try_from( + value: protos::union::ibc::lightclients::linea::v1::MerkleProof, + ) -> Result { + Ok(match required!(value.proof)? { + protos::union::ibc::lightclients::linea::v1::merkle_proof::Proof::Inclusion( + inclusion, + ) => MerkleProof::Inclusion(inclusion.try_into()?), + protos::union::ibc::lightclients::linea::v1::merkle_proof::Proof::Noninclusion( + noninclusion, + ) => MerkleProof::NonInclusion(noninclusion.try_into()?), + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetProof { + pub account_proof: MerkleProof, + pub storage_proofs: Vec, +} diff --git a/light-clients/linea-light-client/Cargo.toml b/light-clients/linea-light-client/Cargo.toml new file mode 100644 index 0000000000..561d350ec9 --- /dev/null +++ b/light-clients/linea-light-client/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors = ["Union Labs"] +edition = "2021" +license-file = { workspace = true } +name = "linea-light-client" +publish = false +version = "0.1.0" + +[lints] +workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +cosmwasm-std = { workspace = true, features = ["abort"] } +ethereum-verifier = { workspace = true } +ethers-core.workspace = true +gnark-mimc = { workspace = true } +hex = { workspace = true } +ics008-wasm-client = { workspace = true } +linea-verifier = { workspace = true } +linea-zktrie = { workspace = true } +protos = { workspace = true } +rlp = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde-json-wasm = { workspace = true } +sha3 = { workspace = true } +thiserror = { workspace = true } +tiny-keccak = { workspace = true, features = ["keccak"] } +unionlabs = { workspace = true, features = ["ethabi", "stargate"] } + +[dev-dependencies] +base64 = { workspace = true } +hex = { workspace = true } +serde_json = { workspace = true } diff --git a/light-clients/linea-light-client/linea-light-client.nix b/light-clients/linea-light-client/linea-light-client.nix new file mode 100644 index 0000000000..0d21e7afd5 --- /dev/null +++ b/light-clients/linea-light-client/linea-light-client.nix @@ -0,0 +1,19 @@ +{ ... }: { + perSystem = { crane, lib, ensure-wasm-client-type, ... }: + let + workspace = (crane.buildWasmContract { + crateDirFromRoot = "light-clients/linea-light-client"; + checks = [ + (file_path: '' + ${ensure-wasm-client-type { + inherit file_path; + type = "Linea"; + }} + '') + ]; + }); + in + { + inherit (workspace) packages checks; + }; +} diff --git a/light-clients/linea-light-client/src/client.rs b/light-clients/linea-light-client/src/client.rs new file mode 100644 index 0000000000..3fac900f79 --- /dev/null +++ b/light-clients/linea-light-client/src/client.rs @@ -0,0 +1,350 @@ +use cosmwasm_std::{Deps, DepsMut, Env}; +use gnark_mimc::new_mimc_constants_bls12_377; +use ics008_wasm_client::{ + storage_utils::{ + read_client_state, read_consensus_state, save_client_state, save_consensus_state, + update_client_state, + }, + IbcClient, IbcClientError, Status, StorageState, +}; +use sha3::Digest; +use unionlabs::{ + cosmwasm::wasm::union::custom_query::{query_consensus_state, UnionCustomQuery}, + encoding::{Decode, EncodeAs, EthAbi, Proto}, + google::protobuf::any::Any, + hash::H256, + ibc::{ + core::{ + client::{genesis_metadata::GenesisMetadata, height::Height}, + commitment::merkle_path::MerklePath, + }, + lightclients::{ + cometbls, + linea::{client_state::ClientState, consensus_state::ConsensusState, header::Header}, + wasm, + }, + }, + ics24::Path, + linea::{ + account::ZkAccount, + proof::{InclusionProof, NonInclusionProof}, + }, + uint::U256, +}; + +use crate::{errors::Error, eth_encoding::generate_commitment_key}; + +type WasmClientState = unionlabs::ibc::lightclients::wasm::client_state::ClientState; +type WasmConsensusState = + unionlabs::ibc::lightclients::wasm::consensus_state::ConsensusState; +type WasmL1ConsensusState = unionlabs::ibc::lightclients::wasm::consensus_state::ConsensusState< + unionlabs::ibc::lightclients::ethereum::consensus_state::ConsensusState, +>; + +pub struct LineaLightClient; + +impl IbcClient for LineaLightClient { + type Error = Error; + + type CustomQuery = UnionCustomQuery; + + type Header = Header; + + type Misbehaviour = Header; + + type ClientState = ClientState; + + type ConsensusState = ConsensusState; + + type Encoding = Proto; + + fn verify_membership( + deps: Deps, + height: Height, + _delay_time_period: u64, + _delay_block_period: u64, + proof: Vec, + mut path: MerklePath, + value: ics008_wasm_client::StorageState, + ) -> Result<(), IbcClientError> { + let consensus_state: WasmConsensusState = + read_consensus_state(deps, &height)?.ok_or(Error::ConsensusStateNotFound(height))?; + let client_state: WasmClientState = read_client_state(deps)?; + + let path = path.key_path.pop().ok_or(Error::EmptyIbcPath)?; + + // This storage root is verified during the header update, so we don't need to verify it again. + let storage_root = consensus_state.data.ibc_storage_root; + + match value { + StorageState::Occupied(value) => { + let inclusion_proof = + InclusionProof::decode(&proof).map_err(Error::StorageProofDecode)?; + do_verify_membership( + path, + storage_root, + client_state.data.l2_ibc_contract_commitment_slot, + inclusion_proof, + value, + )? + } + StorageState::Empty => { + let noninclusion_proof = + NonInclusionProof::decode(&proof).map_err(Error::StorageProofDecode)?; + do_verify_non_membership( + path, + storage_root, + client_state.data.l2_ibc_contract_commitment_slot, + noninclusion_proof, + )? + } + } + + Ok(()) + } + + fn verify_header( + deps: Deps, + env: Env, + header: Self::Header, + ) -> Result<(), IbcClientError> { + let client_state: WasmClientState = read_client_state(deps)?; + let l1_consensus_state = query_consensus_state::( + deps, + &env, + client_state.data.l1_client_id.clone(), + header.l1_height, + ) + .map_err(Error::CustomQuery)?; + linea_verifier::verify_header( + client_state.data, + header, + l1_consensus_state.data.state_root, + ) + .map_err(Error::Verify)?; + Ok(()) + } + + fn verify_misbehaviour( + _deps: Deps, + _env: Env, + _misbehaviour: Self::Misbehaviour, + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) + } + + fn update_state( + mut deps: DepsMut, + _env: Env, + header: Self::Header, + ) -> Result, IbcClientError> { + let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; + + let updated_height = Height { + revision_number: client_state.latest_height.revision_number, + revision_height: header.l1_height.revision_height, + }; + + if client_state.latest_height < header.l1_height { + client_state.data.l1_latest_height = updated_height; + update_client_state::( + deps.branch(), + client_state, + updated_height.revision_height, + ); + } + + // TODO: perhaps force the proof to be an actual inclusion proof off-chain + let account = match header.l2_ibc_contract_proof { + unionlabs::linea::proof::MerkleProof::Inclusion(inclusion) => { + // Guaranteed to success as we previously verified the proof + // which involved decoding and hashing the account. + ZkAccount::try_from(inclusion.proof.value.as_ref()).expect("impossible") + } + unionlabs::linea::proof::MerkleProof::NonInclusion(_) => { + return Err(Error::InvalidL2AccountProof.into()) + } + }; + + let consensus_state = WasmConsensusState { + data: ConsensusState { + ibc_storage_root: account.storage_root, + // must be nanos + timestamp: 1_000_000_000 * header.l2_timestamp, + }, + }; + save_consensus_state::(deps, consensus_state, &updated_height); + Ok(vec![updated_height]) + } + + fn update_state_on_misbehaviour( + deps: DepsMut, + env: Env, + _client_message: Vec, + ) -> Result<(), IbcClientError> { + let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; + client_state.data.frozen_height = Height { + revision_number: client_state.latest_height.revision_number, + revision_height: env.block.height, + }; + save_client_state::(deps, client_state); + Ok(()) + } + + fn check_for_misbehaviour_on_header( + _deps: Deps, + _header: Self::Header, + ) -> Result> { + Ok(false) + } + + fn check_for_misbehaviour_on_misbehaviour( + _deps: Deps, + _misbehaviour: Self::Misbehaviour, + ) -> Result> { + Err(Error::Unimplemented.into()) + } + + fn verify_upgrade_and_update_state( + _deps: DepsMut, + _upgrade_client_state: Self::ClientState, + _upgrade_consensus_state: Self::ConsensusState, + _proof_upgrade_client: Vec, + _proof_upgrade_consensus_state: Vec, + ) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) + } + + fn migrate_client_store(_deps: DepsMut) -> Result<(), IbcClientError> { + Err(Error::Unimplemented.into()) + } + + fn status(deps: Deps, _env: &Env) -> Result> { + let client_state: WasmClientState = read_client_state(deps)?; + + if client_state.data.frozen_height != Height::default() { + return Ok(Status::Frozen); + } + + let Some(_) = read_consensus_state::(deps, &client_state.latest_height)? else { + return Ok(Status::Expired); + }; + + Ok(Status::Active) + } + + fn export_metadata( + _deps: Deps, + _env: &Env, + ) -> Result, IbcClientError> { + Ok(Vec::new()) + } + + fn timestamp_at_height( + deps: Deps, + height: Height, + ) -> Result> { + Ok(read_consensus_state::(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height))? + .data + .timestamp) + } +} + +fn do_verify_membership( + path: String, + storage_root: H256, + ibc_commitment_slot: U256, + storage_proof: InclusionProof, + raw_value: Vec, +) -> Result<(), Error> { + // TODO: handle error + let key = storage_proof.key.try_into().unwrap(); + + check_commitment_key(&path, ibc_commitment_slot, key)?; + + let path = path + .parse::>() + .map_err(Error::PathParse)?; + + let canonical_value = match path { + Path::ClientState(_) => { + Any::::decode(raw_value.as_ref()) + .map_err(Error::CometblsClientStateDecode)? + .0 + .encode_as::() + } + Path::ClientConsensusState(_) => Any::< + wasm::consensus_state::ConsensusState, + >::decode(raw_value.as_ref()) + .map_err(Error::CometblsConsensusStateDecode)? + .0 + .data + .encode_as::(), + _ => raw_value, + }; + + // We store the hash of the data, not the data itself to the commitments map. + let expected_value_hash = H256::from( + sha3::Keccak256::new() + .chain_update(canonical_value) + .finalize(), + ); + + // TODO: handle error + let proof_value = H256(storage_proof.proof.value.clone().try_into().unwrap()); + + if expected_value_hash != proof_value { + return Err(Error::StoredValueMismatch { + expected: expected_value_hash, + stored: proof_value, + }); + } + + let (_, _) = linea_zktrie::verify::verify_inclusion::( + &new_mimc_constants_bls12_377(), + storage_proof.leaf_index, + &storage_proof.proof, + storage_root, + // Key will be verified + Some(key), + )?; + + Ok(()) +} + +/// Verifies that no value is committed at `path` in the counterparty light client's storage. +fn do_verify_non_membership( + path: String, + storage_root: H256, + ibc_commitment_slot: U256, + noninclusion_proof: NonInclusionProof, +) -> Result<(), Error> { + // TODO: handle error + let key = noninclusion_proof.key.clone().try_into().unwrap(); + + check_commitment_key(&path, ibc_commitment_slot, key)?; + + linea_zktrie::verify::verify_noninclusion::( + &new_mimc_constants_bls12_377(), + &noninclusion_proof, + storage_root, + key, + )?; + + Ok(()) +} + +fn check_commitment_key(path: &str, ibc_commitment_slot: U256, key: H256) -> Result<(), Error> { + let expected_commitment_key = generate_commitment_key(path, ibc_commitment_slot); + + // Data MUST be stored to the commitment path that is defined in ICS23. + if expected_commitment_key != key { + Err(Error::InvalidCommitmentKey { + expected: expected_commitment_key, + found: key, + }) + } else { + Ok(()) + } +} diff --git a/light-clients/linea-light-client/src/contract.rs b/light-clients/linea-light-client/src/contract.rs new file mode 100644 index 0000000000..75f65b63d1 --- /dev/null +++ b/light-clients/linea-light-client/src/contract.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; +use ics008_wasm_client::{ + define_cosmwasm_light_client_contract, + storage_utils::{save_proto_client_state, save_proto_consensus_state}, + CustomQueryOf, InstantiateMsg, +}; +use protos::ibc::lightclients::wasm::v1::{ + ClientState as ProtoClientState, ConsensusState as ProtoConsensusState, +}; +use unionlabs::{ + encoding::{DecodeAs, Proto}, + ibc::{core::client::height::Height, lightclients::scroll::client_state::ClientState}, +}; + +use crate::{client::LineaLightClient, errors::Error}; + +#[entry_point] +pub fn instantiate( + mut deps: DepsMut>, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let client_state = + ClientState::decode_as::(&msg.client_state).map_err(Error::ClientStateDecode)?; + + save_proto_consensus_state::( + deps.branch(), + ProtoConsensusState { + data: msg.consensus_state.into(), + }, + &Height { + revision_number: 0, + revision_height: client_state.latest_slot, + }, + ); + save_proto_client_state::( + deps, + ProtoClientState { + data: msg.client_state.into(), + checksum: msg.checksum.into(), + latest_height: Some( + Height { + revision_number: 0, + revision_height: client_state.latest_slot, + } + .into(), + ), + }, + ); + Ok(Response::default()) +} + +define_cosmwasm_light_client_contract!(LineaLightClient, Linea); diff --git a/light-clients/linea-light-client/src/errors.rs b/light-clients/linea-light-client/src/errors.rs new file mode 100644 index 0000000000..7439c481ac --- /dev/null +++ b/light-clients/linea-light-client/src/errors.rs @@ -0,0 +1,92 @@ +use ics008_wasm_client::IbcClientError; +use unionlabs::{ + encoding::{DecodeErrorOf, Proto}, + google::protobuf::any::Any, + hash::H256, + ibc::{core::client::height::Height, lightclients::wasm}, + ics24::PathParseError, +}; + +use crate::client::LineaLightClient; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { + #[error("unable to decode storage proof")] + StorageProofDecode(#[source] DecodeErrorOf), + #[error("unable to decode counterparty's stored cometbls client state")] + CometblsClientStateDecode( + #[source] + DecodeErrorOf< + Proto, + Any, + >, + ), + #[error("unable to decode counterparty's stored cometbls consensus state")] + CometblsConsensusStateDecode( + #[source] + DecodeErrorOf< + Proto, + Any< + wasm::consensus_state::ConsensusState< + unionlabs::ibc::lightclients::cometbls::consensus_state::ConsensusState, + >, + >, + >, + ), + #[error("unable to decode client state")] + ClientStateDecode( + #[source] + DecodeErrorOf, + ), + #[error("unable to decode consensus state")] + ConsensusStateDecode( + #[source] + DecodeErrorOf< + Proto, + unionlabs::ibc::lightclients::scroll::consensus_state::ConsensusState, + >, + ), + + // REVIEW: Move this variant to IbcClientError? + #[error("consensus state not found at height {0}")] + ConsensusStateNotFound(Height), + + #[error("IBC path is empty")] + EmptyIbcPath, + + #[error("invalid commitment key, expected ({expected}) but found ({found})")] + InvalidCommitmentKey { expected: H256, found: H256 }, + + #[error("proof is empty")] + EmptyProof, + + #[error("batching proofs are not supported")] + BatchingProofsNotSupported, + + #[error("expected value ({expected}) and stored value ({stored}) don't match")] + StoredValueMismatch { expected: H256, stored: H256 }, + + #[error("unable to parse ics24 path")] + PathParse(#[from] PathParseError), + + #[error("failed to verify linea header: {0}")] + Verify(#[from] linea_verifier::Error), + + #[error("the operation has not been implemented yet")] + Unimplemented, + + #[error("error while calling custom query: {0}")] + CustomQuery(#[from] unionlabs::cosmwasm::wasm::union::custom_query::Error), + + #[error("L2 account proof must be an inclusion proof")] + InvalidL2AccountProof, + + #[error("failed to verify linea membership proof {0}")] + InvalidMembershipProof(#[from] linea_zktrie::verify::Error), +} + +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) + } +} diff --git a/light-clients/linea-light-client/src/eth_encoding.rs b/light-clients/linea-light-client/src/eth_encoding.rs new file mode 100644 index 0000000000..d6197099d3 --- /dev/null +++ b/light-clients/linea-light-client/src/eth_encoding.rs @@ -0,0 +1,15 @@ +use sha3::Digest; +use unionlabs::{hash::H256, uint::U256}; + +// TODO: move to unionlabs as it can be reused by any chain hosting our EVM IBC + +/// Calculates the slot for a `path` at saved in the commitment map in `slot` +/// +/// key: keccak256(keccak256(abi.encode_packed(path)) || slot) +pub fn generate_commitment_key(path: &str, slot: U256) -> H256 { + sha3::Keccak256::new() + .chain_update(sha3::Keccak256::new().chain_update(path).finalize()) + .chain_update(slot.to_be_bytes()) + .finalize() + .into() +} diff --git a/light-clients/linea-light-client/src/lib.rs b/light-clients/linea-light-client/src/lib.rs new file mode 100644 index 0000000000..b26d0adbfe --- /dev/null +++ b/light-clients/linea-light-client/src/lib.rs @@ -0,0 +1,4 @@ +pub mod client; +pub mod contract; +pub mod errors; +pub mod eth_encoding; diff --git a/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto new file mode 100644 index 0000000000..b2c28725fa --- /dev/null +++ b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; +package union.ibc.lightclients.linea.v1; + +option go_package = "union/ibc/lightclients/linea"; +import "ibc/core/client/v1/client.proto"; +import "union/ibc/lightclients/ethereum/v1/ethereum.proto"; + +message ClientState { + string chain_id = 1; + .ibc.core.client.v1.Height l1_latest_height = 2; + string l1_client_id = 3; + bytes l1_rollup_contract_address = 4; + bytes l1_rollup_current_l2_block_number_slot = 5; + bytes l1_rollup_current_l2_timestamp_slot = 6; + bytes l1_rollup_l2_state_root_hashes_slot = 7; + bytes l2_ibc_contract_address = 8; + bytes l2_ibc_contract_commitment_slot = 9; + .ibc.core.client.v1.Height frozen_height = 10; +} + +message ConsensusState { + bytes ibc_storage_root = 1; + uint64 timestamp = 2; +} + +message Header { + .ibc.core.client.v1.Height l1_height = 1; + .union.ibc.lightclients.ethereum.v1.AccountProof l1_rollup_contract_proof = 2; + uint64 l2_block_number = 3; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_block_number_proof = 4; + bytes l2_state_root = 5; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_state_root_proof = 6; + uint64 l2_timestamp = 7; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_timestamp_proof = 8; + MerkleProof l2_ibc_contract_proof = 9; +} + +message MerklePath { + bytes value = 1; + repeated bytes proof_related_nodes = 2; +} + +message InclusionProof { + bytes key = 1; + uint64 leaf_index = 2; + MerklePath merkle_path = 3; +} + +message NonInclusionProof { + bytes key = 1; + uint64 left_leaf_index = 2; + MerklePath left_proof = 3; + uint64 right_leaf_index = 4; + MerklePath right_proof = 5; +} + +message MerkleProof { + oneof proof { + InclusionProof inclusion = 1; + NonInclusionProof noninclusion = 2; + } +} From 64c7f61147b0f1ec60f0ddb103b3d6213b60d4f0 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Sat, 11 May 2024 01:48:12 +0200 Subject: [PATCH 2/8] chore: spellcheck --- dictionary.txt | 5 +++++ lib/gnark-mimc/src/lib.rs | 2 +- lib/linea-zktrie/src/verify.rs | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dictionary.txt b/dictionary.txt index 758695499f..bc8309a3de 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -145,6 +145,7 @@ Solana Subdemom Sublist Subo +Subtrie Tada Tempdirs Tendermint @@ -172,6 +173,7 @@ Wrapf XMSB YMSB Yair +ZKACCOUNT ZKTRIE Zerg aarch @@ -608,6 +610,7 @@ libwasmvmstatic lifecycles lightclient lightclients +linea linkmode livenesses lmfao @@ -685,6 +688,7 @@ nois nolint nolus nonexist +noninclusion noopener noreferrer notrunc @@ -919,6 +923,7 @@ subsec subslice substituters substores +subtrie sumbitter superforms supermajority diff --git a/lib/gnark-mimc/src/lib.rs b/lib/gnark-mimc/src/lib.rs index 2f6ad1d594..5841120c8e 100644 --- a/lib/gnark-mimc/src/lib.rs +++ b/lib/gnark-mimc/src/lib.rs @@ -119,7 +119,7 @@ impl<'a, F: PrimeField, const K: usize, const E: u64> MiMC<'a, F, K, E> { // https://github.com/Consensys/gnark-crypto/blob/564b6f724c3beac52d805e6e600d0a1fda9770b5/ecc/bn254/fr/mimc/mimc.go#L105 pub fn update(mut self, elements: impl AsRef<[u8]>) -> Result { - // Slight difference, we only accept a mutiple of the field. No hidden, implicit padding. + // Slight difference, we only accept a multiple of the field. No hidden, implicit padding. let elements = elements.as_ref(); if elements.len() % Self::FIELD_ELEMENT_BYTES_LEN != 0 { return Err(Error::InvalidLength(InvalidLength { diff --git a/lib/linea-zktrie/src/verify.rs b/lib/linea-zktrie/src/verify.rs index 81f8112e2b..f3878c66d7 100644 --- a/lib/linea-zktrie/src/verify.rs +++ b/lib/linea-zktrie/src/verify.rs @@ -71,7 +71,7 @@ pub enum Error { #[error("invalid mimc hashing: {0:?}")] MimcError(gnark_mimc::Error), #[error("could not decode leaf value")] - CouldntDecodeValue, + CouldNotDecodeValue, #[error("invalid trie root, actual: {actual}, expected: {expected}")] RootMismatch { actual: H256, expected: H256 }, #[error("invalid subtrie root, actual: {actual}, expected: {expected}")] @@ -242,15 +242,15 @@ pub fn verify_inclusion TryFrom<&'a [u8]>>( } } // For non inclusion proof, we don't know the key of the left/right - // nodes. We instead verify that the expected key is sandwitched after + // nodes. We instead verify that the expected key is sandwiched after // verifying inclusion. Hence, nothing is done there. None => {} } // The value is decoded then hashed, the decoding is required as the value // may need to be transformed before being hashed (ZkAccount keccak field // that need to be split in two elements to fit in the scalar field for - // instane) - let value = V::try_from(merkle_path.value.as_ref()).map_err(|_| Error::CouldntDecodeValue)?; + // instance) + let value = V::try_from(merkle_path.value.as_ref()).map_err(|_| Error::CouldNotDecodeValue)?; // Verify that the value is related to the leaf let recomputed_value = value.clone().hash(constants)?; if verifiable_path.leaf.value != recomputed_value { @@ -748,7 +748,7 @@ mod tests { // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/core/src/test/java/net/consensys/shomei/ZkAccountTest.java#L34 #[test] - fn test_hash_zeroaccount() { + fn test_hash_zero_account() { let mimc_constants = new_mimc_constants_bls12_377(); let mimc = new_mimc_bls12_377(&mimc_constants); let hash = mimc From 9e80490612e9ec7ed912e635635804c4ee298352 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Sat, 11 May 2024 01:58:04 +0200 Subject: [PATCH 3/8] chore: clippy --- lib/gnark-mimc/src/lib.rs | 4 +- lib/linea-zktrie/src/node.rs | 7 ++- lib/linea-zktrie/src/verify.rs | 74 ++++++++++++++---------------- lib/unionlabs/src/linea/account.rs | 18 ++++---- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/lib/gnark-mimc/src/lib.rs b/lib/gnark-mimc/src/lib.rs index 5841120c8e..08663d31fa 100644 --- a/lib/gnark-mimc/src/lib.rs +++ b/lib/gnark-mimc/src/lib.rs @@ -33,7 +33,7 @@ pub fn new_mimc_constants_bls12_377() -> MiMCBls12377Constants { } pub fn new_mimc_bls12_377(constants: &MiMCBls12377Constants) -> MiMCBls12377 { - MiMC::new(&constants) + MiMC::new(constants) } pub fn mimc_sum_bl12377( @@ -52,7 +52,7 @@ pub fn new_mimc_constants_bn254() -> MiMCConstants MiMCBn254 { - MiMC::new(&constants) + MiMC::new(constants) } pub fn mimc_sum_bn254( diff --git a/lib/linea-zktrie/src/node.rs b/lib/linea-zktrie/src/node.rs index 9641d43636..8406b201e2 100644 --- a/lib/linea-zktrie/src/node.rs +++ b/lib/linea-zktrie/src/node.rs @@ -16,8 +16,7 @@ pub const LEAF_TYPE_VALUE: u8 = 0x16; pub fn bytes_to_leaf_path(bytes: &[u8], terminator_path: u8) -> Vec { let mut path = vec![0u8; bytes.len() * 2 + 1]; let mut j = 0; - for i in j..bytes.len() { - let b = bytes[i]; + for b in bytes.iter().skip(j) { path[j] = b >> 4 & 15; path[j + 1] = b & 15; j += 2; @@ -29,7 +28,7 @@ pub fn bytes_to_leaf_path(bytes: &[u8], terminator_path: u8) -> Vec { // TODO: use bitvec // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L82 pub fn node_index_to_bytes(trie_depth: usize, node_index: u64) -> Vec { - hex::decode(&format!("{node_index:0>trie_depth$b}")).unwrap() + hex::decode(format!("{node_index:0>trie_depth$b}")).unwrap() } // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L71 @@ -166,7 +165,7 @@ impl Node { Node::Leaf(node) => node.hash(constants), Node::Branch(node) => node.hash(constants), Node::Root(node) => node.hash(constants), - Node::EmptyLeaf(_) => return Ok(EmptyLeafNode::HASH), + Node::EmptyLeaf(_) => Ok(EmptyLeafNode::HASH), } } } diff --git a/lib/linea-zktrie/src/verify.rs b/lib/linea-zktrie/src/verify.rs index f3878c66d7..214b30b3ce 100644 --- a/lib/linea-zktrie/src/verify.rs +++ b/lib/linea-zktrie/src/verify.rs @@ -32,7 +32,7 @@ impl ZkKey for H160 { fn hash(self, constants: &MiMCBls12377Constants) -> Result { let mut padded_key = [0u8; 32]; padded_key[12..32].copy_from_slice(self.as_ref()); - mimc_sum_bl12377(constants, &padded_key) + mimc_sum_bl12377(constants, padded_key) } } @@ -81,7 +81,10 @@ pub enum Error { #[error("value mismatch, actual: {actual}, expected: {expected}")] ValueMismatch { actual: H256, expected: H256 }, #[error("non adjacent node, left: {left:?}, right: {right:?}")] - NonAdjacentNode { left: LeafNode, right: LeafNode }, + NonAdjacentNode { + left: Box, + right: Box, + }, #[error("key not in center, left: {left}, key: {key} right: {right}")] KeyNotInCenter { left: H256, key: H256, right: H256 }, } @@ -189,16 +192,16 @@ pub fn verify_noninclusion TryFrom<&'a [u8]>>( != U256::from(noninclusion_proof.left_leaf_index) { return Err(Error::NonAdjacentNode { - left: left_path.leaf, - right: right_path.leaf, + left: left_path.leaf.into(), + right: right_path.leaf.into(), }); } // N-.Next == i+ if U256::from_be_bytes(left_path.leaf.next.0) != U256::from(noninclusion_proof.right_leaf_index) { return Err(Error::NonAdjacentNode { - left: left_path.leaf, - right: right_path.leaf, + left: left_path.leaf.into(), + right: right_path.leaf.into(), }); } // HKey- < hash(k) < HKey+ @@ -230,21 +233,18 @@ pub fn verify_inclusion TryFrom<&'a [u8]>>( expected: root, }); } - match key { - Some(key) => { - // Verify that they leaf is related to our key - let recomputed_key = key.hash(constants)?; - if verifiable_path.leaf.hashed_key != recomputed_key { - return Err(Error::KeyMismatch { - actual: recomputed_root, - expected: verifiable_path.leaf.hashed_key, - }); - } + // For non inclusion proof, we don't know the key of the left/right + // nodes. We instead verify that the expected key is sandwiched after + // verifying right/left inclusion. Hence, nothing is done there. + if let Some(key) = key { + // Verify that they leaf is related to our key + let recomputed_key = key.hash(constants)?; + if verifiable_path.leaf.hashed_key != recomputed_key { + return Err(Error::KeyMismatch { + actual: recomputed_root, + expected: verifiable_path.leaf.hashed_key, + }); } - // For non inclusion proof, we don't know the key of the left/right - // nodes. We instead verify that the expected key is sandwiched after - // verifying inclusion. Hence, nothing is done there. - None => {} } // The value is decoded then hashed, the decoding is required as the value // may need to be transformed before being hashed (ZkAccount keccak field @@ -267,26 +267,20 @@ pub fn verify_inclusion TryFrom<&'a [u8]>>( let leaf_hash = verifiable_path.leaf.hash(constants)?; // Starts with the leaf hash, then recursively walk back to the tip of the // tree. - let subtrie_root = verifiable_path - .path - .iter() - .zip(inner_path) - .rev() - .into_iter() - .try_fold( - leaf_hash, - |current_hash, (node, direction)| -> Result { - let node_hash = node.hash(constants)?; - let to_hash = match direction { - // We went on the left branch, we hash against the right sibling - DIRECTION_LEFT => [current_hash.as_ref(), node_hash.as_ref()].concat(), - // We went on the right branch, we hash against the left sibling - DIRECTION_RIGHT => [node_hash.as_ref(), current_hash.as_ref()].concat(), - d => return Err(Error::InvalidDirection(d)), - }; - Ok(mimc_sum_bl12377(constants, to_hash)?) - }, - )?; + let subtrie_root = verifiable_path.path.iter().zip(inner_path).rev().try_fold( + leaf_hash, + |current_hash, (node, direction)| -> Result { + let node_hash = node.hash(constants)?; + let to_hash = match direction { + // We went on the left branch, we hash against the right sibling + DIRECTION_LEFT => [current_hash.as_ref(), node_hash.as_ref()].concat(), + // We went on the right branch, we hash against the left sibling + DIRECTION_RIGHT => [node_hash.as_ref(), current_hash.as_ref()].concat(), + d => return Err(Error::InvalidDirection(d)), + }; + Ok(mimc_sum_bl12377(constants, to_hash)?) + }, + )?; // Verify the subtrie root if verifiable_path.root.child_hash != subtrie_root { return Err(Error::SubtrieRootMismatch { diff --git a/lib/unionlabs/src/linea/account.rs b/lib/unionlabs/src/linea/account.rs index 4bacacbb79..bb11e8f349 100644 --- a/lib/unionlabs/src/linea/account.rs +++ b/lib/unionlabs/src/linea/account.rs @@ -21,20 +21,21 @@ pub struct ZkAccount { } impl ZkAccount { + #[must_use] pub fn into_bytes(self) -> Vec { self.into() } } -impl Into> for ZkAccount { - fn into(self) -> Vec { +impl From for Vec { + fn from(value: ZkAccount) -> Vec { [ - self.nonce.to_be_bytes().as_ref(), - self.balance.to_be_bytes().as_ref(), - &self.storage_root.into_bytes(), - &self.mimc_code_hash.into_bytes(), - &self.keccak_code_hash.into_bytes(), - self.code_size.to_be_bytes().as_ref(), + value.nonce.to_be_bytes().as_ref(), + value.balance.to_be_bytes().as_ref(), + &value.storage_root.into_bytes(), + &value.mimc_code_hash.into_bytes(), + &value.keccak_code_hash.into_bytes(), + value.code_size.to_be_bytes().as_ref(), ] .concat() } @@ -65,6 +66,7 @@ pub struct MimcSafeBytes { } impl MimcSafeBytes { + #[must_use] pub fn into_bytes(self) -> Vec { [self.lsb.0, self.msb.0].concat() } From 825e75ad9e54a75a7c43b6b28f8e4a03ad9fc8d8 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 13 May 2024 14:37:12 +0200 Subject: [PATCH 4/8] chore(linea): move decoding to associated fns --- lib/linea-zktrie/src/node.rs | 24 +++--- lib/linea-zktrie/src/verify.rs | 85 +++++++++++-------- lib/unionlabs/src/linea/account.rs | 34 ++++---- lib/unionlabs/src/scroll/account.rs | 5 +- .../linea-light-client/src/client.rs | 7 +- 5 files changed, 82 insertions(+), 73 deletions(-) diff --git a/lib/linea-zktrie/src/node.rs b/lib/linea-zktrie/src/node.rs index 8406b201e2..36b6529d69 100644 --- a/lib/linea-zktrie/src/node.rs +++ b/lib/linea-zktrie/src/node.rs @@ -49,6 +49,10 @@ pub struct EmptyLeafNode {} impl EmptyLeafNode { // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/EmptyLeafNode.java#L80 pub const HASH: H256 = H256([0u8; 32]); + + pub fn hash(&self) -> H256 { + Self::HASH + } } #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] @@ -73,11 +77,9 @@ impl LeafNode { .concat(), ) } -} -impl TryFrom<&[u8]> for LeafNode { - type Error = InvalidLength; - fn try_from(value: &[u8]) -> Result { + pub fn decode(value: impl AsRef<[u8]>) -> Result { + let value = value.as_ref(); let values = <[u8; 128]>::try_from(value).map_err(|_| InvalidLength { expected: ExpectedLength::Exact(128), found: value.len(), @@ -105,11 +107,9 @@ impl BranchNode { [self.left.as_ref(), self.right.as_ref()].concat(), ) } -} -impl TryFrom<&[u8]> for BranchNode { - type Error = InvalidLength; - fn try_from(value: &[u8]) -> Result { + pub fn decode(value: impl AsRef<[u8]>) -> Result { + let value = value.as_ref(); let values = <[u8; 64]>::try_from(value).map_err(|_| InvalidLength { expected: ExpectedLength::Exact(64), found: value.len(), @@ -135,11 +135,9 @@ impl RootNode { [self.next_free_node.as_ref(), self.child_hash.as_ref()].concat(), ) } -} -impl TryFrom<&[u8]> for RootNode { - type Error = InvalidLength; - fn try_from(value: &[u8]) -> Result { + pub fn decode(value: impl AsRef<[u8]>) -> Result { + let value = value.as_ref(); let values = <[u8; 64]>::try_from(value).map_err(|_| InvalidLength { expected: ExpectedLength::Exact(64), found: value.len(), @@ -165,7 +163,7 @@ impl Node { Node::Leaf(node) => node.hash(constants), Node::Branch(node) => node.hash(constants), Node::Root(node) => node.hash(constants), - Node::EmptyLeaf(_) => Ok(EmptyLeafNode::HASH), + Node::EmptyLeaf(node) => Ok(node.hash()), } } } diff --git a/lib/linea-zktrie/src/verify.rs b/lib/linea-zktrie/src/verify.rs index 214b30b3ce..b10b5580e1 100644 --- a/lib/linea-zktrie/src/verify.rs +++ b/lib/linea-zktrie/src/verify.rs @@ -26,6 +26,10 @@ pub trait ZkValue { type Key: ZkKey; fn hash(self, constants: &MiMCBls12377Constants) -> Result; + + fn decode(value: impl AsRef<[u8]>) -> Result + where + Self: Sized; } impl ZkKey for H160 { @@ -42,6 +46,13 @@ impl ZkValue for ZkAccount { fn hash(self, constants: &MiMCBls12377Constants) -> Result { mimc_sum_bl12377(constants, self.into_bytes()) } + + fn decode(value: impl AsRef<[u8]>) -> Result + where + Self: Sized, + { + ZkAccount::decode(value) + } } impl ZkKey for H256 { @@ -56,6 +67,13 @@ impl ZkValue for H256 { fn hash(self, constants: &MiMCBls12377Constants) -> Result { mimc_sum_bl12377(constants, MimcSafeBytes::from(self.0).into_bytes()) } + + fn decode(value: impl AsRef<[u8]>) -> Result + where + Self: Sized, + { + H256::try_from(value.as_ref()) + } } #[derive(Clone, Debug, PartialEq, thiserror::Error)] @@ -111,20 +129,13 @@ pub struct VerifiablePath { impl TryFrom<&MerklePath> for VerifiablePath { type Error = Error; fn try_from(value: &MerklePath) -> Result { - let root = RootNode::try_from( + let root = RootNode::decode( value .proof_related_nodes .first() - .ok_or(Error::MissingRoot)? - .as_ref(), - )?; - let leaf = LeafNode::try_from( - value - .proof_related_nodes - .last() - .ok_or(Error::MissingLeaf)? - .as_ref(), + .ok_or(Error::MissingRoot)?, )?; + let leaf = LeafNode::decode(value.proof_related_nodes.last().ok_or(Error::MissingLeaf)?)?; // Minus root/leaf let inner_path_len = value.proof_related_nodes.len() - 2; let path = value @@ -132,11 +143,11 @@ impl TryFrom<&MerklePath> for VerifiablePath { .iter() .skip(1) .take(inner_path_len) - .map(|node| { - if node.len() == MiMCBls12377::FIELD_ELEMENT_BYTES_LEN * 2 { - BranchNode::try_from(node.as_ref()).map(Node::Branch) + .map(|raw_node| { + if raw_node.len() == MiMCBls12377::FIELD_ELEMENT_BYTES_LEN * 2 { + BranchNode::decode(raw_node).map(Node::Branch) } else { - LeafNode::try_from(node.as_ref()).map(Node::Leaf) + LeafNode::decode(raw_node).map(Node::Leaf) } }) .collect::, _>>()?; @@ -144,19 +155,19 @@ impl TryFrom<&MerklePath> for VerifiablePath { } } -pub fn verify TryFrom<&'a [u8]>>( +pub fn verify( constants: &MiMCBls12377Constants, proof: &MerkleProof, root: H256, key: V::Key, ) -> Result, Error> { match proof { - MerkleProof::Inclusion(inclusion_proof) => verify_inclusion::( + MerkleProof::Inclusion(inclusion_proof) => verify_inclusion_and_key::( constants, inclusion_proof.leaf_index, &inclusion_proof.proof, root, - Some(key), + key, ) .map(|(_, value)| Some(value)), MerkleProof::NonInclusion(noninclusion_proof) => { @@ -165,7 +176,7 @@ pub fn verify TryFrom<&'a [u8]>>( } } -pub fn verify_noninclusion TryFrom<&'a [u8]>>( +pub fn verify_noninclusion( constants: &MiMCBls12377Constants, noninclusion_proof: &NonInclusionProof, root: H256, @@ -177,7 +188,6 @@ pub fn verify_noninclusion TryFrom<&'a [u8]>>( noninclusion_proof.left_leaf_index, &noninclusion_proof.left_proof, root, - None, )?; // right in root let (right_path, _) = verify_inclusion::( @@ -185,7 +195,6 @@ pub fn verify_noninclusion TryFrom<&'a [u8]>>( noninclusion_proof.right_leaf_index, &noninclusion_proof.right_proof, root, - None, )?; // N+.Prev == i- if U256::from_be_bytes(right_path.leaf.previous.0) @@ -216,12 +225,29 @@ pub fn verify_noninclusion TryFrom<&'a [u8]>>( Ok(()) } -pub fn verify_inclusion TryFrom<&'a [u8]>>( +pub fn verify_inclusion_and_key( + constants: &MiMCBls12377Constants, + leaf_index: u64, + merkle_path: &MerklePath, + root: H256, + key: V::Key, +) -> Result<(VerifiablePath, V), Error> { + let (verifiable_path, value) = verify_inclusion::(constants, leaf_index, merkle_path, root)?; + let recomputed_key = key.hash(constants)?; + if verifiable_path.leaf.hashed_key != recomputed_key { + return Err(Error::KeyMismatch { + actual: recomputed_key, + expected: verifiable_path.leaf.hashed_key, + }); + } + Ok((verifiable_path, value)) +} + +pub fn verify_inclusion( constants: &MiMCBls12377Constants, leaf_index: u64, merkle_path: &MerklePath, root: H256, - key: Option, ) -> Result<(VerifiablePath, V), Error> { let leaf_path = get_leaf_path(ZK_TRIE_DEPTH, leaf_index); let verifiable_path = VerifiablePath::try_from(merkle_path)?; @@ -233,24 +259,11 @@ pub fn verify_inclusion TryFrom<&'a [u8]>>( expected: root, }); } - // For non inclusion proof, we don't know the key of the left/right - // nodes. We instead verify that the expected key is sandwiched after - // verifying right/left inclusion. Hence, nothing is done there. - if let Some(key) = key { - // Verify that they leaf is related to our key - let recomputed_key = key.hash(constants)?; - if verifiable_path.leaf.hashed_key != recomputed_key { - return Err(Error::KeyMismatch { - actual: recomputed_root, - expected: verifiable_path.leaf.hashed_key, - }); - } - } // The value is decoded then hashed, the decoding is required as the value // may need to be transformed before being hashed (ZkAccount keccak field // that need to be split in two elements to fit in the scalar field for // instance) - let value = V::try_from(merkle_path.value.as_ref()).map_err(|_| Error::CouldNotDecodeValue)?; + let value = V::decode(&merkle_path.value).map_err(|_| Error::CouldNotDecodeValue)?; // Verify that the value is related to the leaf let recomputed_value = value.clone().hash(constants)?; if verifiable_path.leaf.value != recomputed_value { diff --git a/lib/unionlabs/src/linea/account.rs b/lib/unionlabs/src/linea/account.rs index bb11e8f349..89fc43f3ff 100644 --- a/lib/unionlabs/src/linea/account.rs +++ b/lib/unionlabs/src/linea/account.rs @@ -25,25 +25,9 @@ impl ZkAccount { pub fn into_bytes(self) -> Vec { self.into() } -} - -impl From for Vec { - fn from(value: ZkAccount) -> Vec { - [ - value.nonce.to_be_bytes().as_ref(), - value.balance.to_be_bytes().as_ref(), - &value.storage_root.into_bytes(), - &value.mimc_code_hash.into_bytes(), - &value.keccak_code_hash.into_bytes(), - value.code_size.to_be_bytes().as_ref(), - ] - .concat() - } -} -impl TryFrom<&[u8]> for ZkAccount { - type Error = InvalidLength; - fn try_from(value: &[u8]) -> Result { + pub fn decode(value: impl AsRef<[u8]>) -> Result { + let value = value.as_ref(); let value = <[u8; ZKACCOUNT_BYTES_LEN]>::try_from(value).map_err(|_| InvalidLength { expected: ExpectedLength::Exact(ZKACCOUNT_BYTES_LEN), found: value.len(), @@ -59,6 +43,20 @@ impl TryFrom<&[u8]> for ZkAccount { } } +impl From for Vec { + fn from(value: ZkAccount) -> Vec { + [ + value.nonce.to_be_bytes().as_ref(), + value.balance.to_be_bytes().as_ref(), + &value.storage_root.into_bytes(), + &value.mimc_code_hash.into_bytes(), + &value.keccak_code_hash.into_bytes(), + value.code_size.to_be_bytes().as_ref(), + ] + .concat() + } +} + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MimcSafeBytes { pub lsb: H256, diff --git a/lib/unionlabs/src/scroll/account.rs b/lib/unionlabs/src/scroll/account.rs index e3f340680f..d23d540de3 100644 --- a/lib/unionlabs/src/scroll/account.rs +++ b/lib/unionlabs/src/scroll/account.rs @@ -29,9 +29,10 @@ pub struct Account { impl Account { pub fn decode(value: impl AsRef<[u8]>) -> Result { - let value = <[u8; 160]>::try_from(value.as_ref()).map_err(|_| InvalidLength { + let value = value.as_ref(); + let value = <[u8; 160]>::try_from(value).map_err(|_| InvalidLength { expected: ExpectedLength::Exact(160), - found: value.as_ref().len(), + found: value.len(), })?; Ok(Account { code_size: u64::from_be_bytes(value.array_slice::<16, 8>()), diff --git a/light-clients/linea-light-client/src/client.rs b/light-clients/linea-light-client/src/client.rs index 3fac900f79..d4358f5def 100644 --- a/light-clients/linea-light-client/src/client.rs +++ b/light-clients/linea-light-client/src/client.rs @@ -159,7 +159,7 @@ impl IbcClient for LineaLightClient { unionlabs::linea::proof::MerkleProof::Inclusion(inclusion) => { // Guaranteed to success as we previously verified the proof // which involved decoding and hashing the account. - ZkAccount::try_from(inclusion.proof.value.as_ref()).expect("impossible") + ZkAccount::decode(inclusion.proof.value).expect("impossible") } unionlabs::linea::proof::MerkleProof::NonInclusion(_) => { return Err(Error::InvalidL2AccountProof.into()) @@ -301,13 +301,12 @@ fn do_verify_membership( }); } - let (_, _) = linea_zktrie::verify::verify_inclusion::( + linea_zktrie::verify::verify_inclusion_and_key::( &new_mimc_constants_bls12_377(), storage_proof.leaf_index, &storage_proof.proof, storage_root, - // Key will be verified - Some(key), + key, )?; Ok(()) From 5c0d0b7ec0b2fc695c0edd40898c56017c6c8f1d Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 13 May 2024 14:59:54 +0200 Subject: [PATCH 5/8] fix(linea): ensure header contains an inclusion proof --- .../src/union.ibc.lightclients.linea.v1.rs | 2 +- lib/linea-verifier/src/verify.rs | 11 ++++------- .../src/ibc/lightclients/linea/header.rs | 4 ++-- light-clients/linea-light-client/src/client.rs | 16 ++++------------ .../union/ibc/lightclients/linea/v1/linea.proto | 2 +- 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs index e062557c98..518c3ec79b 100644 --- a/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs +++ b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs @@ -68,7 +68,7 @@ pub struct Header { #[prost(message, optional, tag = "8")] pub l2_timestamp_proof: ::core::option::Option, #[prost(message, optional, tag = "9")] - pub l2_ibc_contract_proof: ::core::option::Option, + pub l2_ibc_contract_proof: ::core::option::Option, } impl ::prost::Name for Header { const NAME: &'static str = "Header"; diff --git a/lib/linea-verifier/src/verify.rs b/lib/linea-verifier/src/verify.rs index c5be5e2fb3..eb84353440 100644 --- a/lib/linea-verifier/src/verify.rs +++ b/lib/linea-verifier/src/verify.rs @@ -80,19 +80,16 @@ pub fn verify_header( .map_err(Error::InvalidL2StateRootProof)?; // 5. - // TODO: perhaps force the proof to be an actual inclusion proof off-chain - let account = linea_zktrie::verify::verify::( + linea_zktrie::verify::verify_inclusion_and_key::( &new_mimc_constants_bls12_377(), - &header.l2_ibc_contract_proof, + header.l2_ibc_contract_proof.leaf_index, + &header.l2_ibc_contract_proof.proof, header.l2_state_root, client_state.l2_ibc_contract_address, ) .map_err(Error::InvalidL2IbcContractProof)?; - match account { - Some(_) => Ok(()), - None => Err(Error::L2IbcContractProofIsNotInclusion), - } + Ok(()) } pub fn state_root_hashes_mapping_key(slot: U256, l2_block_number: U256) -> U256 { diff --git a/lib/unionlabs/src/ibc/lightclients/linea/header.rs b/lib/unionlabs/src/ibc/lightclients/linea/header.rs index eef401f872..113e537df7 100644 --- a/lib/unionlabs/src/ibc/lightclients/linea/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/linea/header.rs @@ -10,7 +10,7 @@ use crate::{ storage_proof::{StorageProof, TryFromStorageProofError}, }, }, - linea::proof::{MerkleProof, TryFromMerkleProofError}, + linea::proof::{InclusionProof, TryFromMerkleProofError}, }; #[model(proto(raw(protos::union::ibc::lightclients::linea::v1::Header), into, from))] @@ -23,7 +23,7 @@ pub struct Header { pub l2_state_root_proof: StorageProof, pub l2_timestamp: u64, pub l2_timestamp_proof: StorageProof, - pub l2_ibc_contract_proof: MerkleProof, + pub l2_ibc_contract_proof: InclusionProof, } impl From
for protos::union::ibc::lightclients::linea::v1::Header { diff --git a/light-clients/linea-light-client/src/client.rs b/light-clients/linea-light-client/src/client.rs index d4358f5def..b3644746ff 100644 --- a/light-clients/linea-light-client/src/client.rs +++ b/light-clients/linea-light-client/src/client.rs @@ -154,21 +154,13 @@ impl IbcClient for LineaLightClient { ); } - // TODO: perhaps force the proof to be an actual inclusion proof off-chain - let account = match header.l2_ibc_contract_proof { - unionlabs::linea::proof::MerkleProof::Inclusion(inclusion) => { - // Guaranteed to success as we previously verified the proof - // which involved decoding and hashing the account. - ZkAccount::decode(inclusion.proof.value).expect("impossible") - } - unionlabs::linea::proof::MerkleProof::NonInclusion(_) => { - return Err(Error::InvalidL2AccountProof.into()) - } - }; + // Guaranteed to succeed as we previously verified the header + let zk_account = + ZkAccount::decode(header.l2_ibc_contract_proof.proof.value).expect("impossible"); let consensus_state = WasmConsensusState { data: ConsensusState { - ibc_storage_root: account.storage_root, + ibc_storage_root: zk_account.storage_root, // must be nanos timestamp: 1_000_000_000 * header.l2_timestamp, }, diff --git a/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto index b2c28725fa..f95a1410d0 100644 --- a/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto +++ b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto @@ -32,7 +32,7 @@ message Header { .union.ibc.lightclients.ethereum.v1.StorageProof l2_state_root_proof = 6; uint64 l2_timestamp = 7; .union.ibc.lightclients.ethereum.v1.StorageProof l2_timestamp_proof = 8; - MerkleProof l2_ibc_contract_proof = 9; + InclusionProof l2_ibc_contract_proof = 9; } message MerklePath { From c17b5908eb3c62b877f6d2e9a7cfbd40d5b73256 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 13 May 2024 20:03:18 +0200 Subject: [PATCH 6/8] feat(linea): ergonomic types and full header test --- .../src/union.ibc.lightclients.linea.v1.rs | 14 +- lib/linea-verifier/src/verify.rs | 193 +++++++++++++++++- .../ibc/lightclients/linea/client_state.rs | 2 +- .../src/ibc/lightclients/linea/header.rs | 23 +-- .../linea-light-client/src/client.rs | 3 +- .../ibc/lightclients/linea/v1/linea.proto | 11 +- 6 files changed, 200 insertions(+), 46 deletions(-) diff --git a/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs index 518c3ec79b..16879f79d8 100644 --- a/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs +++ b/generated/rust/protos/src/union.ibc.lightclients.linea.v1.rs @@ -55,19 +55,13 @@ pub struct Header { ::core::option::Option, #[prost(message, optional, tag = "2")] pub l1_rollup_contract_proof: ::core::option::Option, - #[prost(uint64, tag = "3")] - pub l2_block_number: u64, - #[prost(message, optional, tag = "4")] + #[prost(message, optional, tag = "3")] pub l2_block_number_proof: ::core::option::Option, - #[prost(bytes = "vec", tag = "5")] - pub l2_state_root: ::prost::alloc::vec::Vec, - #[prost(message, optional, tag = "6")] + #[prost(message, optional, tag = "4")] pub l2_state_root_proof: ::core::option::Option, - #[prost(uint64, tag = "7")] - pub l2_timestamp: u64, - #[prost(message, optional, tag = "8")] + #[prost(message, optional, tag = "5")] pub l2_timestamp_proof: ::core::option::Option, - #[prost(message, optional, tag = "9")] + #[prost(message, optional, tag = "6")] pub l2_ibc_contract_proof: ::core::option::Option, } impl ::prost::Name for Header { diff --git a/lib/linea-verifier/src/verify.rs b/lib/linea-verifier/src/verify.rs index eb84353440..5e25a78d10 100644 --- a/lib/linea-verifier/src/verify.rs +++ b/lib/linea-verifier/src/verify.rs @@ -24,8 +24,6 @@ pub enum Error { InvalidL2IbcContractProof(linea_zktrie::verify::Error), #[error("the l2 ibc contract proof must be an inclusion proof, verify the address?")] L2IbcContractProofIsNotInclusion, - #[error("node value mismatch")] - ValueMismatch, } /* @@ -53,7 +51,7 @@ pub fn verify_header( verify_storage_proof( header.l1_rollup_contract_proof.storage_root, client_state.l1_rollup_current_l2_block_number_slot, - &rlp::encode(&header.l2_block_number), + &rlp::encode(&header.l2_block_number_proof.proofs[0].value), &header.l2_block_number_proof.proofs[0].proof, ) .map_err(Error::InvalidL2BlockNumberProof)?; @@ -62,7 +60,7 @@ pub fn verify_header( verify_storage_proof( header.l1_rollup_contract_proof.storage_root, client_state.l1_rollup_current_l2_timestamp_slot, - &rlp::encode(&header.l2_timestamp), + &rlp::encode(&header.l2_timestamp_proof.proofs[0].value), &header.l2_timestamp_proof.proofs[0].proof, ) .map_err(Error::InvalidL2TimestampProof)?; @@ -71,10 +69,10 @@ pub fn verify_header( verify_storage_proof( header.l1_rollup_contract_proof.storage_root, state_root_hashes_mapping_key( - client_state.l1_rollup_l2_state_root_hashes_slot, - header.l2_block_number.into(), + &client_state.l1_rollup_l2_state_root_hashes_slot, + &header.l2_block_number_proof.proofs[0].value, ), - &rlp::encode(&U256::from_be_bytes(header.l2_state_root.into())), + &rlp::encode(&header.l2_state_root_proof.proofs[0].value), &header.l2_state_root_proof.proofs[0].proof, ) .map_err(Error::InvalidL2StateRootProof)?; @@ -84,7 +82,10 @@ pub fn verify_header( &new_mimc_constants_bls12_377(), header.l2_ibc_contract_proof.leaf_index, &header.l2_ibc_contract_proof.proof, - header.l2_state_root, + header.l2_state_root_proof.proofs[0] + .value + .to_be_bytes() + .into(), client_state.l2_ibc_contract_address, ) .map_err(Error::InvalidL2IbcContractProof)?; @@ -92,7 +93,7 @@ pub fn verify_header( Ok(()) } -pub fn state_root_hashes_mapping_key(slot: U256, l2_block_number: U256) -> U256 { +pub fn state_root_hashes_mapping_key(slot: &U256, l2_block_number: &U256) -> U256 { U256::from_be_bytes( sha3::Keccak256::new() .chain_update(l2_block_number.to_be_bytes()) @@ -101,3 +102,177 @@ pub fn state_root_hashes_mapping_key(slot: U256, l2_block_number: U256) -> U256 .into(), ) } + +#[cfg(test)] +mod tests { + use hex_literal::hex; + use unionlabs::{ + ibc::{ + core::client::height::Height, + lightclients::{ + ethereum::storage_proof::StorageProof, + linea::{client_state::ClientState, header::Header}, + }, + }, + uint::U256, + }; + + use crate::{state_root_hashes_mapping_key, verify_header}; + + #[test] + fn test_state_root_hashes_slot() { + assert_eq!( + U256::from_be_bytes(hex!( + "88223631b07ce89e56b26e6825547f018d754fc79f5331cda31396a6be7d2d18" + )), + state_root_hashes_mapping_key(&0x11A.into(), &620681.into()) + ); + } + + #[test] + fn test_verify_header_for_update() { + let client_state = ClientState { + chain_id: 59144.into(), + l1_client_id: "08-wasm-0".into(), + l1_latest_height: Height { + revision_number: 0, + revision_height: 0, + }, + l1_rollup_contract_address: hex!("B218f8A4Bc926cF1cA7b3423c154a0D627Bdb7E5").into(), + l1_rollup_current_l2_timestamp_slot: 0x118.into(), + l1_rollup_current_l2_block_number_slot: 0x119.into(), + l1_rollup_l2_state_root_hashes_slot: 0x11A.into(), + l2_ibc_contract_address: hex!("5ff137d4b0fdcd49dca30c7cf57e578a026d2789").into(), + l2_ibc_contract_commitment_slot: 0.into(), + frozen_height: Height::default(), + }; + let header = Header { + l1_height: Height { + revision_number: 0, + revision_height: 4946965, // execution: 5855907 + }, + l1_rollup_contract_proof: serde_json::from_str(r#" +{ "storage_root": "0x26a5ca50671f3feb7abdd10c729ee5136358dbaf56be37707a1e7da25ba2cb34", + "proof": +[ + "0xf90211a0b11e984749d4ccd017ae0c73dcf99d86e40e8f0c540d61fd0d2ce5dd2672c1fba01ea2fac8a9b993dd5dec96242d537bf5b0889d8f316511531d639ff04827b7f5a0aefdd2554317f7d23c87dfd47b73f8009baddaf5fb114a0283c63f08a1c85129a03d6f0dc8eb7e0411d8815d14cfd577a055b5dee1fccf31d2371ec79faf8ae867a05fd28375c20b110411da39937fcbb09b8ea7f80b5b022bb24ccfac066dabce0ea0ab4560a07a8c1f853e994612ca764c33beda4464a0f39af212e638c07e04f5c0a0a9e656be287f7b04936a27bad4442f5ae947dd89f0aab9007c771805028f93b6a0cc5d3ca43597c4e80177521c2ae84f62c02afc7ee5bd5d5212b64b200908268ba044242cbc18d9b49966c1855abe98510690c51cdf1d7b9a0541ca35dad8204d22a0057439c38fb1db95c9d41e866d6a11ce9d755ade35c8d8424863ae4eabbe7d66a0dd24caf98fd107f873f6dfee85c0c41a3d76bc86da2cc8eed391f85420d86adaa0103e2876a267f87549762643b0d7381d1d8466a9f9d2aab495c84a05129478f2a0a435a3c7a7a4d84e537f6d57bd8f63c1ecb3953d4b7bda3fb2b776a16adc02bea0170168f226ac9aff7cbc895a95aca034ca50de4e9f298f934aa0349cfef283f3a06af3e817abd010e0156449ae86b5e754d2f86421b8badaf7eab99227ffe4f023a0817e202fc3cfd4539f39a61f3ba8a9a1a3ade5c30743eed1e7c589e7ef55d7dd80", + "0xf90211a0c279f69587929e075408586adcc0ef440c6c7aadfa99ea5d02bb4267e8887f8da050c123139b102649a94242b4622b76b09eb388df72360a700cde9a616ca204f7a0ee5871574c14b7bc1b98e4e809815015ebdebcf19824a32d2a1e424707a72713a01eedfd222f7fdb29262129ca7c252e9bf51c42363a35a616eae34c3eab65f675a0d6db2e197a2f3bb0c1789d6b6c98c8d1628ded03da9d55bb90c27669365a29aea0c95f1a8912300179c5004aea03f82608317bf4d8867a68d0b0b3c1e855a897b6a076340ee77efbf74432a5357e4261a9ee07e0bce0802be5fc0e3ca84f68216c19a095c7031bc4c722c5668898036943f13f006af9c3891d604339e7d1e989a07fa9a0af54a98fc1f02568277cd3e7c660e1ee8d45ede737cdaa238dd5c1362ebc03bea0696778038869da674829b3db8ae10f8653da9241daa00310ad7e6c4687cc04d4a098f56999331323727b97de1fdc52903052728e5299a3eae911ce92d0bacc9090a0325a2a5bd2c0355360dd924c8bc415364fc83cdef7e8f30ebb391c6bbb774382a04569f40a8b68708346ca2ab838cbbe9f77abf1bf81915bf8ac53a7a5f72094e7a03595e3636430b8171a9eb176e8c9785c86ce1cc3646bfb9a34a7b36380d6de40a0089b29e3dbeac038c949f34ae0ca5a4b8c6fcc28c4cced34f01ab7c045fdf5fba0f53d5d8f6b13513db96dc843d2d42626b3398eb59bc5e195a86b7022a6973d7080", + "0xf90211a0110be87dcb360c2d8eeed57453d1d28f0b18a859f00c3a1c9adef0b18cce5183a00237b5e1311c0394c98ced835efebd26466845dd263c512b1beae0372f0cfe2ba014d6e41046fcdac0f53b2b6708e21bf2699d5f3176235420c89b25928e9f2931a007dc242c02eb666f81740156314f95a11cb9d5788dee5bae25f5380d29a926bda0b250c9db115d492ef814650a188a4b34b5aa1d71f14f4b49f07ac459deb0a8f2a015ca94bdb7a0b7ea355cc4fc388fddc7aae8a00b88c0a8d5051d0eb0892af619a0cd59ac92bd17b99a9bba788609f2ea4b3c0779f6c4b74925eb33b374d82b62d7a03b94b825fb14c8f2c7f5a82899baab8aba504225a173626e801b8913f91f20eaa0f4ab579e81c17d19afb6a5065d2aea1a5ae97bc7fb311354f27c83eb874f77eea0c4966ffbf4ef4a721512140d26e063ef588a6becba36dfd433658be8f3908eb7a0be7b48cc2f317beb2698188aa6ab7fe510f9a0de2d319bcec581edab1d4408b3a0292617c565260e1367e8d3478d5ed5f9c7e2110a151b7ff90106ce05e6b1d850a00613087c563f9f63996c41f76331567cdc250f1290bf030fa4903c650583b544a069578db2bda3992ac7a6f210e27ea7a66ef03d7cd34126ad8d287df69d241827a0d94604242d20bf124624bfbeb7fac8dcfe63d05ab1d4b21e94facd99d5a0124ca065322738ae4cff82e970caea7c8aed21413fa39c9bed706b071968569508c07380", + "0xf90211a0367d55701083ad3c06e14a3265244d4dcdaa3a7a236cc7a3c539c1879e761714a0994dbc61cb0f7987987198657ce5e43f342f517cdfa34be82745dba7a74c34cca09408026912c4e8ca608c97e57db85d671e682a894f03b5e822f55e7cbe99eb91a07cabd1c6a4f6cd84a3126f8f08cd4878c72e17ad5b7d66d44a60d7148e916a18a095ec1667475fd39aab1344ddec8a67b0c50b1998290e7ff160c9ce43b84d3553a03ee889f507b105cfc28e50529678f5f0c76e0f0a96057f02c00918c6293de9b9a041ddd0c6b81605a8d2baf7dac505079b01cca0b5a50c2c95cb8d0e0170650215a0e2be93ad2924a6438cac312b761cbf65a848b48b4a5f64c69d6f3fdae8e2084da0bc51dcbb421b905ed99af111c891fe319bdb04b2377dd611a6ca3fb57e09fbc5a0b6b0630f576c0a6623de8d4d597d996611c7da72e2e2a8bbf00b69ae620335cfa09b845dfc319b8c4bdbb69bef557c9ee4d562de393e2fbaf77bd44a37924ca5eca05c1d2b6a037615d7b6a45c2f80f48e052358cbeefca40d59a8bece27398596bfa07a0c52d765e89fb0d2a91880a91d6c7780402a88a9672739e3e401fef1019512a0364de1036ba088b2f474368f46bac9ee90d56c194d4ac848d373c5ce56dfa38ca089d514fd746c9959682aedf104b7b995a04cc4410e3d541ac2967ff3f8917db0a0338de52007d4771cb77c0c5d282222e669b12f5b14ac0e3249757e605c451ea180", + "0xf90211a07ca582d2791e541b2c6f5b1c0827d004765a1a980672ccd03d24e7637c67cdfda040a762b1c9a3652905b0c7868a7c0039a0b762979cecedbb277149561f7534e4a04a014f2dff50f8f996168959f1710d32371cb75d5199621c160752080f54fd65a04f285e4e947cc23771dba016eac91ece045295b314967b53ba0ae6aae235c2e3a0a7479a4d12796bc8f9e835521d450c9a8f3aa791d27ec5da7b5ca6b66e295ee5a03f9efcf9f44daf0e04dc9f4bf63cf7487bafe4286388de436e007ddd273c86aba09f3ec2a1a5cf8eb51a9beeba044f6d960aa820a8313603e00ec0ae7c861112eca02abaae7bcd0190ce014da98b5c5823aadb8212962585492c6b6fae97e83fe29da0c4aa31957084f3d53324629fb161c08e4ab79099f88d2e04d7bafdfd8fbe8908a04d4f794b1313d7395f61eec500542267198a5543ceffcf024ee703cf8516083ba09dfb90468ddd0f817f969158ee39e0debccb02ac2dd36b0bd4eb59097f025cfca03334485d283b726a859b32470cb8ef9b165dd29426176de76199abe6ff3a7f4ba0ee47dfd208abf5090cdfe7830534c89b9e475fc3d9465a329542890035aedd5aa0791716168bcf00ac95285b6575e2cc99b246a2e75903a942170ea06e8379321fa0120138d26438214f82c74e6304ae1d2d0d9f95ad8f9107300433b1636826ed0ea0ec820e8429e67c58d3ac8c75a5a1e450c3c3b72f6b08311c3e8f405e537e229780", + "0xf901718080a0041da6a310753cad27ffab806c70ce10258fd79be61e766f9982689c5d45aad1a0c1601f7f6112727c153f9b5edae3d10b0c1fda3ca0999c2bbe5f910f72d65f56a0ff32ac535390cfdd1d26359e3e955331735de77e51bce1a4c5f1446a06e0b59ca000a3ae4514eb3a2df1b208a2391a6aba8f8a7af7b912a9e471043ede15fc9adca0e039e5b9487ef5bf0a7cb5b4408fda00d92265fa8dd3be18afec511aaa119a6780a0b93abb1775d26ec583c7c4a6910358536995c63b2512b57ef66bae3f98f93e9680a000855288f93146cc24e985085f4d007d6e926c969dfeca97661a23cce6f19283a0d7df92278762ed35c2852147cbb4698eda8bcd51626092c088106d29c198af49a011f02f2cf8f0712589bda5c2b10d0d1574241b39f8c35c7eb20692f4ff1e459180a0bc26cfffd5783ed45750605e43bd566cd995d8de50ac8addc6288943a7b8f6dda0a0aa982d31e76c874b9a9d595179d3e05a9cd630570a0d9f62e2572730af10a580", + "0xf871a010d752f6f8c9313d414365d34e5bdb2633555f7f5319e4a5e901d817467aa6e580808080a0b0101b41cb7101e2bad95ee915a8482f08ad989040056b7943d4a9434d1971b3a0d59c6f9a0a6c333465ace018d65e94bf8fe4353966513b2fabb80ae612b444c480808080808080808080", + "0xf8709d35223643911a8560f041595427e2eec8d440b6fc322943840fb0d6936db850f84e018aee3df9a928ee5878c850a026a5ca50671f3feb7abdd10c729ee5136358dbaf56be37707a1e7da25ba2cb34a04d9be648c5bf39973670d9f8b481d5d0b971e6a2db2deccc6b98cde21c5dd83e" +] +} +"#).unwrap(), + l2_timestamp_proof: StorageProof { + proofs: vec![serde_json::from_str(r#" + { + "key": "0x118", + "value": "0x663a44ff", + "proof": [ + "0xf90211a06bf01d38371674c390e78013c9fda058ea046dbe2e9cb2ce6960b39b31e09ceda07d2d75ba8b60e63033ef53f9599e177ac264ffb7872f333bc71db0c1b878decfa0a838d3057a3491f1c853a30d9ab019ca645bda6034b3d42ae7b8f4fb88ef2762a040943d7bee639787e1c50996c00fd5c556a1adbffaee0239cc24c1b71be502e0a0a23de6032143a31abb641b38c22c3c3cd3e6110348f38a73bc09088bbe7d497aa02d7096b6554a3f0dc1d867bc9987d70f2a39736266b62bcaf7cc801719b1572ea02e069fac56817d4600665a06e28180634a14ffd9b766c33a97d76e43578c0a40a032b0f4f958cc4493b525b6c479e9d5b3d0478a92b04376524e89ade502d8a1f3a044c75bcc0f7f21ba9d44b12416daaa21ef6f59312542c6587cca41f969c77214a09941064b101954a994f0e7df150f8a8deab44a30ebdb0164eb34e83e0130ba36a0241c793cbc66f18b1239e3753308bbd05f7703a11dfeb0e74a32d12f97e9a533a0d2b3a889dfb1fc25c2c0603749b901aac9e0158287e4071af3664903bf441270a030972bdbdc64a6089fd8a13293f8182921b9a1e39ee982eeabd1276b0ce17e29a06971c693d075b25d9686dd670bc0d62cbc2747eacba65f3b9c460e09f386d376a00179730a469043728be984e460d4348f92e59c494b2cae8f62fa887dea5e5c84a090c4c1b31dc8eb2193aa882822151ea4ef47854e4e9076c9b72e6b7fd459fef380", + "0xf90211a089ab735456e60f4475e87ed9b7db93557460dc6adce5b7b7f6ad69d69439b4d8a03545efdc5d50c3b712c91f8f9964af394a139baf2df4d33c0189ed0c5288a772a02fd7428b00e88857acb7b6ef1f537a6333513c6219d49de278f59fabbbfb2a81a038225cd8362fce6228cf894c343edd4905c9e3c4e9923c89f3de4fefe3af4c83a06e304aa57733d2bf2d40390b2c8c64a15b61275008add2bba3fab9f1fe585ac3a0faf3afc37ac7122a7b38b7b4e60f45b4b75a2eea0d51e143f15fed32aae9ce2da05c3e905a073bea2e6da0610bcded5fb3f068ab782c1b02976435ebb1aa1344fea083b7d1bf89365462551bd1f99b30d59b0e326c01a976436feda1deb9386c588ba0d15289e41f3bb3304a88df448d3789efeefd251b062c0272568eff8dd545800da0d3b606f95238a86a9010a25292891271695c697192e4527bf38c0627e3076031a032081178a8d5efe82876d9c75290a5896748b37222c3308440c71b660cb97386a061f95630acfed3dde77007f780d363a255964b0a8834902329a461b17bc712a1a047bf7361759b28635366f5ad2e9dc6d2116ea9470314a6a222c613e7be06cbc3a094daeee28a1f4bd29a05ffd2fc27c9e0d81207896b6b8c8dfcc80ab52c4f6a20a05b759cc27042e6ae7b93c6c24da1ceb4c21b86bd88262b45e2e5ca55af2c0b75a057f2896ccba7ee6ff374c437ff5889956e87fbced65cb6f32b157b6bc57d100a80", + "0xf901b180a01ee013723244fb3a0abe6990c0980ab4f76fd14bc85988c94b8a3a21517a570ea036d37b2e4f73ceadabdf80be5c1375e2c02dca5ef80a30c453fd2da301904d96a03c397fca525135a356765ab94fe45213ff3fd7233a10ad3cd2283949ca839765a08bfa0a753ffef7d73c3a7ca845c8162d11401bc61d1319f72115b762df070338a0e7ceaf1f47bb91a4f2e65aaabbfca2ae49f6c7a022e401e4527adb6e93a7d12ca041bc96608042f4cb2760fa48ac49460e40b14c147743c98790471464586235a4a02fcf6ecd3d005ae7d5dc32310aebd42e796a07f74f2241d3ae2ea512250513098080a0a08f97037a687da400f93378c867c4d5f8e92c3c75eb14d508aceed7f1c663eba0e6161cd08bca312d2ce1898e6f2900054307d2352344f5e4f829be4ea3c001a1a05bfc2f7d4025981389d2cb08418f8c0bd05c77b649c77bbfa5d84c2415a9d4b7a08e4367033feee0b42d5d130205eb648441e0ab381757be302e4bb92ae5836b7ea067c1a588cc7a45bc4615887100e209f5f81d28b01d7366413aa722eb1842d528a0840d08ff81fe1ba507942cdaba945c98dd3b6abad7258c22c0e91474fa8ef1ed80", + "0xf85180a05d70523347f1c1595d2be200fb34baa9921173218a1586fd73b6152bd56060138080808080a065734055c6762d4c50777cc0f480d41a480339ba87301bce2110b29025e6c06e808080808080808080", + "0xe69f20deb247cc158d01ecf635e1727d4c1ad65177ed51d3c365b299b8a5e12e248584663a44ff" + ] + } +"#).unwrap() ] + }, + l2_block_number_proof: StorageProof { proofs: vec![serde_json::from_str(r#" +{ + "key": "0x119", + "value": "0x97889", + "proof": [ + "0xf90211a06bf01d38371674c390e78013c9fda058ea046dbe2e9cb2ce6960b39b31e09ceda07d2d75ba8b60e63033ef53f9599e177ac264ffb7872f333bc71db0c1b878decfa0a838d3057a3491f1c853a30d9ab019ca645bda6034b3d42ae7b8f4fb88ef2762a040943d7bee639787e1c50996c00fd5c556a1adbffaee0239cc24c1b71be502e0a0a23de6032143a31abb641b38c22c3c3cd3e6110348f38a73bc09088bbe7d497aa02d7096b6554a3f0dc1d867bc9987d70f2a39736266b62bcaf7cc801719b1572ea02e069fac56817d4600665a06e28180634a14ffd9b766c33a97d76e43578c0a40a032b0f4f958cc4493b525b6c479e9d5b3d0478a92b04376524e89ade502d8a1f3a044c75bcc0f7f21ba9d44b12416daaa21ef6f59312542c6587cca41f969c77214a09941064b101954a994f0e7df150f8a8deab44a30ebdb0164eb34e83e0130ba36a0241c793cbc66f18b1239e3753308bbd05f7703a11dfeb0e74a32d12f97e9a533a0d2b3a889dfb1fc25c2c0603749b901aac9e0158287e4071af3664903bf441270a030972bdbdc64a6089fd8a13293f8182921b9a1e39ee982eeabd1276b0ce17e29a06971c693d075b25d9686dd670bc0d62cbc2747eacba65f3b9c460e09f386d376a00179730a469043728be984e460d4348f92e59c494b2cae8f62fa887dea5e5c84a090c4c1b31dc8eb2193aa882822151ea4ef47854e4e9076c9b72e6b7fd459fef380", + "0xf90211a06e5dba711aefd578363a9ac6aee4afdf81408cab66be4d4faf533ddc12318e81a026e73fedba365b5ae575c12522ce6cc0ed3738d23dff488cb8685f834a2e5988a0f2699f0c4948954f47cba71bb49a4ce1741ffa524f8307db16ae854f4da7366ba009d94b8e05351eb893db1042d0c7b296c9cd868d9a66a0333873cc302c830163a0a998eeb9b9a2e6e240ff56e6576b717860e54a23ea1392d499321dddf56c3911a04ba0b2de22b692e2cdc492fc6f110be0a2231597469dcafa459619f888f4ca2fa07e72b76f6348005bbb7ef4646f45b2406d681c011f060fe445813025aa791570a06fb27e4ce4304c70b60e6169adc718e3ddd581b539d073ae7b1a87598d9ebdeca0efb05a9ff794077f6331b44ecfbc52f283d40dc722d314c60462e206f5d5c5e9a068b5ad36f5d4576a8fe64f8582993914045ad85d109c4ddc75ab9a43c93942efa0de2e2bf4a86537681d19c2dce8abdfea970f6bd522da5869f2a00234556fcfc2a0be41f0517b378ea2bd1c6b756550e552cc0d2b3b9c88d6a9929b874571fd9140a0af56419aa347dda35c5bab7aa90a7a79d0c0aece0cc1b2605068805e39fe2db0a087210615efb7f7bcb1f0503e51ac91814647bd9824115c48c9954f572bd2c9cda0e25b667f9f6b5f2adbfc2e8fb663f57630ec266485230037ae4f67016f85e9eaa096e82e854f880308aa570683c9118b4faf862feaec037039320df7c12d51a74480", + "0xf90191a09d6f2414cab5d257d3b60573131bd32080c6f7dd8d9e497a05ffe97d7407fde3a0efa7a1cd52783cb6efb8e31fca4ff738638d9f06fd5969586b3717feb79d5b29a0ba902d490d534a954f14bffe063005c13791ec71e8121347ab4b6afa62030517a05ef6b6ea93979aac5bc7e3d9af38673533e31f831c0811cf435c670cb90748a2a04d45ba907e527c4a47cfd60eaf0706148175717e70a5bcf073ee8b4222554765a035501b458f5617e8d43c159cbfae78d7afce9862db2a1b894f29cd997553ff8ea09da5fd230d0e7d479dc4f790b61a34dd1fc6df34fdfcfa18d81a25d11e6472158080a0c908c6530c2a5a8c21e086655ef777e2b8d354bf1545c5496b66eb134cb1a694a0e9dd6a55f2f25fc8f049e94183b436d2dc19821a033f1f802c17b2f428269680a0ea5b9312ed76cc2f9674481299140cb4677c4652998e89a5a4a02766ec6d2638a00e0f73b31600408c573d7d6c34f56ddb4d0c0f1c7c500a037d29318333ad876680a03b05bd8acb4cda93f7af23de99a1d2b5ceff1c45937dd6256d272fcce325bcf98080", + "0xf85180808080808080a0553ac3ce806a18d3843296a9e20adb0cd9754e0a8294d19e0b367fe5d3ccbc28a04ebb7b65bee39683047a9426b0b5184e22c90a09b590da93ec91f04feceff4088080808080808080", + "0xe59f201a1c59257d882f21f6b09f2a6b260448d35f58469939b33d8124d4e43de18483097889" + ] +} +"#).unwrap()] }, + l2_state_root_proof: StorageProof { + proofs: vec![ + serde_json::from_str(r#" +{ + "key": "0x88223631b07ce89e56b26e6825547f018d754fc79f5331cda31396a6be7d2d18", + "value": "0xc76548458cc04a5aa09bffa092b32c912aee635c1c44364ebb911286a10263d", + "proof": [ + "0xf90211a06bf01d38371674c390e78013c9fda058ea046dbe2e9cb2ce6960b39b31e09ceda07d2d75ba8b60e63033ef53f9599e177ac264ffb7872f333bc71db0c1b878decfa0a838d3057a3491f1c853a30d9ab019ca645bda6034b3d42ae7b8f4fb88ef2762a040943d7bee639787e1c50996c00fd5c556a1adbffaee0239cc24c1b71be502e0a0a23de6032143a31abb641b38c22c3c3cd3e6110348f38a73bc09088bbe7d497aa02d7096b6554a3f0dc1d867bc9987d70f2a39736266b62bcaf7cc801719b1572ea02e069fac56817d4600665a06e28180634a14ffd9b766c33a97d76e43578c0a40a032b0f4f958cc4493b525b6c479e9d5b3d0478a92b04376524e89ade502d8a1f3a044c75bcc0f7f21ba9d44b12416daaa21ef6f59312542c6587cca41f969c77214a09941064b101954a994f0e7df150f8a8deab44a30ebdb0164eb34e83e0130ba36a0241c793cbc66f18b1239e3753308bbd05f7703a11dfeb0e74a32d12f97e9a533a0d2b3a889dfb1fc25c2c0603749b901aac9e0158287e4071af3664903bf441270a030972bdbdc64a6089fd8a13293f8182921b9a1e39ee982eeabd1276b0ce17e29a06971c693d075b25d9686dd670bc0d62cbc2747eacba65f3b9c460e09f386d376a00179730a469043728be984e460d4348f92e59c494b2cae8f62fa887dea5e5c84a090c4c1b31dc8eb2193aa882822151ea4ef47854e4e9076c9b72e6b7fd459fef380", + "0xf90211a06e5dba711aefd578363a9ac6aee4afdf81408cab66be4d4faf533ddc12318e81a026e73fedba365b5ae575c12522ce6cc0ed3738d23dff488cb8685f834a2e5988a0f2699f0c4948954f47cba71bb49a4ce1741ffa524f8307db16ae854f4da7366ba009d94b8e05351eb893db1042d0c7b296c9cd868d9a66a0333873cc302c830163a0a998eeb9b9a2e6e240ff56e6576b717860e54a23ea1392d499321dddf56c3911a04ba0b2de22b692e2cdc492fc6f110be0a2231597469dcafa459619f888f4ca2fa07e72b76f6348005bbb7ef4646f45b2406d681c011f060fe445813025aa791570a06fb27e4ce4304c70b60e6169adc718e3ddd581b539d073ae7b1a87598d9ebdeca0efb05a9ff794077f6331b44ecfbc52f283d40dc722d314c60462e206f5d5c5e9a068b5ad36f5d4576a8fe64f8582993914045ad85d109c4ddc75ab9a43c93942efa0de2e2bf4a86537681d19c2dce8abdfea970f6bd522da5869f2a00234556fcfc2a0be41f0517b378ea2bd1c6b756550e552cc0d2b3b9c88d6a9929b874571fd9140a0af56419aa347dda35c5bab7aa90a7a79d0c0aece0cc1b2605068805e39fe2db0a087210615efb7f7bcb1f0503e51ac91814647bd9824115c48c9954f572bd2c9cda0e25b667f9f6b5f2adbfc2e8fb663f57630ec266485230037ae4f67016f85e9eaa096e82e854f880308aa570683c9118b4faf862feaec037039320df7c12d51a74480", + "0xf901b1a02cfbec8dcde4f856780bbf714a7c43c6861adc60eaa34fdcaf037110bbcce51080a0b4fdc18df9506854a2bd324195e2aa727a9c44ff4dbebef7075e2de678e8bec1a04e77bac714fa0b88d2511404cd4c1b15274ee47f1be037bda1cb149732df853ba057346b926ba66b7cb8ed9c9b5edaebebda535d5d022de0e72ea0db377e5780b8a0990801aa1e6b66bb0be3c9470d725d77bd67e6d71a24483783fea6fae3b40f17a0bab9ce9cf2b97c5203f57bdbd0d7090db637160346b42e9aabeb345fd42b04d3a00a4fa4432cfb8b6b41ece4ea07d7401dec5897cb7929278749a6d79c84d1aad880a09dd78a91e3ba790f08f03f00b5de94908ed4d5949fa8351bd38ab99814f374ffa04e1bfca4c175178002cad1c648df8131d4432cf100e5e780cd499e388183c8f080a063475a0150e7d86f30318dfe1573b2b3f65bbe7a7473b11a3874f747042af390a0a20e809c9d1623a39332af33557d797b64dd629a87ee916d8f39515009dbad16a085c10e3aa70b22c7e1ef3e7db13ceb11ed98b4ef20988dbb3b13a1db6325e9a3a0d3f65a6a973adbfd7f4bf96b3c41d2bd39af84c99cf87e812db8db22d2390e2e80", + "0xf871a0c2356c0aa042be156f6257cbc1f83ad9a26f21d379e97c4ef3690ad4f85e18ac8080a006b3d22451ef66415bd75c97b806ada453cb35a88e3edfca995425e8d925841f8080808080a0003d8298a040a91fb79d5a2d30da0f288720ebb83a3154dca33dbc0a2d823a3680808080808080", + "0xf8429f208c6e48a1cab8aedea718bdd632b319f4a1810dfda510f0ba1824e1dab1e7a1a00c76548458cc04a5aa09bffa092b32c912aee635c1c44364ebb911286a10263d" + ] +} +"#).unwrap() + ] + }, + l2_ibc_contract_proof: serde_json::from_str(r#" +{ + "key": "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789", + "leafIndex": 65362, + "proof": { + "proofRelatedNodes": [ + "0x00000000000000000000000000000000000000000000000000000000000120fe0393507c456718a986386c7923fe68b87c29d83ac7f7ce1cdb49afc7e66a4771", + "0x008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809008a47a2a53dd5183a2dc127c399a004e2a6c7e60f73e104d7d79e6a2bd7e809", + "0x060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b060f08aed06ffb90efc9705dc38d37a7000da1add99cef1b8a84b9e72e7c8b7b", + "0x0a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef322550a06dc31ae8e893bca0a076decb8c0caa9036b5f394abf79d7956411eef32255", + "0x01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d01f35ef342eaa841ee4306d38f2a1adeafe8967d23c31fe1a379b9a69353da6d", + "0x090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484090d53176fd185da729d0d68e0c0e646ef148f15864685f4ba56be7b7cbb2484", + "0x11c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f30611c8e229e3e2ae40a4959e036d500753aaedb52cda67d9caf60f0629f0b4f306", + "0x07f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d4807f048ac696418580a55a864a10ed030871fd615d5ab460c54d6184c16441d48", + "0x0f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd80f5dc218160db17cfe8044d7ac4fd55dfcbdf2676815e2c15388f189bf144cd8", + "0x0cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e27390cdf7d06a4b4b0e71713048f5f6ea86016467e909a27bfeeeca67b56c17e2739", + "0x014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b014030b5cbe31660da2d33b6b1265b82bbde9a7ab7f331f8b274f2b798a45a3b", + "0x11c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca55411c8aeb3dc3ca059a29ba20d4471b20987d74a0d79ff8ecda247df6a02eca554", + "0x1092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c221092d1b2349c4fbc88ea0202cf88685e4e316c99697063f786201b27d46e2c22", + "0x0969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a890969f4e85b86f0eb36ad13dfb1f35346d7d6518308dc27e73452c649850f1a89", + "0x079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368079081f446c9a0c7b404834742cea1909426ccfc4696d19e1a08531b0cc30368", + "0x004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458004d50e626bda007887a31f60883e58bce50a1a3e7a3384b9ec18dab319dd458", + "0x0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd0b2ae68e3af633dac72090cc9c9b0dce76cebf5117101a265f54b3b9a851b3cd", + "0x0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c0b7a8a9fe0ee619c9bd7ff504dcb47bdce0193546b53a79dedd5251f4f56f36c", + "0x0defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea80defe934a1ae079cf6ec6022145b60128eeb30503eea4404da990fc2b2430ea8", + "0x0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b0e42718d49cb8c4be515181eda51f41d3b8198af5a2139a4670a8ee06b904a2b", + "0x1276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f51276c046afd611be02a66cf85498d7210a15293357afe07968a86c89356662f5", + "0x02a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b202a9fd706c3c223f9374481b7495fb775c1675407556d93f1edabfe54b3fc9b2", + "0x070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978070382f72e9f322433fb44fc4acfefd74b277b19b6cc1784379e7ca7338a2978", + "0x0133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed10133209cd7936e208da6b743428ff7195e8ef92d3dac72472146ac7497355ed1", + "0x09cbd26c486bc2217bce59337120283f655a7ba65075f98059249f471812d0480b03678742039acaae14fd3964e2d6261b74410043c536f07bcf1bc4495d9f84", + "0x0c5c4d122720c4d6e7866d9b6bc6171c6259be90095976b665406ccf2dc6a8950305d7ebd7da4f82f061632eb7ec0c3060f51af848661d479bb64003f0fc5342", + "0x0c4762f6af9f09a529e70f0b34b7afafe2bba8944eccdcdb95cf13e0ff00ab2209d8b650f132967dba1764abe34c3d446311503ba7712d5f474a6e159b085b5f", + "0x0c3474a51e2654aca28b15add106ac676d92b9416ee788ac0b88873e77a009660176016fa85f1ba2375f784c72fae85763e12018e3e781c306f97ad9f826a22e", + "0x108459110262f154aef2d43fce77d314a7ec867f0068987716ff51582847e498009a6be6c408befa4eb7e6141fd427a2ce6489d50bf5f9de6bde9e100ada3482", + "0x118d3c53f9a3ea556029e867af93e9b4450cbacdf4dff29859e399ae16468e5102cefeff18d2980c8a9253c4609506472ba4764ea99efa6324dacf34740d9f05", + "0x005d88c799974510f99c04afdaba0f6b8f62edd55d8d89910009e148385a72c30a8fac91e2023660e8ac50ff082578361ba0901b16fe691f9b78044cbf6d1c4b", + "0x106f788c7d5990bec78f6c9cadd15604c99a8f1d56c875d324bb5ece63d83f3606694c69c43303aa1c614d60ed8fc66838f368b134cfc1ab00b6c83b2b5b3c8c", + "0x0ba8fdb8888982dde981f8e2cc9177c8c3ce0607661e113604e436951776de9c0b9ec8fec4b0696c73e04fd6bee4aa345633d23ef0c6bc4e4bbcf757af2677f0", + "0x0f5ae90881ea3398fd1a14fb83babc2335dfc4e6298aada1d827042d67dea48f0dee0a62e8ff86baddb091105d845c862089fe2f1963cd3798d636035da4d518", + "0x03d41bdb96726bf7f745784e42eef043c8b797f788d9720e36e460502e14c9fb0923e0e0228d2fe8619e30581e3e225d4e99e0daa011e15ac34c28fa30ea2989", + "0x11335bc4bf8a15d8c116cbdfe74242e80c7f60ac1a614d00f99fb9e1148126930502f7b7740708503e3858bc6df707cf4a1a751bcef3f2a5eb6eff9d8efa5cf8", + "0x0d60d90907794deaabe1e532a128e17ac94ec30339f3e367bc9ecd0aa40fd8b6009f71be21f99f29acd62b42787c99e5192646f808306fff0960ef5cd9a5ac16", + "0x0a6fd861ba25def420f5503fbbc4e0de2e54b4fbf0b22364e4a188eaf72ac58c02e49a2a28faca35409f471b4d981951aeabba2f091a427a2e88c53d1c7eeed3", + "0x0a93ecccd90368342584da9a8623e89a7a71d36f1da58d9874d50c045587138b0476d671e749bd2cd45fe416e1409caa22863f8cebdf926920a9f68b150d92d7", + "0x0821de61351452c22cf6bdafcd85be9a8cb3c2ad0af51f871d44221575785f9d12200803e31923cc68d6c9b906876643688e3a7ccb21264f933028b060564e4d", + "0x0000000000000000000000000000000000000000000000000000000000001c1e000000000000000000000000000000000000000000000000000000000000a354000036e661469dd70081ada16334d16a4049a124e261cd93def5fff88f85afda01b285fb7d6e0c7e05505a348777221f3c9fb491bbbbea4853e62e93f415efe7", + "0x000000000000000000000000000000000000000000000000000000000000894100000000000000000000000000000000000000000000000000000000000002db104a10331d6a854148a10b11c19cf2abae0412c9909ecefca54adc135ee57a950481fe75941093272afb1f8f76353afad6b89b1c19c383b07730c6f160b59243" + ], + "value": "0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000017d7498da306d2911280c3481d8b1510e16062ffaa631812c3ca53639329c1577354f0e4cd850dde1255c33de5e8c499e72ca1f49352847124c0dbfc30d0374d4d5d5e7cddb83c7ac93c806e738300b5357ecdc2e971d6438d34d8e4e17b99b758b1f9cac91c8e700000000000000000000000000000000000000000000000000000000000005c89" + } + } +"#).unwrap(), + }; + + assert_eq!( + verify_header( + client_state, + header, + hex!("4fa153a281bbf4c6d9667c8717d33a680c0849475f28acb9ead55dfa0e797e83").into() + ), + Ok(()) + ); + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs b/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs index 69bc6df877..6d1f4a43ab 100644 --- a/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/linea/client_state.rs @@ -21,8 +21,8 @@ pub struct ClientState { pub l1_client_id: String, pub l1_latest_height: Height, pub l1_rollup_contract_address: H160, - pub l1_rollup_current_l2_block_number_slot: U256, pub l1_rollup_current_l2_timestamp_slot: U256, + pub l1_rollup_current_l2_block_number_slot: U256, pub l1_rollup_l2_state_root_hashes_slot: U256, pub l2_ibc_contract_address: H160, pub l2_ibc_contract_commitment_slot: U256, diff --git a/lib/unionlabs/src/ibc/lightclients/linea/header.rs b/lib/unionlabs/src/ibc/lightclients/linea/header.rs index 113e537df7..21527aab6c 100644 --- a/lib/unionlabs/src/ibc/lightclients/linea/header.rs +++ b/lib/unionlabs/src/ibc/lightclients/linea/header.rs @@ -2,7 +2,6 @@ use macros::model; use crate::{ errors::{required, InvalidLength, MissingField}, - hash::H256, ibc::{ core::client::height::Height, lightclients::ethereum::{ @@ -17,12 +16,9 @@ use crate::{ pub struct Header { pub l1_height: Height, pub l1_rollup_contract_proof: AccountProof, - pub l2_block_number: u64, + pub l2_timestamp_proof: StorageProof, pub l2_block_number_proof: StorageProof, - pub l2_state_root: H256, pub l2_state_root_proof: StorageProof, - pub l2_timestamp: u64, - pub l2_timestamp_proof: StorageProof, pub l2_ibc_contract_proof: InclusionProof, } @@ -31,12 +27,9 @@ impl From
for protos::union::ibc::lightclients::linea::v1::Header { Self { l1_height: Some(value.l1_height.into()), l1_rollup_contract_proof: Some(value.l1_rollup_contract_proof.into()), - l2_block_number: value.l2_block_number, + l2_timestamp_proof: Some(value.l2_timestamp_proof.into()), l2_block_number_proof: Some(value.l2_block_number_proof.into()), - l2_state_root: value.l2_state_root.into(), l2_state_root_proof: Some(value.l2_state_root_proof.into()), - l2_timestamp: value.l2_timestamp, - l2_timestamp_proof: Some(value.l2_timestamp_proof.into()), l2_ibc_contract_proof: Some(value.l2_ibc_contract_proof.into()), } } @@ -66,21 +59,15 @@ impl TryFrom for Header { l1_rollup_contract_proof: required!(value.l1_rollup_contract_proof)? .try_into() .map_err(TryFromHeaderError::L1RollupContractProof)?, - l2_block_number: value.l2_block_number, + l2_timestamp_proof: required!(value.l2_timestamp_proof)? + .try_into() + .map_err(TryFromHeaderError::L2TimestampProof)?, l2_block_number_proof: required!(value.l2_block_number_proof)? .try_into() .map_err(TryFromHeaderError::L2BlockNumberProof)?, - l2_state_root: value - .l2_state_root - .try_into() - .map_err(TryFromHeaderError::L2StateRoot)?, l2_state_root_proof: required!(value.l2_state_root_proof)? .try_into() .map_err(TryFromHeaderError::L2StateRootProof)?, - l2_timestamp: value.l2_timestamp, - l2_timestamp_proof: required!(value.l2_timestamp_proof)? - .try_into() - .map_err(TryFromHeaderError::L2TimestampProof)?, l2_ibc_contract_proof: required!(value.l2_ibc_contract_proof)? .try_into() .map_err(TryFromHeaderError::L2IbcContractProof)?, diff --git a/light-clients/linea-light-client/src/client.rs b/light-clients/linea-light-client/src/client.rs index b3644746ff..41283b54e0 100644 --- a/light-clients/linea-light-client/src/client.rs +++ b/light-clients/linea-light-client/src/client.rs @@ -162,7 +162,8 @@ impl IbcClient for LineaLightClient { data: ConsensusState { ibc_storage_root: zk_account.storage_root, // must be nanos - timestamp: 1_000_000_000 * header.l2_timestamp, + timestamp: 1_000_000_000 + * u64::try_from(header.l2_timestamp_proof.proofs[0].value).expect("impossible"), }, }; save_consensus_state::(deps, consensus_state, &updated_height); diff --git a/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto index f95a1410d0..21f800cb6d 100644 --- a/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto +++ b/uniond/proto/union/ibc/lightclients/linea/v1/linea.proto @@ -26,13 +26,10 @@ message ConsensusState { message Header { .ibc.core.client.v1.Height l1_height = 1; .union.ibc.lightclients.ethereum.v1.AccountProof l1_rollup_contract_proof = 2; - uint64 l2_block_number = 3; - .union.ibc.lightclients.ethereum.v1.StorageProof l2_block_number_proof = 4; - bytes l2_state_root = 5; - .union.ibc.lightclients.ethereum.v1.StorageProof l2_state_root_proof = 6; - uint64 l2_timestamp = 7; - .union.ibc.lightclients.ethereum.v1.StorageProof l2_timestamp_proof = 8; - InclusionProof l2_ibc_contract_proof = 9; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_block_number_proof = 3; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_state_root_proof = 4; + .union.ibc.lightclients.ethereum.v1.StorageProof l2_timestamp_proof = 5; + InclusionProof l2_ibc_contract_proof = 6; } message MerklePath { From 33329b04b631f4d4d8baddc4a798711d0ba58856 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 13 May 2024 20:55:04 +0200 Subject: [PATCH 7/8] fix(linea): unused error variant --- lib/linea-verifier/src/verify.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/linea-verifier/src/verify.rs b/lib/linea-verifier/src/verify.rs index 5e25a78d10..df5e928aae 100644 --- a/lib/linea-verifier/src/verify.rs +++ b/lib/linea-verifier/src/verify.rs @@ -22,8 +22,6 @@ pub enum Error { InvalidL2StateRootProof(ethereum_verifier::Error), #[error("invalid l2 ibc contract proof {0}")] InvalidL2IbcContractProof(linea_zktrie::verify::Error), - #[error("the l2 ibc contract proof must be an inclusion proof, verify the address?")] - L2IbcContractProofIsNotInclusion, } /* From 7cdff1eaaf7165cb413606df7d35a43981b592bb Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Mon, 13 May 2024 20:55:21 +0200 Subject: [PATCH 8/8] feat(linea): more type safe and robust node pathing --- lib/linea-zktrie/src/node.rs | 40 ++++++++++++++++++++++++---------- lib/linea-zktrie/src/verify.rs | 33 +++++++++++++--------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/lib/linea-zktrie/src/node.rs b/lib/linea-zktrie/src/node.rs index 36b6529d69..f2da154183 100644 --- a/lib/linea-zktrie/src/node.rs +++ b/lib/linea-zktrie/src/node.rs @@ -7,21 +7,39 @@ use unionlabs::{ }; // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L27 -pub const SUB_TRIE_ROOT_PATH: [u8; 1] = [1]; +pub const SUB_TRIE_ROOT_PATH: Direction = Direction::Right; -// https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/LeafType.java#L21-L24 -pub const LEAF_TYPE_VALUE: u8 = 0x16; +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Terminator { + // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/node/LeafType.java#L21-L24 + Value, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Direction { + Left, + Right, + Terminator(Terminator), +} // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathGenerator.java#L34 -pub fn bytes_to_leaf_path(bytes: &[u8], terminator_path: u8) -> Vec { - let mut path = vec![0u8; bytes.len() * 2 + 1]; +pub fn bytes_to_leaf_path(bytes: &[u8], terminator_path: Terminator) -> Vec { + let mut path = vec![Direction::Left; bytes.len() * 2 + 1]; let mut j = 0; for b in bytes.iter().skip(j) { - path[j] = b >> 4 & 15; - path[j + 1] = b & 15; + path[j] = if b >> 4 & 15 == 1 { + Direction::Right + } else { + Direction::Left + }; + path[j + 1] = if b & 15 == 1 { + Direction::Right + } else { + Direction::Left + }; j += 2; } - path[j] = terminator_path; + path[j] = Direction::Terminator(terminator_path); path } @@ -32,12 +50,12 @@ pub fn node_index_to_bytes(trie_depth: usize, node_index: u64) -> Vec { } // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/path/PathResolver.java#L71 -pub fn get_leaf_path(trie_depth: usize, node_index: u64) -> Vec { +pub fn get_leaf_path(trie_depth: usize, node_index: u64) -> Vec { [ - SUB_TRIE_ROOT_PATH.as_ref(), + [SUB_TRIE_ROOT_PATH].as_ref(), &bytes_to_leaf_path( &node_index_to_bytes(trie_depth, node_index), - LEAF_TYPE_VALUE, + Terminator::Value, ), ] .concat() diff --git a/lib/linea-zktrie/src/verify.rs b/lib/linea-zktrie/src/verify.rs index b10b5580e1..9da91654bb 100644 --- a/lib/linea-zktrie/src/verify.rs +++ b/lib/linea-zktrie/src/verify.rs @@ -10,10 +10,7 @@ use unionlabs::{ uint::U256, }; -use crate::node::{get_leaf_path, BranchNode, LeafNode, Node, RootNode}; - -pub const DIRECTION_LEFT: u8 = 0; -pub const DIRECTION_RIGHT: u8 = 1; +use crate::node::{get_leaf_path, BranchNode, Direction, LeafNode, Node, RootNode, Terminator}; // https://github.com/Consensys/shomei/blob/955b4d8100f1a12702cdefc3fa79b16dd1c038e6/trie/src/main/java/net/consensys/shomei/trie/ZKTrie.java#L64C1-L65C47 pub const ZK_TRIE_DEPTH: usize = 40; @@ -78,8 +75,8 @@ impl ZkValue for H256 { #[derive(Clone, Debug, PartialEq, thiserror::Error)] pub enum Error { - #[error("invalid direction {0}")] - InvalidDirection(u8), + #[error("invalid direction {0:?}")] + InvalidDirection(Direction), #[error("missing root node")] MissingRoot, #[error("missing leaf node")] @@ -136,8 +133,8 @@ impl TryFrom<&MerklePath> for VerifiablePath { .ok_or(Error::MissingRoot)?, )?; let leaf = LeafNode::decode(value.proof_related_nodes.last().ok_or(Error::MissingLeaf)?)?; - // Minus root/leaf - let inner_path_len = value.proof_related_nodes.len() - 2; + // Skip the root as we manually check against it + let inner_path_len = value.proof_related_nodes.len() - 1; let path = value .proof_related_nodes .iter() @@ -273,25 +270,25 @@ pub fn verify_inclusion( }); } // The algorithm we use is slightly more explicit, we actually extract both - // the root and leaf so the inner path must exclude both root leaf + // the root so the inner path must exclude it // Minus root/leaf - let inner_path_len = leaf_path.len() - 2; - let inner_path = leaf_path.into_iter().skip(1).take(inner_path_len); - let leaf_hash = verifiable_path.leaf.hash(constants)?; + let inner_path = leaf_path.into_iter().skip(1); + let hash = |x| mimc_sum_bl12377(constants, x); // Starts with the leaf hash, then recursively walk back to the tip of the // tree. let subtrie_root = verifiable_path.path.iter().zip(inner_path).rev().try_fold( - leaf_hash, + H256::default(), |current_hash, (node, direction)| -> Result { let node_hash = node.hash(constants)?; - let to_hash = match direction { + let next_hash = match direction { // We went on the left branch, we hash against the right sibling - DIRECTION_LEFT => [current_hash.as_ref(), node_hash.as_ref()].concat(), + Direction::Left => hash([current_hash.as_ref(), node_hash.as_ref()].concat())?, // We went on the right branch, we hash against the left sibling - DIRECTION_RIGHT => [node_hash.as_ref(), current_hash.as_ref()].concat(), - d => return Err(Error::InvalidDirection(d)), + Direction::Right => hash([node_hash.as_ref(), current_hash.as_ref()].concat())?, + // We are at the leaf level, we start with it's hash + Direction::Terminator(Terminator::Value) => node_hash, }; - Ok(mimc_sum_bl12377(constants, to_hash)?) + Ok(next_hash) }, )?; // Verify the subtrie root