From 59efc3ed6899ddf87bd5458a7712fa6c4fecb07e Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Thu, 9 Jan 2025 13:57:53 -0800 Subject: [PATCH 01/10] imp: bulk move ics23-related codebase to newly defined ics23 package --- cairo-libs/Scarb.lock | 9 ++ cairo-libs/Scarb.toml | 5 + cairo-libs/packages/cometbft/Scarb.toml | 3 +- cairo-libs/packages/cometbft/src/lib.cairo | 1 - .../packages/cometbft/src/light_client.cairo | 2 +- cairo-libs/packages/ics23/Scarb.toml | 27 ++++ cairo-libs/packages/ics23/src/errors.cairo | 1 + cairo-libs/packages/ics23/src/lib.cairo | 8 + .../src/ics23.cairo => ics23/src/types.cairo} | 142 +++++++++++++++++- 9 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 cairo-libs/packages/ics23/Scarb.toml create mode 100644 cairo-libs/packages/ics23/src/errors.cairo create mode 100644 cairo-libs/packages/ics23/src/lib.cairo rename cairo-libs/packages/{cometbft/src/ics23.cairo => ics23/src/types.cairo} (61%) diff --git a/cairo-libs/Scarb.lock b/cairo-libs/Scarb.lock index 4c614967..8e8e2540 100644 --- a/cairo-libs/Scarb.lock +++ b/cairo-libs/Scarb.lock @@ -4,6 +4,15 @@ version = 1 [[package]] name = "cometbft" version = "0.1.0" +dependencies = [ + "ics23", + "protobuf", + "snforge_std", +] + +[[package]] +name = "ics23" +version = "0.1.0" dependencies = [ "protobuf", "snforge_std", diff --git a/cairo-libs/Scarb.toml b/cairo-libs/Scarb.toml index 756d10ae..4c12c00a 100644 --- a/cairo-libs/Scarb.toml +++ b/cairo-libs/Scarb.toml @@ -3,6 +3,7 @@ members = [ "packages/protobuf", "packages/cometbft", "packages/serde_json", + "packages/ics23", ] [workspace.package] @@ -27,5 +28,9 @@ cairo_test = "2.8.4" starknet = "2.8.4" snforge_std = "0.31.0" +# internal dependencies +protobuf = { path = "packages/protobuf" } +ics23 = { path = "packages/ics23" } + [workspace.tool.fmt] sort-module-level-items = true diff --git a/cairo-libs/packages/cometbft/Scarb.toml b/cairo-libs/packages/cometbft/Scarb.toml index df4a55a6..b46f8fa8 100644 --- a/cairo-libs/packages/cometbft/Scarb.toml +++ b/cairo-libs/packages/cometbft/Scarb.toml @@ -4,7 +4,8 @@ version = "0.1.0" edition = { workspace = true } [dependencies] -protobuf = { path = "../protobuf" } +protobuf = { workspace = true } +ics23 = { workspace = true } [dev-dependencies] cairo_test = { workspace = true } diff --git a/cairo-libs/packages/cometbft/src/lib.cairo b/cairo-libs/packages/cometbft/src/lib.cairo index 283c04b9..f2a523cc 100644 --- a/cairo-libs/packages/cometbft/src/lib.cairo +++ b/cairo-libs/packages/cometbft/src/lib.cairo @@ -2,7 +2,6 @@ pub mod light_client; pub mod utils; pub mod types; pub mod ibc; -pub mod ics23; #[cfg(test)] mod tests; diff --git a/cairo-libs/packages/cometbft/src/light_client.cairo b/cairo-libs/packages/cometbft/src/light_client.cairo index cfa21969..f866bc3f 100644 --- a/cairo-libs/packages/cometbft/src/light_client.cairo +++ b/cairo-libs/packages/cometbft/src/light_client.cairo @@ -1,5 +1,5 @@ use cometbft::ibc::{Height, MerkleRoot}; -use cometbft::ics23::ProofSpec; +use ics23::ProofSpec; use protobuf::types::wkt::{Timestamp, Duration}; use cometbft::utils::Fraction; use cometbft::types::{SignedHeader, ValidatorSet}; diff --git a/cairo-libs/packages/ics23/Scarb.toml b/cairo-libs/packages/ics23/Scarb.toml new file mode 100644 index 00000000..3540a348 --- /dev/null +++ b/cairo-libs/packages/ics23/Scarb.toml @@ -0,0 +1,27 @@ +[package] +name = "ics23" +version = { workspace = true } +edition = { workspace = true } +cairo-version = { workspace = true } +scarb-version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +license = { workspace = true } +Readme = { workspace = true } +keywords = { workspace = true } + +description = """ + Contains the implementation of ICS-23 vector commitments in Cairo. +""" + +[lib] + +[scripts] +test = { workspace = true } + +[dependencies] +protobuf = { workspace = true } + +[dev-dependencies] +cairo_test = { workspace = true } +snforge_std = { workspace = true } diff --git a/cairo-libs/packages/ics23/src/errors.cairo b/cairo-libs/packages/ics23/src/errors.cairo new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/cairo-libs/packages/ics23/src/errors.cairo @@ -0,0 +1 @@ + diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo new file mode 100644 index 00000000..02185ef2 --- /dev/null +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -0,0 +1,8 @@ +mod types; +mod errors; + +pub use types::{ + MerkleProof, MerkleProofImpl, MerkleProofTrait, Proof, ExistenceProof, ExistenceProofImpl, + ExistenceProofTrait, NonExistenceProof, InnerOp, LeafOp, ProofSpec +}; +pub use errors::ICS23Errors; diff --git a/cairo-libs/packages/cometbft/src/ics23.cairo b/cairo-libs/packages/ics23/src/types.cairo similarity index 61% rename from cairo-libs/packages/cometbft/src/ics23.cairo rename to cairo-libs/packages/ics23/src/types.cairo index 68c89a73..f7cf61aa 100644 --- a/cairo-libs/packages/cometbft/src/ics23.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -6,6 +6,145 @@ use protobuf::primitives::array::{ByteArrayAsProtoMessage}; use protobuf::primitives::numeric::{UnsignedAsProtoMessage, I32AsProtoMessage, BoolAsProtoMessage}; use protobuf::types::tag::WireType; +#[derive(Default, Debug, Drop, PartialEq, Serde)] +pub struct MerkleProof { + pub proofs: Array, +} + +#[generate_trait] +pub impl MerkleProofImpl of MerkleProofTrait { + fn verify_membership( + self: @MerkleProof, + spec: ProofSpec, + root: RootBytes, + keys: Array, + value: Array + ) {} + + fn verify_non_membership( + self: @MerkleProof, spec: ProofSpec, root: RootBytes, keys: Array + ) {} +} + +/// Contains nested proof types within a commitment proof. It currently supports +/// existence and non-existence proofs to meet the core requirements of IBC. Batch +/// and compressed proofs can be added in the future if necessary. +#[derive(Default, Debug, Drop, PartialEq, Serde)] +pub enum Proof { + #[default] + Exist: ExistenceProof, + NonExist: NonExistenceProof, +} + +#[derive(Default, Debug, Drop, PartialEq, Serde)] +pub struct ExistenceProof { + pub key: Array, + pub value: Array, + pub leaf: Array, + pub path: Array, +} + +#[generate_trait] +pub impl ExistenceProofImpl of ExistenceProofTrait { + fn calculate_existence_root(self: @ExistenceProof) -> RootBytes { + self.calculate_existence_root_for_spec(Option::None) + } + + fn calculate_existence_root_for_spec( + self: @ExistenceProof, spec: Option + ) -> RootBytes { + [0; 8] + } +} + +impl ExistenceProofAsProtoMessage of ProtoMessage { + fn encode_raw(self: @ExistenceProof, ref context: EncodeContext) { + context.encode_repeated_field(1, self.key); + context.encode_repeated_field(2, self.value); + context.encode_repeated_field(3, self.leaf); + context.encode_repeated_field(4, self.path); + } + + fn decode_raw(ref self: ExistenceProof, ref context: DecodeContext) { + context.decode_repeated_field(1, ref self.key); + context.decode_repeated_field(2, ref self.value); + context.decode_repeated_field(3, ref self.leaf); + context.decode_repeated_field(4, ref self.path); + } + + fn wire_type() -> WireType { + WireType::LengthDelimited + } +} + +impl ExistenceProofAsProtoName of ProtoName { + fn type_url() -> ByteArray { + "ExistenceProof" + } +} + +#[derive(Default, Debug, Drop, PartialEq, Serde)] +pub struct NonExistenceProof { + pub key: Array, + pub left: ExistenceProof, + pub right: ExistenceProof, +} + +impl NonExistenceProofAsProtoMessage of ProtoMessage { + fn encode_raw(self: @NonExistenceProof, ref context: EncodeContext) { + context.encode_repeated_field(1, self.key); + context.encode_field(2, self.left); + context.encode_field(3, self.right); + } + + fn decode_raw(ref self: NonExistenceProof, ref context: DecodeContext) { + context.decode_repeated_field(1, ref self.key); + context.decode_field(2, ref self.left); + context.decode_field(3, ref self.right); + } + + fn wire_type() -> WireType { + WireType::LengthDelimited + } +} + +impl NonExistenceProofAsProtoName of ProtoName { + fn type_url() -> ByteArray { + "NonExistenceProof" + } +} + +#[derive(Default, Debug, Drop, PartialEq, Serde)] +pub struct InnerOp { + pub hash: HashOp, + pub prefix: Array, + pub suffix: Array, +} + +impl InnerOpAsProtoMessage of ProtoMessage { + fn encode_raw(self: @InnerOp, ref context: EncodeContext) { + context.encode_field(1, self.hash); + context.encode_repeated_field(2, self.prefix); + context.encode_repeated_field(3, self.suffix); + } + + fn decode_raw(ref self: InnerOp, ref context: DecodeContext) { + context.decode_field(1, ref self.hash); + context.decode_repeated_field(2, ref self.prefix); + context.decode_repeated_field(3, ref self.suffix); + } + + fn wire_type() -> WireType { + WireType::LengthDelimited + } +} + +impl InnerOpAsProtoName of ProtoName { + fn type_url() -> ByteArray { + "InnerOp" + } +} + #[derive(Default, Debug, Copy, Drop, PartialEq, Serde)] pub enum HashOp { #[default] @@ -56,7 +195,6 @@ impl U64IntoHashOp of Into { } } - #[derive(Default, Debug, Copy, Drop, PartialEq, Serde)] pub enum LengthOp { #[default] @@ -217,3 +355,5 @@ impl ProofSpecAsProtoName of ProtoName { "ProofSpec" } } + +pub type RootBytes = [u32; 8]; From 016eec4ddfa4005d11e4d168000230e7e34256bc Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Fri, 10 Jan 2025 20:02:57 -0800 Subject: [PATCH 02/10] feat: install types, methods and ops (1st iter) --- cairo-libs/Scarb.lock | 50 +++++ cairo-libs/Scarb.toml | 9 +- cairo-libs/packages/ics23/Scarb.toml | 3 +- cairo-libs/packages/ics23/src/errors.cairo | 16 +- cairo-libs/packages/ics23/src/lib.cairo | 6 +- cairo-libs/packages/ics23/src/ops.cairo | 57 ++++++ cairo-libs/packages/ics23/src/types.cairo | 220 ++++++++++++++------- cairo-libs/packages/ics23/src/utils.cairo | 39 ++++ 8 files changed, 326 insertions(+), 74 deletions(-) create mode 100644 cairo-libs/packages/ics23/src/ops.cairo create mode 100644 cairo-libs/packages/ics23/src/utils.cairo diff --git a/cairo-libs/Scarb.lock b/cairo-libs/Scarb.lock index 8e8e2540..8cc2a4ed 100644 --- a/cairo-libs/Scarb.lock +++ b/cairo-libs/Scarb.lock @@ -1,6 +1,55 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "alexandria_bytes" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_data_structures", + "alexandria_math", +] + +[[package]] +name = "alexandria_data_structures" +version = "0.2.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_encoding", +] + +[[package]] +name = "alexandria_encoding" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_bytes", + "alexandria_math", + "alexandria_numeric", +] + +[[package]] +name = "alexandria_math" +version = "0.2.1" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" + +[[package]] +name = "alexandria_numeric" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_math", + "alexandria_searching", +] + +[[package]] +name = "alexandria_searching" +version = "0.1.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=95d98a5#95d98a5182001d07673b856a356eff0e6bd05354" +dependencies = [ + "alexandria_data_structures", +] + [[package]] name = "cometbft" version = "0.1.0" @@ -14,6 +63,7 @@ dependencies = [ name = "ics23" version = "0.1.0" dependencies = [ + "alexandria_numeric", "protobuf", "snforge_std", ] diff --git a/cairo-libs/Scarb.toml b/cairo-libs/Scarb.toml index 4c12c00a..9345a6cf 100644 --- a/cairo-libs/Scarb.toml +++ b/cairo-libs/Scarb.toml @@ -23,10 +23,11 @@ test = "snforge test" [workspace.dependencies] # external dependencies -assert_macros = "2.8.4" -cairo_test = "2.8.4" -starknet = "2.8.4" -snforge_std = "0.31.0" +alexandria_numeric = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "95d98a5" } +assert_macros = "2.8.4" +cairo_test = "2.8.4" +starknet = "2.8.4" +snforge_std = "0.31.0" # internal dependencies protobuf = { path = "packages/protobuf" } diff --git a/cairo-libs/packages/ics23/Scarb.toml b/cairo-libs/packages/ics23/Scarb.toml index 3540a348..2ec42895 100644 --- a/cairo-libs/packages/ics23/Scarb.toml +++ b/cairo-libs/packages/ics23/Scarb.toml @@ -20,7 +20,8 @@ description = """ test = { workspace = true } [dependencies] -protobuf = { workspace = true } +alexandria_numeric = { workspace = true } +protobuf = { workspace = true } [dev-dependencies] cairo_test = { workspace = true } diff --git a/cairo-libs/packages/ics23/src/errors.cairo b/cairo-libs/packages/ics23/src/errors.cairo index 8b137891..45f9a863 100644 --- a/cairo-libs/packages/ics23/src/errors.cairo +++ b/cairo-libs/packages/ics23/src/errors.cairo @@ -1 +1,15 @@ - +pub mod ICS23Errors { + pub const MISSING_MERKLE_PROOF: felt252 = 'ICS23: missing merkle proof'; + pub const MISSING_KEY: felt252 = 'ICS23: missing key'; + pub const MISSING_VALUE: felt252 = 'ICS23: missing value'; + pub const MISSING_CHILD_HASH: felt252 = 'ICS23: missing child hash'; + pub const MISMATCHED_KEY: felt252 = 'ICS23: mismatched key'; + pub const MISMATCHED_VALUE: felt252 = 'ICS23: mismatched value'; + pub const MISMATCHED_ROOT: felt252 = 'ICS23: mismatched root'; + pub const MISMATCHED_NUM_OF_PROOFS: felt252 = 'ICS23: mismatched num of proofs'; + pub const INVALID_MERKLE_PROOF: felt252 = 'ICS23: invalid merkle proof'; + pub const INVALID_PROOF_TYPE: felt252 = 'ICS23: invalid proof type'; + pub const INVALID_DEPTH_RANGE: felt252 = 'ICS23: invalid depth range'; + pub const UNSUPPORTED_HASH_OP: felt252 = 'ICS23: unsupported hash op'; + pub const ZERO_MERKLE_ROOT: felt252 = 'ICS23: zero merkle root'; +} diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index 02185ef2..b7f916f4 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -1,8 +1,12 @@ mod types; mod errors; +mod utils; +mod ops; pub use types::{ MerkleProof, MerkleProofImpl, MerkleProofTrait, Proof, ExistenceProof, ExistenceProofImpl, - ExistenceProofTrait, NonExistenceProof, InnerOp, LeafOp, ProofSpec + ExistenceProofTrait, NonExistenceProof, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp }; pub use errors::ICS23Errors; +pub use utils::{array_u8_into_array_u32, array_u32_into_array_u8}; +pub use ops::{apply_inner, apply_leaf, calc_length}; diff --git a/cairo-libs/packages/ics23/src/ops.cairo b/cairo-libs/packages/ics23/src/ops.cairo new file mode 100644 index 00000000..b3efbff9 --- /dev/null +++ b/cairo-libs/packages/ics23/src/ops.cairo @@ -0,0 +1,57 @@ +use core::sha256::{compute_sha256_u32_array, compute_sha256_byte_array}; +use ics23::{ + InnerOp, LeafOp, HashOp, ICS23Errors, array_u8_into_array_u32, array_u32_into_array_u8, LengthOp +}; + +pub fn apply_inner(inner: @InnerOp, child: [u32; 8]) -> [u32; 8] { + assert(inner.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); + assert(child != [0; 8], ICS23Errors::MISSING_CHILD_HASH); + let mut data: Array = ArrayTrait::new(); + data.append_span(inner.prefix.span()); + let child_as_u32_array: Array = child.span().into(); + data.append_span(array_u32_into_array_u8(child_as_u32_array).span()); + data.append_span(inner.suffix.span()); + let (bytes, last_word, last_word_len) = array_u8_into_array_u32(data); + compute_sha256_u32_array(bytes, last_word, last_word_len) +} + +pub fn apply_leaf(leaf_op: @LeafOp, key: @ByteArray, value: Array) -> [u32; 8] { + assert(leaf_op.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); + assert(key.len() > 0, ICS23Errors::MISSING_KEY); + assert(value.len() > 0, ICS23Errors::MISSING_VALUE); + let (mut data, last_word, last_word_len) = array_u8_into_array_u32(leaf_op.prefix.clone()); + let prekey = prepare_leaf_byte_array_data(leaf_op.prehash_key, leaf_op.length, key); + data.append_span(prekey.span()); + let preval = prepare_leaf_u32_array_data(leaf_op.prehash_value, leaf_op.length, value); + data.append_span(preval.span()); + compute_sha256_u32_array(data, last_word, last_word_len) +} + +pub fn prepare_leaf_u32_array_data( + prehash: @HashOp, length: @LengthOp, data: Array +) -> Array { + assert(data.len() > 0, ICS23Errors::MISSING_VALUE); + assert(prehash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); + let h = compute_sha256_u32_array(data, 0, 0); + calc_length(length, h.span().into()) +} + +pub fn prepare_leaf_byte_array_data( + prehash: @HashOp, length: @LengthOp, data: @ByteArray +) -> Array { + assert(data.len() > 0, ICS23Errors::MISSING_VALUE); + assert(prehash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); + let h = compute_sha256_byte_array(data); + calc_length(length, h.span().into()) +} + +pub fn calc_length(length_op: @LengthOp, data: Array) -> Array { + match length_op { + LengthOp::NoPrefix => data, + LengthOp::VarProto => { + let mut data = data; + data.append(data.len()); + data + } + } +} diff --git a/cairo-libs/packages/ics23/src/types.cairo b/cairo-libs/packages/ics23/src/types.cairo index f7cf61aa..64a33128 100644 --- a/cairo-libs/packages/ics23/src/types.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -5,6 +5,7 @@ use protobuf::types::message::{ use protobuf::primitives::array::{ByteArrayAsProtoMessage}; use protobuf::primitives::numeric::{UnsignedAsProtoMessage, I32AsProtoMessage, BoolAsProtoMessage}; use protobuf::types::tag::WireType; +use ics23::{ICS23Errors, apply_inner, apply_leaf}; #[derive(Default, Debug, Drop, PartialEq, Serde)] pub struct MerkleProof { @@ -15,15 +16,59 @@ pub struct MerkleProof { pub impl MerkleProofImpl of MerkleProofTrait { fn verify_membership( self: @MerkleProof, - spec: ProofSpec, + specs: ProofSpecs, root: RootBytes, keys: Array, - value: Array - ) {} + value: Array, + ) { + let proofs_len = self.proofs.len(); + assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); + assert(root == [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); + assert(value.len() > 0, ICS23Errors::MISSING_VALUE); + assert(proofs_len == specs.specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + let mut subroot = [0; 8]; + let mut subvalue: Array = ArrayTrait::new(); + let mut i = 0; + while i < proofs_len { + match self.proofs[i] { + Proof::Exist(p) => { + subroot = p.calculate_root(); + p.verify(specs.specs[i], @subroot, keys[proofs_len - 1 - i], @value); + }, + _ => panic!("{}", ICS23Errors::INVALID_PROOF_TYPE), + } + subvalue = subroot.span().into(); + i += 1; + }; + assert(root == subroot, ICS23Errors::INVALID_MERKLE_PROOF); + } fn verify_non_membership( - self: @MerkleProof, spec: ProofSpec, root: RootBytes, keys: Array - ) {} + self: @MerkleProof, specs: ProofSpecs, root: RootBytes, keys: Array + ) { + let proofs_len = self.proofs.len(); + assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); + assert(root == [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); + assert(proofs_len == specs.specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + let mut subroot = [0; 8]; + let mut i = 0; + while i < proofs_len { + match self.proofs[i] { + Proof::NonExist(p) => { + subroot = p.calculate_root(); + p.verify(specs.specs[i], @subroot, keys[proofs_len - i]); + self + .verify_membership( + specs.clone(), root, keys.clone(), subroot.span().into() + ) // TODO: add start_index + }, + _ => panic!("{}", ICS23Errors::INVALID_PROOF_TYPE), + } + i += 1; + }; + } } /// Contains nested proof types within a commitment proof. It currently supports @@ -38,37 +83,54 @@ pub enum Proof { #[derive(Default, Debug, Drop, PartialEq, Serde)] pub struct ExistenceProof { - pub key: Array, - pub value: Array, - pub leaf: Array, + pub key: ByteArray, + pub value: Array, + pub leaf: LeafOp, pub path: Array, } #[generate_trait] pub impl ExistenceProofImpl of ExistenceProofTrait { - fn calculate_existence_root(self: @ExistenceProof) -> RootBytes { - self.calculate_existence_root_for_spec(Option::None) + fn calculate_root(self: @ExistenceProof) -> RootBytes { + self.calculate_root_for_spec(Option::None) } - fn calculate_existence_root_for_spec( - self: @ExistenceProof, spec: Option - ) -> RootBytes { - [0; 8] + fn calculate_root_for_spec(self: @ExistenceProof, spec: Option<@ProofSpec>) -> RootBytes { + assert(self.key.len() > 0, ICS23Errors::MISSING_KEY); + assert(self.value.len() > 0, ICS23Errors::MISSING_VALUE); + let mut hash = apply_leaf(self.leaf, self.key, self.value.clone()); + for i in 0..self.path.len() { + hash = apply_inner(self.path[i], hash); + }; + hash + } + + fn verify( + self: @ExistenceProof, + spec: @ProofSpec, + root: @RootBytes, + key: @ByteArray, + value: @Array, + ) { + assert(self.key == key, ICS23Errors::MISMATCHED_KEY); + assert(self.value == value, ICS23Errors::MISMATCHED_VALUE); + let calc = self.calculate_root_for_spec(Option::Some(spec)); + assert(@calc == root, ICS23Errors::MISMATCHED_ROOT) } } impl ExistenceProofAsProtoMessage of ProtoMessage { fn encode_raw(self: @ExistenceProof, ref context: EncodeContext) { - context.encode_repeated_field(1, self.key); + context.encode_field(1, self.key); context.encode_repeated_field(2, self.value); - context.encode_repeated_field(3, self.leaf); + context.encode_field(3, self.leaf); context.encode_repeated_field(4, self.path); } fn decode_raw(ref self: ExistenceProof, ref context: DecodeContext) { - context.decode_repeated_field(1, ref self.key); + context.decode_field(1, ref self.key); context.decode_repeated_field(2, ref self.value); - context.decode_repeated_field(3, ref self.leaf); + context.decode_field(3, ref self.leaf); context.decode_repeated_field(4, ref self.path); } @@ -90,6 +152,19 @@ pub struct NonExistenceProof { pub right: ExistenceProof, } +#[generate_trait] +pub impl NonExistenceProofImpl of NonExistenceProofTrait { + fn calculate_root(self: @NonExistenceProof) -> RootBytes { + self.calculate_root_for_spec(Option::None) + } + + fn calculate_root_for_spec(self: @NonExistenceProof, spec: Option) -> RootBytes { + [0; 8] + } + + fn verify(self: @NonExistenceProof, spec: @ProofSpec, root: @RootBytes, key: @ByteArray) {} +} + impl NonExistenceProofAsProtoMessage of ProtoMessage { fn encode_raw(self: @NonExistenceProof, ref context: EncodeContext) { context.encode_repeated_field(1, self.key); @@ -150,14 +225,6 @@ pub enum HashOp { #[default] NoOp, Sha256, - Sha512, - Keccak256, - Ripemd160, - Bitcoin, - Sha512_256, - Blake2b_512, - Blake2b_256, - Blake3, } impl HashOpIntoU64 of Into { @@ -165,14 +232,6 @@ impl HashOpIntoU64 of Into { match self { HashOp::NoOp => 0, HashOp::Sha256 => 1, - HashOp::Sha512 => 2, - HashOp::Keccak256 => 3, - HashOp::Ripemd160 => 4, - HashOp::Bitcoin => 5, - HashOp::Sha512_256 => 6, - HashOp::Blake2b_512 => 7, - HashOp::Blake2b_256 => 8, - HashOp::Blake3 => 9, } } } @@ -182,14 +241,6 @@ impl U64IntoHashOp of Into { match self { 0 => HashOp::NoOp, 1 => HashOp::Sha256, - 2 => HashOp::Sha512, - 3 => HashOp::Keccak256, - 4 => HashOp::Ripemd160, - 5 => HashOp::Bitcoin, - 6 => HashOp::Sha512_256, - 7 => HashOp::Blake2b_512, - 8 => HashOp::Blake2b_256, - 9 => HashOp::Blake3, _ => panic!("invalid HashOp"), } } @@ -200,13 +251,6 @@ pub enum LengthOp { #[default] NoPrefix, VarProto, - VarRlp, - Fixed32Big, - Fixed32Little, - Fixed64Big, - Fixed64Little, - Require32Bytes, - Require64Bytes, } impl LengthOpIntoU64 of Into { @@ -214,13 +258,6 @@ impl LengthOpIntoU64 of Into { match self { LengthOp::NoPrefix => 0, LengthOp::VarProto => 1, - LengthOp::VarRlp => 2, - LengthOp::Fixed32Big => 3, - LengthOp::Fixed32Little => 4, - LengthOp::Fixed64Big => 5, - LengthOp::Fixed64Little => 6, - LengthOp::Require32Bytes => 7, - LengthOp::Require64Bytes => 8, } } } @@ -230,13 +267,6 @@ impl U64IntoLengthOp of Into { match self { 0 => LengthOp::NoPrefix, 1 => LengthOp::VarProto, - 2 => LengthOp::VarRlp, - 3 => LengthOp::Fixed32Big, - 4 => LengthOp::Fixed32Little, - 5 => LengthOp::Fixed64Big, - 6 => LengthOp::Fixed64Little, - 7 => LengthOp::Require32Bytes, - 8 => LengthOp::Require64Bytes, _ => panic!("invalid length op"), } } @@ -248,7 +278,7 @@ pub struct InnerSpec { pub child_size: i32, pub min_prefix_length: i32, pub max_prefix_length: i32, - pub empty_child: ByteArray, + pub empty_child: ByteArray, // TODO: determine the correct type! pub hash: HashOp, } @@ -288,7 +318,7 @@ pub struct LeafOp { pub prehash_key: HashOp, pub prehash_value: HashOp, pub length: LengthOp, - pub prefix: ByteArray, + pub prefix: Array, } impl LeafOpAsProtoMessage of ProtoMessage { @@ -297,7 +327,7 @@ impl LeafOpAsProtoMessage of ProtoMessage { context.encode_field(2, self.prehash_key); context.encode_field(3, self.prehash_value); context.encode_field(4, self.length); - context.encode_field(5, self.prefix); + context.encode_repeated_field(5, self.prefix); } fn decode_raw(ref self: LeafOp, ref context: DecodeContext) { @@ -305,7 +335,7 @@ impl LeafOpAsProtoMessage of ProtoMessage { context.decode_field(2, ref self.prehash_key); context.decode_field(3, ref self.prehash_value); context.decode_field(4, ref self.length); - context.decode_field(5, ref self.prefix); + context.decode_repeated_field(5, ref self.prefix); } fn wire_type() -> WireType { @@ -319,6 +349,11 @@ impl LeafOpAsProtoName of ProtoName { } } +#[derive(Default, Debug, Clone, Drop, PartialEq, Serde)] +pub struct ProofSpecs { + specs: Array, +} + #[derive(Default, Debug, Clone, Drop, PartialEq, Serde)] pub struct ProofSpec { pub leaf_spec: LeafOp, @@ -328,6 +363,57 @@ pub struct ProofSpec { pub prehash_key_before_comparison: bool, } +#[generate_trait] +pub impl ProofSpecImpl of ProofSpecTrait { + fn iavl() -> ProofSpec { + let leaf_spec = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::Sha256, + length: LengthOp::VarProto, + prefix: array![0], + }; + let inner_spec = InnerSpec { + child_order: array![0, 1], + min_prefix_length: 4, + max_prefix_length: 12, + child_size: 33, + empty_child: "", + hash: HashOp::Sha256, + }; + ProofSpec { + leaf_spec, inner_spec, min_depth: 0, max_depth: 0, prehash_key_before_comparison: false + } + } + + fn tendermint() -> ProofSpec { + let leaf_spec = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::Sha256, + length: LengthOp::VarProto, + prefix: array![0], + }; + let inner_spec = InnerSpec { + child_order: array![0, 1], + min_prefix_length: 1, + max_prefix_length: 1, + child_size: 32, + empty_child: "", + hash: HashOp::Sha256, + }; + ProofSpec { + leaf_spec, inner_spec, min_depth: 0, max_depth: 0, prehash_key_before_comparison: false + } + } + + fn validate(self: @ProofSpec) { + assert(self.max_depth < @0, ICS23Errors::INVALID_DEPTH_RANGE); + assert(self.min_depth < @0, ICS23Errors::INVALID_DEPTH_RANGE); + assert(self.max_depth > self.min_depth, ICS23Errors::INVALID_DEPTH_RANGE); + } +} + impl ProofSpecAsProtoMessage of ProtoMessage { fn encode_raw(self: @ProofSpec, ref context: EncodeContext) { context.encode_field(1, self.leaf_spec); diff --git a/cairo-libs/packages/ics23/src/utils.cairo b/cairo-libs/packages/ics23/src/utils.cairo new file mode 100644 index 00000000..7db11890 --- /dev/null +++ b/cairo-libs/packages/ics23/src/utils.cairo @@ -0,0 +1,39 @@ +use alexandria_numeric::integers::UIntBytes; + +pub fn array_u8_into_array_u32(input: Array) -> (Array, u32, u32) { + let mut result: Array = ArrayTrait::new(); + let mut last_word: u32 = 0; + let mut last_word_len: u32 = 0; + + let mut i: usize = 0; + while i < input.len() { + let mut value: u32 = 0; + let mut j: usize = 0; + while j < 4 { + if i + j >= input.len() { + break; + }; + value *= 0x100; + value = value + (*input.at(i + j)).into(); + j += 1; + }; + if j % 4 == 0 { + result.append(value); + } else { + last_word = value; + last_word_len = j.try_into().unwrap(); + } + i += 4; + }; + + (result, last_word, last_word_len) +} + +pub fn array_u32_into_array_u8(input: Array) -> Array { + let mut result: Array = ArrayTrait::new(); + for i in input { + let a = i.to_bytes(); + result.append_span(a); + }; + result +} From 67792cd94909daed8db9fb30613060785a73b9e7 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Wed, 22 Jan 2025 16:58:24 -0800 Subject: [PATCH 03/10] feat: complete apply_leaf implementation --- cairo-libs/Scarb.lock | 1 + cairo-libs/Scarb.toml | 1 + cairo-libs/packages/ics23/Scarb.toml | 5 +- cairo-libs/packages/ics23/src/errors.cairo | 1 + cairo-libs/packages/ics23/src/lib.cairo | 11 ++- cairo-libs/packages/ics23/src/ops.cairo | 87 +++++++++++++------ cairo-libs/packages/ics23/src/tests/ops.cairo | 42 +++++++++ cairo-libs/packages/ics23/src/types.cairo | 43 +++++---- cairo-libs/packages/ics23/src/utils.cairo | 35 ++++++++ 9 files changed, 180 insertions(+), 46 deletions(-) create mode 100644 cairo-libs/packages/ics23/src/tests/ops.cairo diff --git a/cairo-libs/Scarb.lock b/cairo-libs/Scarb.lock index 8cc2a4ed..477d0155 100644 --- a/cairo-libs/Scarb.lock +++ b/cairo-libs/Scarb.lock @@ -63,6 +63,7 @@ dependencies = [ name = "ics23" version = "0.1.0" dependencies = [ + "alexandria_math", "alexandria_numeric", "protobuf", "snforge_std", diff --git a/cairo-libs/Scarb.toml b/cairo-libs/Scarb.toml index 9345a6cf..1faf8926 100644 --- a/cairo-libs/Scarb.toml +++ b/cairo-libs/Scarb.toml @@ -24,6 +24,7 @@ test = "snforge test" [workspace.dependencies] # external dependencies alexandria_numeric = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "95d98a5" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "95d98a5" } assert_macros = "2.8.4" cairo_test = "2.8.4" starknet = "2.8.4" diff --git a/cairo-libs/packages/ics23/Scarb.toml b/cairo-libs/packages/ics23/Scarb.toml index 2ec42895..3e75ce60 100644 --- a/cairo-libs/packages/ics23/Scarb.toml +++ b/cairo-libs/packages/ics23/Scarb.toml @@ -24,5 +24,6 @@ alexandria_numeric = { workspace = true } protobuf = { workspace = true } [dev-dependencies] -cairo_test = { workspace = true } -snforge_std = { workspace = true } +alexandria_math = { workspace = true } +cairo_test = { workspace = true } +snforge_std = { workspace = true } diff --git a/cairo-libs/packages/ics23/src/errors.cairo b/cairo-libs/packages/ics23/src/errors.cairo index 45f9a863..ef2a57d2 100644 --- a/cairo-libs/packages/ics23/src/errors.cairo +++ b/cairo-libs/packages/ics23/src/errors.cairo @@ -9,6 +9,7 @@ pub mod ICS23Errors { pub const MISMATCHED_NUM_OF_PROOFS: felt252 = 'ICS23: mismatched num of proofs'; pub const INVALID_MERKLE_PROOF: felt252 = 'ICS23: invalid merkle proof'; pub const INVALID_PROOF_TYPE: felt252 = 'ICS23: invalid proof type'; + pub const INVALID_INNER_SPEC: felt252 = 'ICS23: invalid inner spec'; pub const INVALID_DEPTH_RANGE: felt252 = 'ICS23: invalid depth range'; pub const UNSUPPORTED_HASH_OP: felt252 = 'ICS23: unsupported hash op'; pub const ZERO_MERKLE_ROOT: felt252 = 'ICS23: zero merkle root'; diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index b7f916f4..23f453a1 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -2,11 +2,18 @@ mod types; mod errors; mod utils; mod ops; +#[cfg(test)] +mod tests { + mod ops; +} pub use types::{ MerkleProof, MerkleProofImpl, MerkleProofTrait, Proof, ExistenceProof, ExistenceProofImpl, ExistenceProofTrait, NonExistenceProof, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp }; pub use errors::ICS23Errors; -pub use utils::{array_u8_into_array_u32, array_u32_into_array_u8}; -pub use ops::{apply_inner, apply_leaf, calc_length}; +pub use utils::{ + array_u8_into_array_u32, array_u32_into_array_u8, byte_array_to_array_u8, ArrayU32IntoArrayU8, + SliceU32IntoArrayU32, IntoArrayU32, +}; +pub(crate) use ops::{apply_inner, apply_leaf, proto_len}; diff --git a/cairo-libs/packages/ics23/src/ops.cairo b/cairo-libs/packages/ics23/src/ops.cairo index b3efbff9..b1b17711 100644 --- a/cairo-libs/packages/ics23/src/ops.cairo +++ b/cairo-libs/packages/ics23/src/ops.cairo @@ -1,57 +1,92 @@ use core::sha256::{compute_sha256_u32_array, compute_sha256_byte_array}; use ics23::{ - InnerOp, LeafOp, HashOp, ICS23Errors, array_u8_into_array_u32, array_u32_into_array_u8, LengthOp + InnerOp, LeafOp, HashOp, ICS23Errors, byte_array_to_array_u8, LengthOp, ArrayU32IntoArrayU8, + SliceU32IntoArrayU32, IntoArrayU32, }; pub fn apply_inner(inner: @InnerOp, child: [u32; 8]) -> [u32; 8] { + // Sanity checks assert(inner.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); assert(child != [0; 8], ICS23Errors::MISSING_CHILD_HASH); + + // Construct the data let mut data: Array = ArrayTrait::new(); data.append_span(inner.prefix.span()); - let child_as_u32_array: Array = child.span().into(); - data.append_span(array_u32_into_array_u8(child_as_u32_array).span()); + let u8_child_array: Array = child.into(); + data.append_span(u8_child_array.span()); data.append_span(inner.suffix.span()); - let (bytes, last_word, last_word_len) = array_u8_into_array_u32(data); + + // Compute the hash + let (bytes, last_word, last_word_len) = data.into_array_u32(); compute_sha256_u32_array(bytes, last_word, last_word_len) } pub fn apply_leaf(leaf_op: @LeafOp, key: @ByteArray, value: Array) -> [u32; 8] { + // Sanity check assert(leaf_op.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); - assert(key.len() > 0, ICS23Errors::MISSING_KEY); - assert(value.len() > 0, ICS23Errors::MISSING_VALUE); - let (mut data, last_word, last_word_len) = array_u8_into_array_u32(leaf_op.prefix.clone()); - let prekey = prepare_leaf_byte_array_data(leaf_op.prehash_key, leaf_op.length, key); + + // Construct the data + let mut data: Array = ArrayTrait::new(); + data.append_span(leaf_op.prefix.span()); + let prekey = prepare_leaf_byte_array(leaf_op.prehash_key, leaf_op.length, key); data.append_span(prekey.span()); - let preval = prepare_leaf_u32_array_data(leaf_op.prehash_value, leaf_op.length, value); + let preval = prepare_leaf_u32_array(leaf_op.prehash_value, leaf_op.length, value); data.append_span(preval.span()); - compute_sha256_u32_array(data, last_word, last_word_len) + + // Compute the hash + let (bytes, last_word, last_word_len) = data.into_array_u32(); + compute_sha256_u32_array(bytes, last_word, last_word_len) } -pub fn prepare_leaf_u32_array_data( - prehash: @HashOp, length: @LengthOp, data: Array -) -> Array { +pub fn prepare_leaf_u32_array(prehash: @HashOp, length: @LengthOp, data: Array) -> Array { assert(data.len() > 0, ICS23Errors::MISSING_VALUE); - assert(prehash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); - let h = compute_sha256_u32_array(data, 0, 0); - calc_length(length, h.span().into()) + do_length(length, hash_u32_array(prehash, data)) } -pub fn prepare_leaf_byte_array_data( - prehash: @HashOp, length: @LengthOp, data: @ByteArray -) -> Array { - assert(data.len() > 0, ICS23Errors::MISSING_VALUE); - assert(prehash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); - let h = compute_sha256_byte_array(data); - calc_length(length, h.span().into()) +pub fn prepare_leaf_byte_array(prehash: @HashOp, length: @LengthOp, data: @ByteArray) -> Array { + assert(data.len() > 0, ICS23Errors::MISSING_KEY); + do_length(length, hash_byte_array(prehash, data)) } -pub fn calc_length(length_op: @LengthOp, data: Array) -> Array { +pub fn hash_u32_array(hash_op: @HashOp, data: Array) -> Array { + match hash_op { + HashOp::NoOp => data.into(), + HashOp::Sha256 => { compute_sha256_u32_array(data, 0, 0).into() } + } +} + +pub fn hash_byte_array(hash_op: @HashOp, data: @ByteArray) -> Array { + match hash_op { + HashOp::NoOp => byte_array_to_array_u8(data), + HashOp::Sha256 => { compute_sha256_byte_array(data).into() } + } +} + +pub fn do_length(length_op: @LengthOp, data: Array) -> Array { match length_op { LengthOp::NoPrefix => data, LengthOp::VarProto => { let mut data = data; - data.append(data.len()); - data + let mut len = proto_len(data.len()); + len.append_span(data.span()); + len } } } + +pub fn proto_len(length: u32) -> Array { + let mut result: Array = ArrayTrait::new(); + let mut len = length; + for _ in 0 + ..10_u32 { + if len < 0x80 { + result.append(len.try_into().unwrap()); + break; + } else { + let remaining_len = (len & 0x7F) | 0x80; + result.append(remaining_len.try_into().unwrap()); + len /= 0x80; + }; + }; + result +} diff --git a/cairo-libs/packages/ics23/src/tests/ops.cairo b/cairo-libs/packages/ics23/src/tests/ops.cairo new file mode 100644 index 00000000..76f2460c --- /dev/null +++ b/cairo-libs/packages/ics23/src/tests/ops.cairo @@ -0,0 +1,42 @@ +use ics23::{LeafOp, LengthOp, HashOp, apply_leaf, proto_len}; +use alexandria_math::pow; + +#[test] +fn test_apply_leaf_hash() { + let leaf = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::NoOp, + length: LengthOp::NoPrefix, + prefix: array![], + }; + let key: ByteArray = "foo"; + let value = array![0x62, 0x61, 0x72]; // bar + let hash = apply_leaf(@leaf, @key, value); + + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L210 + let expected = [ + 3282800625, 924903597, 2420628793, 1181432969, 1961202370, 4197989706, 962621772, 2935014642 + ]; + + assert_eq!(hash, expected); +} + +fn check_proto_len(value: u32, expected: Array) { + assert_eq!(proto_len(value), expected); +} + +#[test] +fn test_proto_len() { + check_proto_len(pow(2, 0) - 1, array![0x00]); + check_proto_len(pow(2, 0), array![0x01]); // 1 + check_proto_len(pow(2, 7) - 1, array![0x7F]); // 127 + check_proto_len(pow(2, 7), array![0x80, 0x01]); // 128 + check_proto_len(pow(2, 14) - 1, array![0xFF, 0x7F]); // [255, 127] + check_proto_len(pow(2, 14), array![0x80, 0x80, 0x01]); // [128, 128, 1] + check_proto_len(pow(2, 21) - 1, array![0xFF, 0xFF, 0x7F]); // [255, 255, 127] + check_proto_len(pow(2, 21), array![0x80, 0x80, 0x80, 0x01]); // [128, 128, 128, 1] + check_proto_len(pow(2, 28) - 1, array![0xFF, 0xFF, 0xFF, 0x7F]); // [255, 255, 255, 127] + check_proto_len(pow(2, 28), array![0x80, 0x80, 0x80, 0x80, 0x01]); // [128, 128, 128, 128, 1] + check_proto_len(0xffffffff, array![0xFF, 0xFF, 0xFF, 0xFF, 0x0F]); // [255, 255, 255, 255, 15] +} diff --git a/cairo-libs/packages/ics23/src/types.cairo b/cairo-libs/packages/ics23/src/types.cairo index 64a33128..bc389d0d 100644 --- a/cairo-libs/packages/ics23/src/types.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -23,7 +23,7 @@ pub impl MerkleProofImpl of MerkleProofTrait { ) { let proofs_len = self.proofs.len(); assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); - assert(root == [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); + assert(root != [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); assert(value.len() > 0, ICS23Errors::MISSING_VALUE); assert(proofs_len == specs.specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); @@ -31,12 +31,11 @@ pub impl MerkleProofImpl of MerkleProofTrait { let mut subvalue: Array = ArrayTrait::new(); let mut i = 0; while i < proofs_len { - match self.proofs[i] { - Proof::Exist(p) => { - subroot = p.calculate_root(); - p.verify(specs.specs[i], @subroot, keys[proofs_len - 1 - i], @value); - }, - _ => panic!("{}", ICS23Errors::INVALID_PROOF_TYPE), + if let Proof::Exist(p) = self.proofs[i] { + subroot = p.calculate_root(); + p.verify(specs.specs[i], @subroot, keys[proofs_len - 1 - i], @value); + } else { + panic!("{}", ICS23Errors::INVALID_PROOF_TYPE); } subvalue = subroot.span().into(); i += 1; @@ -99,9 +98,21 @@ pub impl ExistenceProofImpl of ExistenceProofTrait { assert(self.key.len() > 0, ICS23Errors::MISSING_KEY); assert(self.value.len() > 0, ICS23Errors::MISSING_VALUE); let mut hash = apply_leaf(self.leaf, self.key, self.value.clone()); - for i in 0..self.path.len() { - hash = apply_inner(self.path[i], hash); - }; + for i in 0 + ..self + .path + .len() { + hash = apply_inner(self.path[i], hash); + if let Option::Some(s) = spec { + // NOTE: Multiplied by 4 since the hash is a u32 array, but the + // child size is in u8 bytes. + assert( + !(hash.span().len() + * 4 > *s.inner_spec.child_size && s.inner_spec.child_size >= @32), + ICS23Errors::INVALID_INNER_SPEC + ); + } + }; hash } @@ -274,10 +285,10 @@ impl U64IntoLengthOp of Into { #[derive(Default, Debug, Clone, Drop, PartialEq, Serde)] pub struct InnerSpec { - pub child_order: Array, - pub child_size: i32, - pub min_prefix_length: i32, - pub max_prefix_length: i32, + pub child_order: Array, + pub child_size: u32, + pub min_prefix_length: u32, + pub max_prefix_length: u32, pub empty_child: ByteArray, // TODO: determine the correct type! pub hash: HashOp, } @@ -358,8 +369,8 @@ pub struct ProofSpecs { pub struct ProofSpec { pub leaf_spec: LeafOp, pub inner_spec: InnerSpec, - pub max_depth: i32, - pub min_depth: i32, + pub max_depth: u32, + pub min_depth: u32, pub prehash_key_before_comparison: bool, } diff --git a/cairo-libs/packages/ics23/src/utils.cairo b/cairo-libs/packages/ics23/src/utils.cairo index 7db11890..9c3e2fe2 100644 --- a/cairo-libs/packages/ics23/src/utils.cairo +++ b/cairo-libs/packages/ics23/src/utils.cairo @@ -37,3 +37,38 @@ pub fn array_u32_into_array_u8(input: Array) -> Array { }; result } + +pub fn byte_array_to_array_u8(input: @ByteArray) -> Array { + let mut output: Array = array![]; + let mut i = 0; + while i < input.len() { + output.append(input[i]); + i += 1; + }; + output +} + +/// Converts the give type `T` into an array of `u32` values. If the last word +/// is not a full word, the method returns the last word and its length. +pub trait IntoArrayU32 { + fn into_array_u32(self: T) -> (Array, u32, u32); +} + +pub impl ArrayU8IntoArrayU32 of IntoArrayU32> { + fn into_array_u32(self: Array) -> (Array, u32, u32) { + array_u8_into_array_u32(self) + } +} + +pub impl ArrayU32IntoArrayU8 of Into, Array> { + fn into(self: Array) -> Array { + array_u32_into_array_u8(self) + } +} + +pub impl SliceU32IntoArrayU32 of Into<[u32; 8], Array> { + fn into(self: [u32; 8]) -> Array { + let u32_array: Array = self.span().into(); + u32_array.into() + } +} From 2554c38b8bf899dd6431215e1d638318221fd523 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Wed, 22 Jan 2025 17:18:06 -0800 Subject: [PATCH 04/10] chore: place reusable conversion func under ics23 --- cairo-contracts/Scarb.lock | 14 ++++- cairo-contracts/Scarb.toml | 2 +- cairo-contracts/packages/core/Scarb.toml | 6 +- .../packages/core/src/channel/types.cairo | 5 +- .../packages/core/src/client/types.cairo | 3 +- .../packages/core/src/commitment/types.cairo | 3 +- .../packages/core/src/commitment/utils.cairo | 58 +------------------ .../packages/core/src/host/identifiers.cairo | 3 +- cairo-contracts/packages/core/src/lib.cairo | 5 +- .../packages/core/src/tests/commitment.cairo | 47 +-------------- cairo-libs/packages/ics23/src/lib.cairo | 5 +- .../packages/ics23/src/tests/utils.cairo | 44 ++++++++++++++ cairo-libs/packages/ics23/src/utils.cairo | 36 ++++++++---- 13 files changed, 102 insertions(+), 129 deletions(-) create mode 100644 cairo-libs/packages/ics23/src/tests/utils.cairo diff --git a/cairo-contracts/Scarb.lock b/cairo-contracts/Scarb.lock index ebf3090e..5002dba9 100644 --- a/cairo-contracts/Scarb.lock +++ b/cairo-contracts/Scarb.lock @@ -58,6 +58,14 @@ dependencies = [ "alexandria_data_structures", ] +[[package]] +name = "ics23" +version = "0.1.0" +dependencies = [ + "alexandria_numeric", + "protobuf", +] + [[package]] name = "openzeppelin_access" version = "0.18.0" @@ -110,6 +118,10 @@ version = "0.18.0" source = "registry+https://scarbs.xyz/" checksum = "sha256:725b212839f3eddc32791408609099c5e808c167ca0cf331d8c1d778b07a4e21" +[[package]] +name = "protobuf" +version = "0.1.0" + [[package]] name = "serde_json" version = "0.1.0" @@ -182,7 +194,7 @@ dependencies = [ name = "starknet_ibc_core" version = "0.1.0" dependencies = [ - "alexandria_numeric", + "ics23", "openzeppelin_testing", "snforge_std", "starknet_ibc_testkit", diff --git a/cairo-contracts/Scarb.toml b/cairo-contracts/Scarb.toml index 816bc13d..671dcd52 100644 --- a/cairo-contracts/Scarb.toml +++ b/cairo-contracts/Scarb.toml @@ -25,7 +25,6 @@ test = "snforge test" [workspace.dependencies] # external dependencies -alexandria_numeric = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "95d98a5" } alexandria_sorting = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "95d98a5" } assert_macros = "2.8.4" starknet = "2.8.4" @@ -36,6 +35,7 @@ openzeppelin_utils = "0.18.0" snforge_std = "0.31.0" # internal dependencies +ics23 = { path = "../cairo-libs/packages/ics23" } serde_json = { path = "../cairo-libs/packages/serde_json" } # ibc dependencies diff --git a/cairo-contracts/packages/core/Scarb.toml b/cairo-contracts/packages/core/Scarb.toml index 5d9ab4cf..971e3550 100644 --- a/cairo-contracts/packages/core/Scarb.toml +++ b/cairo-contracts/packages/core/Scarb.toml @@ -24,8 +24,10 @@ fmt = { workspace = true } [dependencies] # external dependencies -alexandria_numeric = { workspace = true } -starknet = { workspace = true } +starknet = { workspace = true } + +# internal dependencies +ics23 = { workspace = true } # ibc dependencies starknet_ibc_utils = { workspace = true } diff --git a/cairo-contracts/packages/core/src/channel/types.cairo b/cairo-contracts/packages/core/src/channel/types.cairo index 16c1db86..816d621e 100644 --- a/cairo-contracts/packages/core/src/channel/types.cairo +++ b/cairo-contracts/packages/core/src/channel/types.cairo @@ -1,9 +1,8 @@ use core::num::traits::Zero; +use ics23::{IntoArrayU32, array_u8_into_array_u32}; use starknet_ibc_core::channel::ChannelErrors; use starknet_ibc_core::client::{Height, Timestamp, HeightPartialOrd, TimestampPartialOrd}; -use starknet_ibc_core::commitment::{ - array_u8_into_array_u32, IntoArrayU32, StateValueZero, StateValue -}; +use starknet_ibc_core::commitment::{StateValueZero, StateValue}; use starknet_ibc_core::host::{ ConnectionId, ChannelId, ChannelIdZero, PortId, PortIdTrait, Sequence }; diff --git a/cairo-contracts/packages/core/src/client/types.cairo b/cairo-contracts/packages/core/src/client/types.cairo index 5fe02234..e5ffeed5 100644 --- a/cairo-contracts/packages/core/src/client/types.cairo +++ b/cairo-contracts/packages/core/src/client/types.cairo @@ -1,9 +1,10 @@ use core::num::traits::{CheckedAdd, Zero}; use core::traits::PartialOrd; +use ics23::IntoArrayU32; use starknet::SyscallResult; use starknet::storage_access::{Store, StorageBaseAddress}; use starknet_ibc_core::client::ClientErrors; -use starknet_ibc_core::commitment::{IntoArrayU32, U32CollectorImpl}; +use starknet_ibc_core::commitment::U32CollectorImpl; use starknet_ibc_core::host::ClientId; #[derive(Clone, Debug, Drop, PartialEq, Serde)] diff --git a/cairo-contracts/packages/core/src/commitment/types.cairo b/cairo-contracts/packages/core/src/commitment/types.cairo index c1b8efd4..727a01cc 100644 --- a/cairo-contracts/packages/core/src/commitment/types.cairo +++ b/cairo-contracts/packages/core/src/commitment/types.cairo @@ -1,8 +1,9 @@ use core::num::traits::Zero; use core::sha256::{compute_sha256_byte_array, compute_sha256_u32_array}; +use ics23::{IntoArrayU32, array_u32_into_array_u8}; use starknet_ibc_core::channel::Acknowledgement; use starknet_ibc_core::client::{Height, Timestamp}; -use starknet_ibc_core::commitment::{U32CollectorImpl, IntoArrayU32, array_u32_into_array_u8}; +use starknet_ibc_core::commitment::U32CollectorImpl; // ----------------------------------------------------------- // Commitment Value diff --git a/cairo-contracts/packages/core/src/commitment/utils.cairo b/cairo-contracts/packages/core/src/commitment/utils.cairo index 69feca93..66b8b1ec 100644 --- a/cairo-contracts/packages/core/src/commitment/utils.cairo +++ b/cairo-contracts/packages/core/src/commitment/utils.cairo @@ -1,4 +1,4 @@ -use alexandria_numeric::integers::UIntBytes; +use ics23::IntoArrayU32; #[derive(Drop, Clone)] pub struct U32Collector { @@ -31,12 +31,6 @@ pub impl U32CollectorImpl of U32CollectorTrait { } } -/// Converts the give type `T` into an array of `u32` values. If the last word -/// is not a full word, the method returns the last word and its length. -pub trait IntoArrayU32 { - fn into_array_u32(self: T) -> (Array, u32, u32); -} - pub impl U64IntoArrayU32 of IntoArrayU32 { fn into_array_u32(self: u64) -> (Array, u32, u32) { (u64_into_array_u32(self), 0, 0) @@ -51,53 +45,3 @@ pub fn u64_into_array_u32(value: u64) -> Array { array.append(lower); array } - -pub impl ArrayU8IntoArrayU32 of IntoArrayU32> { - fn into_array_u32(self: Array) -> (Array, u32, u32) { - array_u8_into_array_u32(self) - } -} - -pub fn array_u8_into_array_u32(input: Array) -> (Array, u32, u32) { - let mut result: Array = ArrayTrait::new(); - let mut last_word: u32 = 0; - let mut last_word_len: u32 = 0; - - let mut i: usize = 0; - while i < input.len() { - let mut value: u32 = 0; - let mut j: usize = 0; - while j < 4 { - if i + j >= input.len() { - break; - }; - value *= 0x100; - value = value + (*input.at(i + j)).into(); - j += 1; - }; - if j % 4 == 0 { - result.append(value); - } else { - last_word = value; - last_word_len = j.try_into().unwrap(); - } - i += 4; - }; - - (result, last_word, last_word_len) -} - -pub fn array_u32_into_array_u8(input: Array) -> Array { - let mut result: Array = ArrayTrait::new(); - for i in input { - let a = i.to_bytes(); - result.append_span(a); - }; - result -} - -pub impl ArrayU32IntoArrayU8 of Into, Array> { - fn into(self: Array) -> Array { - array_u32_into_array_u8(self) - } -} diff --git a/cairo-contracts/packages/core/src/host/identifiers.cairo b/cairo-contracts/packages/core/src/host/identifiers.cairo index ac39606c..ab2df70c 100644 --- a/cairo-contracts/packages/core/src/host/identifiers.cairo +++ b/cairo-contracts/packages/core/src/host/identifiers.cairo @@ -3,7 +3,8 @@ use core::num::traits::CheckedAdd; use core::num::traits::Zero; use core::to_byte_array::FormatAsByteArray; use core::traits::TryInto; -use starknet_ibc_core::commitment::{ArrayU32IntoArrayU8, StateValue, u64_into_array_u32}; +use ics23::{ArrayU32IntoArrayU8, u64_into_array_u32}; +use starknet_ibc_core::commitment::StateValue; use starknet_ibc_core::host::errors::HostErrors; use starknet_ibc_utils::{ValidateBasic, ComputeKey, poseidon_hash}; diff --git a/cairo-contracts/packages/core/src/lib.cairo b/cairo-contracts/packages/core/src/lib.cairo index ff0a4f22..9d5ef17b 100644 --- a/cairo-contracts/packages/core/src/lib.cairo +++ b/cairo-contracts/packages/core/src/lib.cairo @@ -25,10 +25,7 @@ pub mod commitment { Commitment, CommitmentZero, StateValue, StateValueZero, StateProof, StateProofZero, StateRoot, StateRootZero, compute_packet_commitment, compute_ack_commitment, }; - pub use utils::{ - IntoArrayU32, U64IntoArrayU32, U32Collector, U32CollectorImpl, U32CollectorTrait, - u64_into_array_u32, array_u8_into_array_u32, array_u32_into_array_u8, ArrayU32IntoArrayU8 - }; + pub use utils::{U32Collector, U32CollectorImpl, U32CollectorTrait,}; } pub mod connection { mod errors; diff --git a/cairo-contracts/packages/core/src/tests/commitment.cairo b/cairo-contracts/packages/core/src/tests/commitment.cairo index 0ffae9ff..e1fe9afd 100644 --- a/cairo-contracts/packages/core/src/tests/commitment.cairo +++ b/cairo-contracts/packages/core/src/tests/commitment.cairo @@ -1,51 +1,6 @@ -use starknet_ibc_core::commitment::{ - u64_into_array_u32, array_u8_into_array_u32, compute_ack_commitment -}; +use starknet_ibc_core::commitment::compute_ack_commitment; use starknet_ibc_testkit::dummies::{ERC20, PACKET_COMMITMENT_ON_SN}; -#[test] -fn test_u64_into_array_u32() { - assert_eq!(u64_into_array_u32(0), array![0, 0]); - assert_eq!(u64_into_array_u32(1), array![0, 1]); - assert_eq!(u64_into_array_u32(4294967296), array![1, 0]); - assert_eq!(u64_into_array_u32(4294967297), array![1, 1]); - assert_eq!(u64_into_array_u32(8589934592), array![2, 0]); - assert_eq!(u64_into_array_u32(8589934593), array![2, 1]); - assert_eq!(u64_into_array_u32(4294967295), array![0, 4294967295]); - assert_eq!(u64_into_array_u32(18446744073709551615), array![4294967295, 4294967295]); -} - -#[test] -fn test_array_u8_into_array_u32() { - let array = array![]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![], 0, 0)); - - let array = array![0]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![], 0, 1)); - - let array = array![0, 0, 0, 1]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![1], 0, 0)); - - let array = array![255, 255, 255, 255]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![4294967295], 0, 0)); - - // This corresponds to the following JSON: {"result": "AQ=="}, which - // represents the successful acknoledgement in ICS-20 application. - let array = array![123, 34, 114, 101, 115, 117, 108, 116, 34, 58, 34, 65, 81, 61, 61, 34, 125]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![2065855077, 1937075316, 574235201, 1362967842], 125, 1)); - - let array = array![ - 123, 34, 114, 101, 115, 117, 108, 116, 34, 58, 34, 65, 81, 61, 61, 34, 125, 126 - ]; - let result = array_u8_into_array_u32(array); - assert_eq!(result, (array![2065855077, 1937075316, 574235201, 1362967842], 32126, 2)); -} - // Snapshot test to ensure the computation of packet commitment stays // consistent. #[test] diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index 23f453a1..bf807344 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -5,6 +5,7 @@ mod ops; #[cfg(test)] mod tests { mod ops; + mod utils; } pub use types::{ @@ -13,7 +14,7 @@ pub use types::{ }; pub use errors::ICS23Errors; pub use utils::{ - array_u8_into_array_u32, array_u32_into_array_u8, byte_array_to_array_u8, ArrayU32IntoArrayU8, - SliceU32IntoArrayU32, IntoArrayU32, + array_u8_into_array_u32, array_u32_into_array_u8, byte_array_to_array_u8, u64_into_array_u32, + ArrayU32IntoArrayU8, SliceU32IntoArrayU32, IntoArrayU32, U64IntoArrayU32 }; pub(crate) use ops::{apply_inner, apply_leaf, proto_len}; diff --git a/cairo-libs/packages/ics23/src/tests/utils.cairo b/cairo-libs/packages/ics23/src/tests/utils.cairo new file mode 100644 index 00000000..47c4c58e --- /dev/null +++ b/cairo-libs/packages/ics23/src/tests/utils.cairo @@ -0,0 +1,44 @@ +use ics23::{array_u8_into_array_u32, u64_into_array_u32}; + +#[test] +fn test_array_u8_into_array_u32() { + let array = array![]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![], 0, 0)); + + let array = array![0]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![], 0, 1)); + + let array = array![0, 0, 0, 1]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![1], 0, 0)); + + let array = array![255, 255, 255, 255]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![4294967295], 0, 0)); + + // This corresponds to the following JSON: {"result": "AQ=="}, which + // represents the successful acknowledgement in ICS-20 application. + let array = array![123, 34, 114, 101, 115, 117, 108, 116, 34, 58, 34, 65, 81, 61, 61, 34, 125]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![2065855077, 1937075316, 574235201, 1362967842], 125, 1)); + + let array = array![ + 123, 34, 114, 101, 115, 117, 108, 116, 34, 58, 34, 65, 81, 61, 61, 34, 125, 126 + ]; + let result = array_u8_into_array_u32(array); + assert_eq!(result, (array![2065855077, 1937075316, 574235201, 1362967842], 32126, 2)); +} + +#[test] +fn test_u64_into_array_u32() { + assert_eq!(u64_into_array_u32(0), array![0, 0]); + assert_eq!(u64_into_array_u32(1), array![0, 1]); + assert_eq!(u64_into_array_u32(4294967296), array![1, 0]); + assert_eq!(u64_into_array_u32(4294967297), array![1, 1]); + assert_eq!(u64_into_array_u32(8589934592), array![2, 0]); + assert_eq!(u64_into_array_u32(8589934593), array![2, 1]); + assert_eq!(u64_into_array_u32(4294967295), array![0, 4294967295]); + assert_eq!(u64_into_array_u32(18446744073709551615), array![4294967295, 4294967295]); +} diff --git a/cairo-libs/packages/ics23/src/utils.cairo b/cairo-libs/packages/ics23/src/utils.cairo index 9c3e2fe2..65542635 100644 --- a/cairo-libs/packages/ics23/src/utils.cairo +++ b/cairo-libs/packages/ics23/src/utils.cairo @@ -38,16 +38,6 @@ pub fn array_u32_into_array_u8(input: Array) -> Array { result } -pub fn byte_array_to_array_u8(input: @ByteArray) -> Array { - let mut output: Array = array![]; - let mut i = 0; - while i < input.len() { - output.append(input[i]); - i += 1; - }; - output -} - /// Converts the give type `T` into an array of `u32` values. If the last word /// is not a full word, the method returns the last word and its length. pub trait IntoArrayU32 { @@ -72,3 +62,29 @@ pub impl SliceU32IntoArrayU32 of Into<[u32; 8], Array> { u32_array.into() } } + +pub fn byte_array_to_array_u8(input: @ByteArray) -> Array { + let mut output: Array = array![]; + let mut i = 0; + while i < input.len() { + output.append(input[i]); + i += 1; + }; + output +} + +pub fn u64_into_array_u32(value: u64) -> Array { + let mut array: Array = ArrayTrait::new(); + let upper = (value / 0x100000000).try_into().unwrap(); + let lower = (value % 0x100000000).try_into().unwrap(); + array.append(upper); + array.append(lower); + array +} + +pub impl U64IntoArrayU32 of IntoArrayU32 { + fn into_array_u32(self: u64) -> (Array, u32, u32) { + (u64_into_array_u32(self), 0, 0) + } +} + From 625257ee4eead43cc87812829205e232b6aa3e96 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Wed, 22 Jan 2025 17:20:07 -0800 Subject: [PATCH 05/10] chore: remove u64 conv from core --- .../packages/core/src/commitment/utils.cairo | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cairo-contracts/packages/core/src/commitment/utils.cairo b/cairo-contracts/packages/core/src/commitment/utils.cairo index 66b8b1ec..30e800e1 100644 --- a/cairo-contracts/packages/core/src/commitment/utils.cairo +++ b/cairo-contracts/packages/core/src/commitment/utils.cairo @@ -31,17 +31,3 @@ pub impl U32CollectorImpl of U32CollectorTrait { } } -pub impl U64IntoArrayU32 of IntoArrayU32 { - fn into_array_u32(self: u64) -> (Array, u32, u32) { - (u64_into_array_u32(self), 0, 0) - } -} - -pub fn u64_into_array_u32(value: u64) -> Array { - let mut array: Array = ArrayTrait::new(); - let upper = (value / 0x100000000).try_into().unwrap(); - let lower = (value % 0x100000000).try_into().unwrap(); - array.append(upper); - array.append(lower); - array -} From 960fb0c5e54123110f42089e798a9c0f1e3f3c02 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Thu, 23 Jan 2025 15:13:21 -0800 Subject: [PATCH 06/10] test: complete apply inner and leaf tests and debug --- cairo-libs/packages/ics23/src/lib.cairo | 3 +- cairo-libs/packages/ics23/src/ops.cairo | 24 ++-- cairo-libs/packages/ics23/src/tests/ops.cairo | 103 ++++++++++++++++-- .../packages/ics23/src/tests/utils.cairo | 15 ++- cairo-libs/packages/ics23/src/types.cairo | 12 +- cairo-libs/packages/ics23/src/utils.cairo | 38 ++++++- 6 files changed, 166 insertions(+), 29 deletions(-) diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index bf807344..9d0aa2e6 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -14,7 +14,8 @@ pub use types::{ }; pub use errors::ICS23Errors; pub use utils::{ + ArrayU32IntoArrayU8, SliceU32IntoArrayU8, ByteArrayIntoArrayU32, IntoArrayU32, U64IntoArrayU32, array_u8_into_array_u32, array_u32_into_array_u8, byte_array_to_array_u8, u64_into_array_u32, - ArrayU32IntoArrayU8, SliceU32IntoArrayU32, IntoArrayU32, U64IntoArrayU32 + array_u8_to_byte_array, encode_hex }; pub(crate) use ops::{apply_inner, apply_leaf, proto_len}; diff --git a/cairo-libs/packages/ics23/src/ops.cairo b/cairo-libs/packages/ics23/src/ops.cairo index b1b17711..543117c3 100644 --- a/cairo-libs/packages/ics23/src/ops.cairo +++ b/cairo-libs/packages/ics23/src/ops.cairo @@ -1,19 +1,18 @@ use core::sha256::{compute_sha256_u32_array, compute_sha256_byte_array}; use ics23::{ - InnerOp, LeafOp, HashOp, ICS23Errors, byte_array_to_array_u8, LengthOp, ArrayU32IntoArrayU8, - SliceU32IntoArrayU32, IntoArrayU32, + InnerOp, LeafOp, HashOp, ICS23Errors, LengthOp, ArrayU32IntoArrayU8, SliceU32IntoArrayU8, + IntoArrayU32, byte_array_to_array_u8 }; -pub fn apply_inner(inner: @InnerOp, child: [u32; 8]) -> [u32; 8] { +pub fn apply_inner(inner: @InnerOp, child: Array) -> [u32; 8] { // Sanity checks assert(inner.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); - assert(child != [0; 8], ICS23Errors::MISSING_CHILD_HASH); + assert(child.len() > 0, ICS23Errors::MISSING_CHILD_HASH); // Construct the data let mut data: Array = ArrayTrait::new(); data.append_span(inner.prefix.span()); - let u8_child_array: Array = child.into(); - data.append_span(u8_child_array.span()); + data.append_span(child.span()); data.append_span(inner.suffix.span()); // Compute the hash @@ -21,7 +20,7 @@ pub fn apply_inner(inner: @InnerOp, child: [u32; 8]) -> [u32; 8] { compute_sha256_u32_array(bytes, last_word, last_word_len) } -pub fn apply_leaf(leaf_op: @LeafOp, key: @ByteArray, value: Array) -> [u32; 8] { +pub fn apply_leaf(leaf_op: @LeafOp, key: @ByteArray, value: Array,) -> [u32; 8] { // Sanity check assert(leaf_op.hash == @HashOp::Sha256, ICS23Errors::UNSUPPORTED_HASH_OP); @@ -38,7 +37,7 @@ pub fn apply_leaf(leaf_op: @LeafOp, key: @ByteArray, value: Array) -> [u32; compute_sha256_u32_array(bytes, last_word, last_word_len) } -pub fn prepare_leaf_u32_array(prehash: @HashOp, length: @LengthOp, data: Array) -> Array { +pub fn prepare_leaf_u32_array(prehash: @HashOp, length: @LengthOp, data: Array,) -> Array { assert(data.len() > 0, ICS23Errors::MISSING_VALUE); do_length(length, hash_u32_array(prehash, data)) } @@ -48,10 +47,13 @@ pub fn prepare_leaf_byte_array(prehash: @HashOp, length: @LengthOp, data: @ByteA do_length(length, hash_byte_array(prehash, data)) } -pub fn hash_u32_array(hash_op: @HashOp, data: Array) -> Array { +pub fn hash_u32_array(hash_op: @HashOp, data: Array) -> Array { match hash_op { - HashOp::NoOp => data.into(), - HashOp::Sha256 => { compute_sha256_u32_array(data, 0, 0).into() } + HashOp::NoOp => { data }, + HashOp::Sha256 => { + let (bytes, last_word, last_word_len) = data.into_array_u32(); + compute_sha256_u32_array(bytes, last_word, last_word_len).into() + } } } diff --git a/cairo-libs/packages/ics23/src/tests/ops.cairo b/cairo-libs/packages/ics23/src/tests/ops.cairo index 76f2460c..a24b7bd1 100644 --- a/cairo-libs/packages/ics23/src/tests/ops.cairo +++ b/cairo-libs/packages/ics23/src/tests/ops.cairo @@ -1,5 +1,8 @@ -use ics23::{LeafOp, LengthOp, HashOp, apply_leaf, proto_len}; use alexandria_math::pow; +use ics23::{ + InnerOp, LeafOp, LengthOp, HashOp, apply_leaf, apply_inner, proto_len, encode_hex, + SliceU32IntoArrayU8, ByteArrayIntoArrayU32, byte_array_to_array_u8, +}; #[test] fn test_apply_leaf_hash() { @@ -11,15 +14,101 @@ fn test_apply_leaf_hash() { prefix: array![], }; let key: ByteArray = "foo"; - let value = array![0x62, 0x61, 0x72]; // bar - let hash = apply_leaf(@leaf, @key, value); + let value: ByteArray = "bar"; + let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L210 - let expected = [ - 3282800625, 924903597, 2420628793, 1181432969, 1961202370, 4197989706, 962621772, 2935014642 - ]; + assert_eq!( + encode_hex(hash.into()), "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" + ); +} + +#[test] +fn test_apply_leaf_hash_length() { + let leaf = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::NoOp, + length: LengthOp::VarProto, + prefix: array![], + }; + + let key: ByteArray = "food"; + let value: ByteArray = "some longer text"; + let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); + + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L246 + assert_eq!( + encode_hex(hash.into()), "b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265" + ); +} + +#[test] +fn test_apply_leaf_prehash_value() { + let leaf = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::Sha256, + length: LengthOp::VarProto, + prefix: array![], + }; + + let key: ByteArray = "food"; + let value: ByteArray = "yet another long string"; + let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); + + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L282 + assert_eq!( + encode_hex(hash.into()), "87e0483e8fb624aef2e2f7b13f4166cda485baa8e39f437c83d74c94bedb148f" + ); +} + +#[test] +fn test_apply_inner_prefix_suffix() { + let inner = InnerOp { + hash: HashOp::Sha256, + prefix: array![1, 35, 69, 103, 137], + suffix: array![222, 173, 190, 239], + }; + let child = array![0, 202, 254, 0]; + let hash = apply_inner(@inner, child); + + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 + assert_eq!( + encode_hex(hash.into()), "0339f76086684506a6d42a60da4b5a719febd4d96d8b8d85ae92849e3a849a5e" + ); +} + +#[test] +fn test_apply_inner_prefix_only() { + let inner = InnerOp { + hash: HashOp::Sha256, + prefix: array![0, 32, 64, 128, 160, 192, 224], + suffix: array![], + }; + let child = array![255, 204, 187, 153, 119, 85, 51, 17, 0]; + let hash = apply_inner(@inner, child); + + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 + assert_eq!( + encode_hex(hash.into()), "45bece1678cf2e9f4f2ae033e546fc35a2081b2415edcb13121a0e908dca1927" + ); +} + +#[test] +fn test_apply_inner_suffix_only() { + let inner = InnerOp { + hash: HashOp::Sha256, + prefix: array![], + suffix: byte_array_to_array_u8(@" just kidding!") + }; + let child = byte_array_to_array_u8(@"this is a sha256 hash, really...."); + let hash = apply_inner(@inner, child); - assert_eq!(hash, expected); + // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L316 + assert_eq!( + encode_hex(hash.into()), "79ef671d27e42a53fba2201c1bbc529a099af578ee8a38df140795db0ae2184b" + ); } fn check_proto_len(value: u32, expected: Array) { diff --git a/cairo-libs/packages/ics23/src/tests/utils.cairo b/cairo-libs/packages/ics23/src/tests/utils.cairo index 47c4c58e..06717709 100644 --- a/cairo-libs/packages/ics23/src/tests/utils.cairo +++ b/cairo-libs/packages/ics23/src/tests/utils.cairo @@ -1,4 +1,4 @@ -use ics23::{array_u8_into_array_u32, u64_into_array_u32}; +use ics23::{array_u8_into_array_u32, u64_into_array_u32, encode_hex}; #[test] fn test_array_u8_into_array_u32() { @@ -42,3 +42,16 @@ fn test_u64_into_array_u32() { assert_eq!(u64_into_array_u32(4294967295), array![0, 4294967295]); assert_eq!(u64_into_array_u32(18446744073709551615), array![4294967295, 4294967295]); } + +#[test] +fn test_encode_hex() { + assert_eq!("", encode_hex(array![])); + assert_eq!("00", encode_hex(array![0])); + assert_eq!("01", encode_hex(array![1])); + assert_eq!("7f", encode_hex(array![127])); + assert_eq!("80", encode_hex(array![128])); + assert_eq!("ff", encode_hex(array![255])); + assert_eq!("0001", encode_hex(array![0, 1])); + assert_eq!("fffe", encode_hex(array![255, 254])); + assert_eq!("0123456789abcdef", encode_hex(array![1, 35, 69, 103, 137, 171, 205, 239])); +} diff --git a/cairo-libs/packages/ics23/src/types.cairo b/cairo-libs/packages/ics23/src/types.cairo index bc389d0d..7118b13c 100644 --- a/cairo-libs/packages/ics23/src/types.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -5,7 +5,7 @@ use protobuf::types::message::{ use protobuf::primitives::array::{ByteArrayAsProtoMessage}; use protobuf::primitives::numeric::{UnsignedAsProtoMessage, I32AsProtoMessage, BoolAsProtoMessage}; use protobuf::types::tag::WireType; -use ics23::{ICS23Errors, apply_inner, apply_leaf}; +use ics23::{ICS23Errors, SliceU32IntoArrayU8, apply_inner, apply_leaf}; #[derive(Default, Debug, Drop, PartialEq, Serde)] pub struct MerkleProof { @@ -19,7 +19,7 @@ pub impl MerkleProofImpl of MerkleProofTrait { specs: ProofSpecs, root: RootBytes, keys: Array, - value: Array, + value: Array, ) { let proofs_len = self.proofs.len(); assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); @@ -60,7 +60,7 @@ pub impl MerkleProofImpl of MerkleProofTrait { p.verify(specs.specs[i], @subroot, keys[proofs_len - i]); self .verify_membership( - specs.clone(), root, keys.clone(), subroot.span().into() + specs.clone(), root, keys.clone(), subroot.into() ) // TODO: add start_index }, _ => panic!("{}", ICS23Errors::INVALID_PROOF_TYPE), @@ -83,7 +83,7 @@ pub enum Proof { #[derive(Default, Debug, Drop, PartialEq, Serde)] pub struct ExistenceProof { pub key: ByteArray, - pub value: Array, + pub value: Array, pub leaf: LeafOp, pub path: Array, } @@ -102,7 +102,7 @@ pub impl ExistenceProofImpl of ExistenceProofTrait { ..self .path .len() { - hash = apply_inner(self.path[i], hash); + hash = apply_inner(self.path[i], hash.into()); if let Option::Some(s) = spec { // NOTE: Multiplied by 4 since the hash is a u32 array, but the // child size is in u8 bytes. @@ -121,7 +121,7 @@ pub impl ExistenceProofImpl of ExistenceProofTrait { spec: @ProofSpec, root: @RootBytes, key: @ByteArray, - value: @Array, + value: @Array, ) { assert(self.key == key, ICS23Errors::MISMATCHED_KEY); assert(self.value == value, ICS23Errors::MISMATCHED_VALUE); diff --git a/cairo-libs/packages/ics23/src/utils.cairo b/cairo-libs/packages/ics23/src/utils.cairo index 65542635..26657ac6 100644 --- a/cairo-libs/packages/ics23/src/utils.cairo +++ b/cairo-libs/packages/ics23/src/utils.cairo @@ -29,12 +29,15 @@ pub fn array_u8_into_array_u32(input: Array) -> (Array, u32, u32) { (result, last_word, last_word_len) } -pub fn array_u32_into_array_u8(input: Array) -> Array { +pub fn array_u32_into_array_u8(input: Array, last_word: u32, last_word_len: u32) -> Array { let mut result: Array = ArrayTrait::new(); for i in input { let a = i.to_bytes(); result.append_span(a); }; + if last_word_len > 0 && last_word != 0 { + result.append_span(last_word.to_bytes()); + }; result } @@ -52,17 +55,24 @@ pub impl ArrayU8IntoArrayU32 of IntoArrayU32> { pub impl ArrayU32IntoArrayU8 of Into, Array> { fn into(self: Array) -> Array { - array_u32_into_array_u8(self) + array_u32_into_array_u8(self, 0, 0) } } -pub impl SliceU32IntoArrayU32 of Into<[u32; 8], Array> { +pub impl SliceU32IntoArrayU8 of Into<[u32; 8], Array> { fn into(self: [u32; 8]) -> Array { let u32_array: Array = self.span().into(); u32_array.into() } } +pub impl ByteArrayIntoArrayU32 of IntoArrayU32 { + fn into_array_u32(self: ByteArray) -> (Array, u32, u32) { + let bytes = byte_array_to_array_u8(@self); + bytes.into_array_u32() + } +} + pub fn byte_array_to_array_u8(input: @ByteArray) -> Array { let mut output: Array = array![]; let mut i = 0; @@ -73,6 +83,28 @@ pub fn byte_array_to_array_u8(input: @ByteArray) -> Array { output } +pub fn array_u8_to_byte_array(input: @Array) -> ByteArray { + let mut output = ""; + let mut i = 0; + while i < input.len() { + output.append_byte(*input[i]); + i += 1; + }; + output +} + +pub fn encode_hex(bytes: Array) -> ByteArray { + let mut output = ""; + let hex_chars: ByteArray = "0123456789abcdef"; + for b in bytes { + let high: u32 = (b / 16).try_into().unwrap(); + let low: u32 = (b % 16).try_into().unwrap(); + output.append_byte(hex_chars[high]); + output.append_byte(hex_chars[low]); + }; + output +} + pub fn u64_into_array_u32(value: u64) -> Array { let mut array: Array = ArrayTrait::new(); let upper = (value / 0x100000000).try_into().unwrap(); From c2d4b1069a7319c230192bc7c880a72e0bbfd7e1 Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Thu, 23 Jan 2025 15:54:05 -0800 Subject: [PATCH 07/10] feat: hex decoding + separate out verify funcs --- .../packages/core/src/commitment/types.cairo | 2 +- cairo-libs/packages/ics23/src/lib.cairo | 9 ++- cairo-libs/packages/ics23/src/tests/ops.cairo | 30 ++++---- .../packages/ics23/src/tests/utils.cairo | 27 +++++--- .../packages/ics23/src/tests/verify.cairo | 1 + cairo-libs/packages/ics23/src/types.cairo | 68 ------------------- cairo-libs/packages/ics23/src/utils.cairo | 30 ++++++++ cairo-libs/packages/ics23/src/verify.cairo | 58 ++++++++++++++++ 8 files changed, 124 insertions(+), 101 deletions(-) create mode 100644 cairo-libs/packages/ics23/src/tests/verify.cairo create mode 100644 cairo-libs/packages/ics23/src/verify.cairo diff --git a/cairo-contracts/packages/core/src/commitment/types.cairo b/cairo-contracts/packages/core/src/commitment/types.cairo index 727a01cc..79305159 100644 --- a/cairo-contracts/packages/core/src/commitment/types.cairo +++ b/cairo-contracts/packages/core/src/commitment/types.cairo @@ -44,7 +44,7 @@ pub impl CommitmentZero of Zero { pub impl CommitmentIntoStateValue of Into { fn into(self: Commitment) -> StateValue { - let value = array_u32_into_array_u8(self.into()); + let value = array_u32_into_array_u8(self.into(), 0, 0); StateValue { value } } } diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index 9d0aa2e6..bc0777c1 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -1,21 +1,24 @@ mod types; mod errors; mod utils; +mod verify; mod ops; #[cfg(test)] mod tests { mod ops; mod utils; + mod verify; } pub use types::{ - MerkleProof, MerkleProofImpl, MerkleProofTrait, Proof, ExistenceProof, ExistenceProofImpl, - ExistenceProofTrait, NonExistenceProof, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp + Proof, ExistenceProof, ExistenceProofImpl, ExistenceProofTrait, NonExistenceProof, + NonExistenceProofImpl, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp, RootBytes, ProofSpecImpl }; pub use errors::ICS23Errors; pub use utils::{ ArrayU32IntoArrayU8, SliceU32IntoArrayU8, ByteArrayIntoArrayU32, IntoArrayU32, U64IntoArrayU32, array_u8_into_array_u32, array_u32_into_array_u8, byte_array_to_array_u8, u64_into_array_u32, - array_u8_to_byte_array, encode_hex + array_u8_to_byte_array, encode_hex, decode_hex, }; +pub use verify::{verify_membership, verify_non_membership}; pub(crate) use ops::{apply_inner, apply_leaf, proto_len}; diff --git a/cairo-libs/packages/ics23/src/tests/ops.cairo b/cairo-libs/packages/ics23/src/tests/ops.cairo index a24b7bd1..672d1d71 100644 --- a/cairo-libs/packages/ics23/src/tests/ops.cairo +++ b/cairo-libs/packages/ics23/src/tests/ops.cairo @@ -1,9 +1,10 @@ use alexandria_math::pow; use ics23::{ - InnerOp, LeafOp, LengthOp, HashOp, apply_leaf, apply_inner, proto_len, encode_hex, + InnerOp, LeafOp, LengthOp, HashOp, apply_leaf, apply_inner, proto_len, encode_hex, decode_hex, SliceU32IntoArrayU8, ByteArrayIntoArrayU32, byte_array_to_array_u8, }; +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L210 #[test] fn test_apply_leaf_hash() { let leaf = LeafOp { @@ -17,12 +18,12 @@ fn test_apply_leaf_hash() { let value: ByteArray = "bar"; let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L210 assert_eq!( encode_hex(hash.into()), "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2" ); } +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L246 #[test] fn test_apply_leaf_hash_length() { let leaf = LeafOp { @@ -37,12 +38,12 @@ fn test_apply_leaf_hash_length() { let value: ByteArray = "some longer text"; let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L246 assert_eq!( encode_hex(hash.into()), "b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265" ); } +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L282 #[test] fn test_apply_leaf_prehash_value() { let leaf = LeafOp { @@ -57,55 +58,48 @@ fn test_apply_leaf_prehash_value() { let value: ByteArray = "yet another long string"; let hash = apply_leaf(@leaf, @key, byte_array_to_array_u8(@value)); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L282 assert_eq!( encode_hex(hash.into()), "87e0483e8fb624aef2e2f7b13f4166cda485baa8e39f437c83d74c94bedb148f" ); } +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 #[test] fn test_apply_inner_prefix_suffix() { let inner = InnerOp { - hash: HashOp::Sha256, - prefix: array![1, 35, 69, 103, 137], - suffix: array![222, 173, 190, 239], + hash: HashOp::Sha256, prefix: decode_hex(@"0123456789"), suffix: decode_hex(@"deadbeef"), }; - let child = array![0, 202, 254, 0]; + let child = decode_hex(@"00cafe00"); let hash = apply_inner(@inner, child); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 assert_eq!( encode_hex(hash.into()), "0339f76086684506a6d42a60da4b5a719febd4d96d8b8d85ae92849e3a849a5e" ); } +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 #[test] fn test_apply_inner_prefix_only() { let inner = InnerOp { - hash: HashOp::Sha256, - prefix: array![0, 32, 64, 128, 160, 192, 224], - suffix: array![], + hash: HashOp::Sha256, prefix: decode_hex(@"00204080a0c0e0"), suffix: array![], }; - let child = array![255, 204, 187, 153, 119, 85, 51, 17, 0]; + let child = decode_hex(@"ffccbb997755331100"); let hash = apply_inner(@inner, child); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L299 assert_eq!( encode_hex(hash.into()), "45bece1678cf2e9f4f2ae033e546fc35a2081b2415edcb13121a0e908dca1927" ); } +// https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L316 #[test] fn test_apply_inner_suffix_only() { let inner = InnerOp { - hash: HashOp::Sha256, - prefix: array![], - suffix: byte_array_to_array_u8(@" just kidding!") + hash: HashOp::Sha256, prefix: array![], suffix: byte_array_to_array_u8(@" just kidding!") }; let child = byte_array_to_array_u8(@"this is a sha256 hash, really...."); let hash = apply_inner(@inner, child); - // https://github.com/cosmos/ics23/blob/c7c728879896fb260fe76b208ea6a17c2b0132a3/rust/src/ops.rs#L316 assert_eq!( encode_hex(hash.into()), "79ef671d27e42a53fba2201c1bbc529a099af578ee8a38df140795db0ae2184b" ); diff --git a/cairo-libs/packages/ics23/src/tests/utils.cairo b/cairo-libs/packages/ics23/src/tests/utils.cairo index 06717709..08454c3d 100644 --- a/cairo-libs/packages/ics23/src/tests/utils.cairo +++ b/cairo-libs/packages/ics23/src/tests/utils.cairo @@ -1,4 +1,4 @@ -use ics23::{array_u8_into_array_u32, u64_into_array_u32, encode_hex}; +use ics23::{array_u8_into_array_u32, u64_into_array_u32, encode_hex, decode_hex}; #[test] fn test_array_u8_into_array_u32() { @@ -43,15 +43,20 @@ fn test_u64_into_array_u32() { assert_eq!(u64_into_array_u32(18446744073709551615), array![4294967295, 4294967295]); } +fn check_hex_codec(hex: @ByteArray) { + assert_eq!(hex, @encode_hex(decode_hex(hex))); +} + #[test] -fn test_encode_hex() { - assert_eq!("", encode_hex(array![])); - assert_eq!("00", encode_hex(array![0])); - assert_eq!("01", encode_hex(array![1])); - assert_eq!("7f", encode_hex(array![127])); - assert_eq!("80", encode_hex(array![128])); - assert_eq!("ff", encode_hex(array![255])); - assert_eq!("0001", encode_hex(array![0, 1])); - assert_eq!("fffe", encode_hex(array![255, 254])); - assert_eq!("0123456789abcdef", encode_hex(array![1, 35, 69, 103, 137, 171, 205, 239])); +fn test_decode_hex() { + check_hex_codec(@""); + check_hex_codec(@"00"); + check_hex_codec(@"01"); + check_hex_codec(@"7f"); + check_hex_codec(@"80"); + check_hex_codec(@"ff"); + check_hex_codec(@"0001"); + check_hex_codec(@"fffe"); + check_hex_codec(@"0123456789abcdef"); + check_hex_codec(@"fedcba9876543210"); } diff --git a/cairo-libs/packages/ics23/src/tests/verify.cairo b/cairo-libs/packages/ics23/src/tests/verify.cairo new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/cairo-libs/packages/ics23/src/tests/verify.cairo @@ -0,0 +1 @@ + diff --git a/cairo-libs/packages/ics23/src/types.cairo b/cairo-libs/packages/ics23/src/types.cairo index 7118b13c..6415e608 100644 --- a/cairo-libs/packages/ics23/src/types.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -7,69 +7,6 @@ use protobuf::primitives::numeric::{UnsignedAsProtoMessage, I32AsProtoMessage, B use protobuf::types::tag::WireType; use ics23::{ICS23Errors, SliceU32IntoArrayU8, apply_inner, apply_leaf}; -#[derive(Default, Debug, Drop, PartialEq, Serde)] -pub struct MerkleProof { - pub proofs: Array, -} - -#[generate_trait] -pub impl MerkleProofImpl of MerkleProofTrait { - fn verify_membership( - self: @MerkleProof, - specs: ProofSpecs, - root: RootBytes, - keys: Array, - value: Array, - ) { - let proofs_len = self.proofs.len(); - assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); - assert(root != [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); - assert(value.len() > 0, ICS23Errors::MISSING_VALUE); - assert(proofs_len == specs.specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); - assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); - let mut subroot = [0; 8]; - let mut subvalue: Array = ArrayTrait::new(); - let mut i = 0; - while i < proofs_len { - if let Proof::Exist(p) = self.proofs[i] { - subroot = p.calculate_root(); - p.verify(specs.specs[i], @subroot, keys[proofs_len - 1 - i], @value); - } else { - panic!("{}", ICS23Errors::INVALID_PROOF_TYPE); - } - subvalue = subroot.span().into(); - i += 1; - }; - assert(root == subroot, ICS23Errors::INVALID_MERKLE_PROOF); - } - - fn verify_non_membership( - self: @MerkleProof, specs: ProofSpecs, root: RootBytes, keys: Array - ) { - let proofs_len = self.proofs.len(); - assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); - assert(root == [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); - assert(proofs_len == specs.specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); - assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); - let mut subroot = [0; 8]; - let mut i = 0; - while i < proofs_len { - match self.proofs[i] { - Proof::NonExist(p) => { - subroot = p.calculate_root(); - p.verify(specs.specs[i], @subroot, keys[proofs_len - i]); - self - .verify_membership( - specs.clone(), root, keys.clone(), subroot.into() - ) // TODO: add start_index - }, - _ => panic!("{}", ICS23Errors::INVALID_PROOF_TYPE), - } - i += 1; - }; - } -} - /// Contains nested proof types within a commitment proof. It currently supports /// existence and non-existence proofs to meet the core requirements of IBC. Batch /// and compressed proofs can be added in the future if necessary. @@ -360,11 +297,6 @@ impl LeafOpAsProtoName of ProtoName { } } -#[derive(Default, Debug, Clone, Drop, PartialEq, Serde)] -pub struct ProofSpecs { - specs: Array, -} - #[derive(Default, Debug, Clone, Drop, PartialEq, Serde)] pub struct ProofSpec { pub leaf_spec: LeafOp, diff --git a/cairo-libs/packages/ics23/src/utils.cairo b/cairo-libs/packages/ics23/src/utils.cairo index 26657ac6..722153da 100644 --- a/cairo-libs/packages/ics23/src/utils.cairo +++ b/cairo-libs/packages/ics23/src/utils.cairo @@ -93,6 +93,36 @@ pub fn array_u8_to_byte_array(input: @Array) -> ByteArray { output } +pub fn decode_hex(hex: @ByteArray) -> Array { + let mut output: Array = array![]; + let len = hex.len(); + assert(len % 2 == 0, 'Invalid hex length'); + let mut i = 0; + while i < len { + let high = hex[i]; + let low = hex[i + 1]; + assert(is_valid_hex_char(high), 'Invalid hex character'); + assert(is_valid_hex_char(low), 'Invalid hex character'); + let high = if high >= 97 { + high - 87 + } else { + high - 48 + }; + let low = if low >= 97 { + low - 87 + } else { + low - 48 + }; + output.append(high * 16 + low); + i += 2; + }; + output +} +// Only accept lowercase hex characters +pub fn is_valid_hex_char(c: u8) -> bool { + (c >= 48 && c <= 57) || (c >= 97 && c <= 102) +} + pub fn encode_hex(bytes: Array) -> ByteArray { let mut output = ""; let hex_chars: ByteArray = "0123456789abcdef"; diff --git a/cairo-libs/packages/ics23/src/verify.cairo b/cairo-libs/packages/ics23/src/verify.cairo new file mode 100644 index 00000000..25938335 --- /dev/null +++ b/cairo-libs/packages/ics23/src/verify.cairo @@ -0,0 +1,58 @@ +use ics23::{ + Proof, ProofSpec, RootBytes, ICS23Errors, ExistenceProofImpl, NonExistenceProofImpl, + SliceU32IntoArrayU8 +}; + +pub fn verify_membership( + specs: Array, + proofs: @Array, + root: RootBytes, + keys: Array, + value: Array, +) { + let proofs_len = proofs.len(); + assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); + assert(root != [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); + assert(value.len() > 0, ICS23Errors::MISSING_VALUE); + assert(proofs_len == specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + let mut subroot = [0; 8]; + let mut subvalue: Array = ArrayTrait::new(); + let mut i = 0; + while i < proofs_len { + if let Proof::Exist(p) = proofs[i] { + subroot = p.calculate_root(); + p.verify(specs[i], @subroot, keys[proofs_len - 1 - i], @value); + } else { + panic!("{}", ICS23Errors::INVALID_PROOF_TYPE); + } + subvalue = subroot.span().into(); + i += 1; + }; + assert(root == subroot, ICS23Errors::INVALID_MERKLE_PROOF); +} + +pub fn verify_non_membership( + specs: Array, proofs: @Array, root: RootBytes, keys: Array +) { + let proofs_len = proofs.len(); + assert(proofs_len > 0, ICS23Errors::MISSING_MERKLE_PROOF); + assert(root == [0; 8], ICS23Errors::ZERO_MERKLE_ROOT); + assert(proofs_len == specs.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + assert(proofs_len == keys.len(), ICS23Errors::MISMATCHED_NUM_OF_PROOFS); + let mut subroot = [0; 8]; + let mut i = 0; + while i < proofs_len { + if let Proof::NonExist(p) = proofs[i] { + subroot = p.calculate_root(); + p.verify(specs[i], @subroot, keys[proofs_len - 1 - i]); + + verify_membership( + specs.clone(), proofs, root, keys.clone(), subroot.into() + ) // TODO: add start_index + } else { + panic!("{}", ICS23Errors::INVALID_PROOF_TYPE); + } + i += 1; + }; +} From 91d61736a98bab103a0514992f32e53b9df9adac Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Fri, 31 Jan 2025 12:13:35 -0800 Subject: [PATCH 08/10] tests: calculate root --- .../packages/ics23/src/tests/verify.cairo | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/cairo-libs/packages/ics23/src/tests/verify.cairo b/cairo-libs/packages/ics23/src/tests/verify.cairo index 8b137891..d6f553d8 100644 --- a/cairo-libs/packages/ics23/src/tests/verify.cairo +++ b/cairo-libs/packages/ics23/src/tests/verify.cairo @@ -1 +1,51 @@ +use ics23::{LeafOp, LengthOp, HashOp, InnerOp, ExistenceProof, ExistenceProofImpl, SliceU32IntoArrayU8, byte_array_to_array_u8, encode_hex, decode_hex}; +#[test] +fn test_calculate_root_from_leaf() { + let leaf = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::NoOp, + length: LengthOp::VarProto, + prefix: array![], + }; + + let proof = ExistenceProof { + key: "food", + value: byte_array_to_array_u8(@"some longer text"), + leaf, + path: array![], + }; + + let root = proof.calculate_root(); + + assert_eq!(encode_hex(root.into()), "b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265"); +} + +#[test] +fn test_calculate_root_from_leaf_and_inner() { + let leaf = LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoOp, + prehash_value: HashOp::NoOp, + length: LengthOp::VarProto, + prefix: array![] + }; + + let inner = InnerOp { + hash: HashOp::Sha256, + prefix: decode_hex(@"deadbeef00cafe00"), + suffix: array![] + }; + + let proof = ExistenceProof { + key: "food", + value: byte_array_to_array_u8(@"some longer text"), + leaf, + path: array![inner], + }; + + let root = proof.calculate_root(); + + assert_eq!(encode_hex(root.into()), "836ea236a6902a665c2a004c920364f24cad52ded20b1e4f22c3179bfe25b2a9"); +} From fa69f375322fdaa39730341d287b90445c9650cf Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Tue, 4 Feb 2025 13:27:43 -0800 Subject: [PATCH 09/10] feat: implement check_existence_spec --- cairo-libs/Scarb.lock | 2 +- cairo-libs/packages/ics23/Scarb.toml | 1 - cairo-libs/packages/ics23/src/errors.cairo | 13 +++ cairo-libs/packages/ics23/src/lib.cairo | 5 +- cairo-libs/packages/ics23/src/ops.cairo | 19 +--- cairo-libs/packages/ics23/src/tests/ops.cairo | 21 +---- .../packages/ics23/src/tests/verify.cairo | 27 +++--- cairo-libs/packages/ics23/src/types.cairo | 5 + cairo-libs/packages/ics23/src/verify.cairo | 75 ++++++++++++++- cairo-libs/packages/protobuf/Scarb.toml | 10 +- cairo-libs/packages/protobuf/src/errors.cairo | 5 + cairo-libs/packages/protobuf/src/lib.cairo | 7 +- .../protobuf/src/primitives/numeric.cairo | 8 +- .../protobuf/src/primitives/utils.cairo | 65 +------------ .../src/{tests.cairo => tests/proto.cairo} | 0 .../packages/protobuf/src/tests/varint.cairo | 48 ++++++++++ cairo-libs/packages/protobuf/src/varint.cairo | 91 +++++++++++++++++++ 17 files changed, 274 insertions(+), 128 deletions(-) create mode 100644 cairo-libs/packages/protobuf/src/errors.cairo rename cairo-libs/packages/protobuf/src/{tests.cairo => tests/proto.cairo} (100%) create mode 100644 cairo-libs/packages/protobuf/src/tests/varint.cairo create mode 100644 cairo-libs/packages/protobuf/src/varint.cairo diff --git a/cairo-libs/Scarb.lock b/cairo-libs/Scarb.lock index 477d0155..af4bd0df 100644 --- a/cairo-libs/Scarb.lock +++ b/cairo-libs/Scarb.lock @@ -63,7 +63,6 @@ dependencies = [ name = "ics23" version = "0.1.0" dependencies = [ - "alexandria_math", "alexandria_numeric", "protobuf", "snforge_std", @@ -73,6 +72,7 @@ dependencies = [ name = "protobuf" version = "0.1.0" dependencies = [ + "alexandria_math", "snforge_std", ] diff --git a/cairo-libs/packages/ics23/Scarb.toml b/cairo-libs/packages/ics23/Scarb.toml index 3e75ce60..c34b8211 100644 --- a/cairo-libs/packages/ics23/Scarb.toml +++ b/cairo-libs/packages/ics23/Scarb.toml @@ -24,6 +24,5 @@ alexandria_numeric = { workspace = true } protobuf = { workspace = true } [dev-dependencies] -alexandria_math = { workspace = true } cairo_test = { workspace = true } snforge_std = { workspace = true } diff --git a/cairo-libs/packages/ics23/src/errors.cairo b/cairo-libs/packages/ics23/src/errors.cairo index ef2a57d2..11fd11fc 100644 --- a/cairo-libs/packages/ics23/src/errors.cairo +++ b/cairo-libs/packages/ics23/src/errors.cairo @@ -10,7 +10,20 @@ pub mod ICS23Errors { pub const INVALID_MERKLE_PROOF: felt252 = 'ICS23: invalid merkle proof'; pub const INVALID_PROOF_TYPE: felt252 = 'ICS23: invalid proof type'; pub const INVALID_INNER_SPEC: felt252 = 'ICS23: invalid inner spec'; + pub const INVALID_INNER_OP_SIZE: felt252 = 'ICS23: invalid inner op size'; + pub const INVALID_INNER_PREFIX: felt252 = 'ICS23: invalid inner prefix'; + pub const INVALID_INNER_PREFIX_LEN: felt252 = 'ICS23: invalid inner prefix len'; + pub const INVALID_INNER_SUFFIX: felt252 = 'ICS23: invalid inner suffix'; + pub const INVALID_HASH_OP: felt252 = 'ICS23: invalid hash op'; + pub const INVALID_PREHASH_KEY: felt252 = 'ICS23: invalid prehash key'; + pub const INVALID_PREHASH_VALUE: felt252 = 'ICS23: invalid prehash value'; + pub const INVALID_LENGTH_OP: felt252 = 'ICS23: invalid length op'; pub const INVALID_DEPTH_RANGE: felt252 = 'ICS23: invalid depth range'; + pub const INVALID_LEAF_PREFIX: felt252 = 'ICS23: invalid leaf prefix'; + pub const INVALID_IAVL_HEIGHT_PREFIX: felt252 = 'ICS23: invalid height prefix'; pub const UNSUPPORTED_HASH_OP: felt252 = 'ICS23: unsupported hash op'; pub const ZERO_MERKLE_ROOT: felt252 = 'ICS23: zero merkle root'; + pub const ZERO_IAVL_SIZE_PREFIX: felt252 = 'ICS23: zero size prefix'; + pub const ZERO_IAVL_VERSION_PREFIX: felt252 = 'ICS23: zero version prefix'; + pub const ZERO_CHILD_SIZE: felt252 = 'ICS23: zero child size'; } diff --git a/cairo-libs/packages/ics23/src/lib.cairo b/cairo-libs/packages/ics23/src/lib.cairo index bc0777c1..0e904aae 100644 --- a/cairo-libs/packages/ics23/src/lib.cairo +++ b/cairo-libs/packages/ics23/src/lib.cairo @@ -12,7 +12,8 @@ mod tests { pub use types::{ Proof, ExistenceProof, ExistenceProofImpl, ExistenceProofTrait, NonExistenceProof, - NonExistenceProofImpl, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp, RootBytes, ProofSpecImpl + NonExistenceProofImpl, InnerOp, LeafOp, ProofSpec, HashOp, LengthOp, RootBytes, ProofSpecImpl, + ProofSpecTrait, InnerSpec }; pub use errors::ICS23Errors; pub use utils::{ @@ -21,4 +22,4 @@ pub use utils::{ array_u8_to_byte_array, encode_hex, decode_hex, }; pub use verify::{verify_membership, verify_non_membership}; -pub(crate) use ops::{apply_inner, apply_leaf, proto_len}; +pub(crate) use ops::{apply_inner, apply_leaf}; diff --git a/cairo-libs/packages/ics23/src/ops.cairo b/cairo-libs/packages/ics23/src/ops.cairo index 543117c3..240c1a8b 100644 --- a/cairo-libs/packages/ics23/src/ops.cairo +++ b/cairo-libs/packages/ics23/src/ops.cairo @@ -3,6 +3,7 @@ use ics23::{ InnerOp, LeafOp, HashOp, ICS23Errors, LengthOp, ArrayU32IntoArrayU8, SliceU32IntoArrayU8, IntoArrayU32, byte_array_to_array_u8 }; +use protobuf::varint::encode_varint_to_u8_array; pub fn apply_inner(inner: @InnerOp, child: Array) -> [u32; 8] { // Sanity checks @@ -69,26 +70,10 @@ pub fn do_length(length_op: @LengthOp, data: Array) -> Array { LengthOp::NoPrefix => data, LengthOp::VarProto => { let mut data = data; - let mut len = proto_len(data.len()); + let mut len = encode_varint_to_u8_array(data.len()); len.append_span(data.span()); len } } } -pub fn proto_len(length: u32) -> Array { - let mut result: Array = ArrayTrait::new(); - let mut len = length; - for _ in 0 - ..10_u32 { - if len < 0x80 { - result.append(len.try_into().unwrap()); - break; - } else { - let remaining_len = (len & 0x7F) | 0x80; - result.append(remaining_len.try_into().unwrap()); - len /= 0x80; - }; - }; - result -} diff --git a/cairo-libs/packages/ics23/src/tests/ops.cairo b/cairo-libs/packages/ics23/src/tests/ops.cairo index 672d1d71..fd24da05 100644 --- a/cairo-libs/packages/ics23/src/tests/ops.cairo +++ b/cairo-libs/packages/ics23/src/tests/ops.cairo @@ -1,6 +1,5 @@ -use alexandria_math::pow; use ics23::{ - InnerOp, LeafOp, LengthOp, HashOp, apply_leaf, apply_inner, proto_len, encode_hex, decode_hex, + InnerOp, LeafOp, LengthOp, HashOp, apply_leaf, apply_inner, encode_hex, decode_hex, SliceU32IntoArrayU8, ByteArrayIntoArrayU32, byte_array_to_array_u8, }; @@ -105,21 +104,3 @@ fn test_apply_inner_suffix_only() { ); } -fn check_proto_len(value: u32, expected: Array) { - assert_eq!(proto_len(value), expected); -} - -#[test] -fn test_proto_len() { - check_proto_len(pow(2, 0) - 1, array![0x00]); - check_proto_len(pow(2, 0), array![0x01]); // 1 - check_proto_len(pow(2, 7) - 1, array![0x7F]); // 127 - check_proto_len(pow(2, 7), array![0x80, 0x01]); // 128 - check_proto_len(pow(2, 14) - 1, array![0xFF, 0x7F]); // [255, 127] - check_proto_len(pow(2, 14), array![0x80, 0x80, 0x01]); // [128, 128, 1] - check_proto_len(pow(2, 21) - 1, array![0xFF, 0xFF, 0x7F]); // [255, 255, 127] - check_proto_len(pow(2, 21), array![0x80, 0x80, 0x80, 0x01]); // [128, 128, 128, 1] - check_proto_len(pow(2, 28) - 1, array![0xFF, 0xFF, 0xFF, 0x7F]); // [255, 255, 255, 127] - check_proto_len(pow(2, 28), array![0x80, 0x80, 0x80, 0x80, 0x01]); // [128, 128, 128, 128, 1] - check_proto_len(0xffffffff, array![0xFF, 0xFF, 0xFF, 0xFF, 0x0F]); // [255, 255, 255, 255, 15] -} diff --git a/cairo-libs/packages/ics23/src/tests/verify.cairo b/cairo-libs/packages/ics23/src/tests/verify.cairo index d6f553d8..53acdd66 100644 --- a/cairo-libs/packages/ics23/src/tests/verify.cairo +++ b/cairo-libs/packages/ics23/src/tests/verify.cairo @@ -1,4 +1,7 @@ -use ics23::{LeafOp, LengthOp, HashOp, InnerOp, ExistenceProof, ExistenceProofImpl, SliceU32IntoArrayU8, byte_array_to_array_u8, encode_hex, decode_hex}; +use ics23::{ + LeafOp, LengthOp, HashOp, InnerOp, ExistenceProof, ExistenceProofImpl, SliceU32IntoArrayU8, + byte_array_to_array_u8, encode_hex, decode_hex +}; #[test] fn test_calculate_root_from_leaf() { @@ -11,15 +14,14 @@ fn test_calculate_root_from_leaf() { }; let proof = ExistenceProof { - key: "food", - value: byte_array_to_array_u8(@"some longer text"), - leaf, - path: array![], + key: "food", value: byte_array_to_array_u8(@"some longer text"), leaf, path: array![], }; let root = proof.calculate_root(); - assert_eq!(encode_hex(root.into()), "b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265"); + assert_eq!( + encode_hex(root.into()), "b68f5d298e915ae1753dd333da1f9cf605411a5f2e12516be6758f365e6db265" + ); } #[test] @@ -33,19 +35,16 @@ fn test_calculate_root_from_leaf_and_inner() { }; let inner = InnerOp { - hash: HashOp::Sha256, - prefix: decode_hex(@"deadbeef00cafe00"), - suffix: array![] + hash: HashOp::Sha256, prefix: decode_hex(@"deadbeef00cafe00"), suffix: array![] }; let proof = ExistenceProof { - key: "food", - value: byte_array_to_array_u8(@"some longer text"), - leaf, - path: array![inner], + key: "food", value: byte_array_to_array_u8(@"some longer text"), leaf, path: array![inner], }; let root = proof.calculate_root(); - assert_eq!(encode_hex(root.into()), "836ea236a6902a665c2a004c920364f24cad52ded20b1e4f22c3179bfe25b2a9"); + assert_eq!( + encode_hex(root.into()), "836ea236a6902a665c2a004c920364f24cad52ded20b1e4f22c3179bfe25b2a9" + ); } diff --git a/cairo-libs/packages/ics23/src/types.cairo b/cairo-libs/packages/ics23/src/types.cairo index 6415e608..0bb8791e 100644 --- a/cairo-libs/packages/ics23/src/types.cairo +++ b/cairo-libs/packages/ics23/src/types.cairo @@ -355,6 +355,11 @@ pub impl ProofSpecImpl of ProofSpecTrait { assert(self.min_depth < @0, ICS23Errors::INVALID_DEPTH_RANGE); assert(self.max_depth > self.min_depth, ICS23Errors::INVALID_DEPTH_RANGE); } + + fn is_iavl(self: @ProofSpec) -> bool { + let iavl_spec = Self::iavl(); + @iavl_spec.leaf_spec == self.leaf_spec && @iavl_spec.inner_spec == self.inner_spec + } } impl ProofSpecAsProtoMessage of ProtoMessage { diff --git a/cairo-libs/packages/ics23/src/verify.cairo b/cairo-libs/packages/ics23/src/verify.cairo index 25938335..8c39a1b8 100644 --- a/cairo-libs/packages/ics23/src/verify.cairo +++ b/cairo-libs/packages/ics23/src/verify.cairo @@ -1,7 +1,8 @@ use ics23::{ - Proof, ProofSpec, RootBytes, ICS23Errors, ExistenceProofImpl, NonExistenceProofImpl, - SliceU32IntoArrayU8 + Proof, ProofSpec, ProofSpecTrait, RootBytes, ICS23Errors, ExistenceProofImpl, + NonExistenceProofImpl, SliceU32IntoArrayU8, ExistenceProof, LeafOp, HashOp, InnerOp, }; +use protobuf::varint::decode_varint_from_u8_array; pub fn verify_membership( specs: Array, @@ -56,3 +57,73 @@ pub fn verify_non_membership( i += 1; }; } + +fn check_existence_spec(proof: ExistenceProof, spec: ProofSpec) { + if spec.is_iavl() { + ensure_leaf_prefix(proof.leaf.prefix.clone()); + } + ensure_leaf(@proof.leaf, @spec.leaf_spec); + + let inner_len = proof.path.len(); + if spec.min_depth != 0 { + assert(inner_len >= spec.min_depth, ICS23Errors::INVALID_INNER_OP_SIZE); + assert(inner_len <= spec.max_depth, ICS23Errors::INVALID_INNER_OP_SIZE); + } + + for i in 0 + ..inner_len { + let inner = proof.path.at(i); + if spec.is_iavl() { + ensure_inner_prefix(inner.prefix.clone(), i.try_into().unwrap(), inner.hash); + } + ensure_inner(inner, spec.clone()); + } +} + +fn ensure_leaf_prefix(prefix: Array) { + let rem = ensure_iavl_prefix(prefix, 0); + assert(rem == 0, ICS23Errors::INVALID_LEAF_PREFIX); +} + +fn ensure_iavl_prefix(prefix: Array, min_height: u64) -> u32 { + let mut prefix_bytes = prefix; + let (height, _) = decode_varint_from_u8_array(ref prefix_bytes); + assert(height > min_height, ICS23Errors::INVALID_IAVL_HEIGHT_PREFIX); + let (size, _) = decode_varint_from_u8_array(ref prefix_bytes); + assert(size > 0, ICS23Errors::ZERO_IAVL_SIZE_PREFIX); + let (version, _) = decode_varint_from_u8_array(ref prefix_bytes); + assert(version > 0, ICS23Errors::ZERO_IAVL_VERSION_PREFIX); + prefix_bytes.len() +} + +fn ensure_leaf(leaf: @LeafOp, leaf_spec: @LeafOp) { + assert(leaf.hash == leaf_spec.hash, ICS23Errors::INVALID_HASH_OP); + assert(leaf.prehash_key == leaf_spec.prehash_key, ICS23Errors::INVALID_PREHASH_KEY); + assert(leaf.prehash_value == leaf_spec.prehash_value, ICS23Errors::INVALID_PREHASH_VALUE); + assert(leaf.length == leaf_spec.length, ICS23Errors::INVALID_LENGTH_OP); + assert(leaf.prefix == leaf_spec.prefix, ICS23Errors::INVALID_LEAF_PREFIX); +} + +fn ensure_inner_prefix(prefix: Array, min_height: u64, hash_op: @HashOp) { + let rem = ensure_iavl_prefix(prefix, min_height); + assert(rem == 0 || rem == 1 || rem == 34, ICS23Errors::INVALID_INNER_PREFIX); + assert(hash_op == @HashOp::Sha256, ICS23Errors::INVALID_HASH_OP); +} + +fn ensure_inner(inner: @InnerOp, spec: ProofSpec) { + let inner_spec = spec.inner_spec; + assert(inner.hash == @inner_spec.hash, ICS23Errors::INVALID_HASH_OP); + assert(inner.prefix == @spec.leaf_spec.prefix, ICS23Errors::INVALID_INNER_PREFIX); + assert( + inner.prefix.len() >= inner_spec.min_prefix_length, ICS23Errors::INVALID_INNER_PREFIX_LEN + ); + assert( + inner.prefix.len() <= inner_spec.min_prefix_length, ICS23Errors::INVALID_INNER_PREFIX_LEN + ); + assert(inner_spec.child_size > 0, ICS23Errors::ZERO_CHILD_SIZE); + assert( + inner_spec.min_prefix_length + inner_spec.child_size > inner_spec.max_prefix_length, + ICS23Errors::INVALID_INNER_PREFIX_LEN + ); + assert(inner.suffix.len() % inner_spec.child_size == 0, ICS23Errors::INVALID_INNER_SUFFIX); +} diff --git a/cairo-libs/packages/protobuf/Scarb.toml b/cairo-libs/packages/protobuf/Scarb.toml index 5861535f..10465870 100644 --- a/cairo-libs/packages/protobuf/Scarb.toml +++ b/cairo-libs/packages/protobuf/Scarb.toml @@ -3,8 +3,14 @@ name = "protobuf" version = "0.1.0" edition = { workspace = true } +[lib] + +[scripts] +test = { workspace = true } + [dependencies] [dev-dependencies] -cairo_test = { workspace = true } -snforge_std = { workspace = true } +alexandria_math = { workspace = true } +cairo_test = { workspace = true } +snforge_std = { workspace = true } diff --git a/cairo-libs/packages/protobuf/src/errors.cairo b/cairo-libs/packages/protobuf/src/errors.cairo new file mode 100644 index 00000000..a40c0ebc --- /dev/null +++ b/cairo-libs/packages/protobuf/src/errors.cairo @@ -0,0 +1,5 @@ +pub mod ProtobufErrors { + pub const INVALID_VARINT_SIZE: felt252 = 'Protobuf: invalid varint size'; + pub const INVALID_VARINT: felt252 = 'Protobuf: invalid varint'; + pub const OVERFLOWED_VARINT: felt252 = 'Protobuf: overflowed varint'; +} diff --git a/cairo-libs/packages/protobuf/src/lib.cairo b/cairo-libs/packages/protobuf/src/lib.cairo index e4708807..edc3f5f6 100644 --- a/cairo-libs/packages/protobuf/src/lib.cairo +++ b/cairo-libs/packages/protobuf/src/lib.cairo @@ -3,6 +3,11 @@ pub mod types; pub mod base64; pub mod hex; pub mod utils; +pub mod varint; +pub mod errors; #[cfg(test)] -mod tests; +mod tests { + mod proto; + mod varint; +} diff --git a/cairo-libs/packages/protobuf/src/primitives/numeric.cairo b/cairo-libs/packages/protobuf/src/primitives/numeric.cairo index 1ad6bb90..a2170b4f 100644 --- a/cairo-libs/packages/protobuf/src/primitives/numeric.cairo +++ b/cairo-libs/packages/protobuf/src/primitives/numeric.cairo @@ -3,9 +3,9 @@ use protobuf::types::message::{ }; use protobuf::types::tag::WireType; use protobuf::primitives::utils::{ - encode_varint_u64, decode_varint_u64, encode_2_complement_64, decode_2_complement_64, - encode_2_complement_32, decode_2_complement_32 + encode_2_complement_64, decode_2_complement_64, encode_2_complement_32, decode_2_complement_32 }; +use protobuf::varint::{encode_varint_to_byte_array, decode_varint_from_byte_array}; pub impl UnsignedAsProtoMessage< T, +Into, +TryInto, +Copy, +Drop @@ -13,12 +13,12 @@ pub impl UnsignedAsProtoMessage< fn encode_raw(self: @T, ref context: EncodeContext) { let num = (*self).into(); - let bytes = encode_varint_u64(@num); + let bytes = encode_varint_to_byte_array(num); context.buffer.append(@bytes); } fn decode_raw(ref self: T, ref context: DecodeContext) { - self = decode_varint_u64(context.buffer, ref context.index).try_into().unwrap() + self = decode_varint_from_byte_array(context.buffer, ref context.index).try_into().unwrap() } fn wire_type() -> WireType { diff --git a/cairo-libs/packages/protobuf/src/primitives/utils.cairo b/cairo-libs/packages/protobuf/src/primitives/utils.cairo index 85ce24f6..31a6df3c 100644 --- a/cairo-libs/packages/protobuf/src/primitives/utils.cairo +++ b/cairo-libs/packages/protobuf/src/primitives/utils.cairo @@ -1,42 +1,3 @@ -pub fn decode_varint_u64(bytes: @ByteArray, ref index: usize) -> u64 { - let mut value: u64 = 0; - let mut shift: u64 = 1; - let mut done = false; - while index < bytes.len() { - let byte = bytes[index]; - index += 1; - // 0x7F == 0x0111_1111 - value = value | ((byte & 0x7F).into() * shift); - if byte & 0x80 == 0 { - done = true; - break; - } - // 0x80 == 0x1000_0000 - shift *= 0x80; - }; - if !done { - panic!("invalid varint"); - } - value -} - -pub fn encode_varint_u64(value: @u64) -> ByteArray { - if value == @0 { - return "\x00"; - } - let mut bytes = ""; - let mut value = *value; - while value > 0 { - let mut byte: u8 = (value & 0x7F).try_into().unwrap(); - value = value / 0x80; - if value > 0 { - byte = byte | 0x80; - } - bytes.append_byte(byte); - }; - bytes -} - pub fn encode_2_complement_64(value: @i64) -> u64 { let value = *value; if value < 0 { @@ -77,30 +38,7 @@ pub fn decode_2_complement_32(value: @u32) -> i32 { #[cfg(test)] mod tests { - use super::{ - decode_varint_u64, encode_varint_u64, decode_2_complement_64, encode_2_complement_64 - }; - - use protobuf::hex::decode as hex_decode; - - #[test] - fn test_encode_varint_u64_default() { - assert_eq!(encode_varint_u64(@0), "\x00"); - let mut index = 0; - assert_eq!(decode_varint_u64(@"\x00", ref index), 0); - } - - #[test] - fn test_encode_decode_varint_u64() { - let value = 0x1234567890ABCDEF; - let bytes = encode_varint_u64(@value); - let hex = "ef9baf8589cf959a12"; - let bytes2 = hex_decode(@hex); - assert_eq!(bytes, bytes2, "invalid encoded bytes"); - let mut index = 0; - let decoded = decode_varint_u64(@bytes, ref index); - assert_eq!(decoded, value, "invalid decoded value"); - } + use super::{decode_2_complement_64, encode_2_complement_64}; #[test] fn test_encode_decode_2_complement_zero() { @@ -111,7 +49,6 @@ mod tests { assert_eq!(decoded, value, "invalid decoded value"); } - #[test] fn test_encode_decode_2_complement_one() { let value = 1; diff --git a/cairo-libs/packages/protobuf/src/tests.cairo b/cairo-libs/packages/protobuf/src/tests/proto.cairo similarity index 100% rename from cairo-libs/packages/protobuf/src/tests.cairo rename to cairo-libs/packages/protobuf/src/tests/proto.cairo diff --git a/cairo-libs/packages/protobuf/src/tests/varint.cairo b/cairo-libs/packages/protobuf/src/tests/varint.cairo new file mode 100644 index 00000000..56f0b3ce --- /dev/null +++ b/cairo-libs/packages/protobuf/src/tests/varint.cairo @@ -0,0 +1,48 @@ +use alexandria_math::pow; +use protobuf::varint::{ + encode_varint_to_byte_array, decode_varint_from_byte_array, encode_varint_to_u8_array +}; +use protobuf::hex::decode as hex_decode; + +fn assert_encode_varint(value: u32, expected: Array) { + assert_eq!(encode_varint_to_u8_array(value), expected); +} + +#[test] +fn test_encode_varint() { + assert_encode_varint(pow(2, 0) - 1, array![0x00]); + assert_encode_varint(pow(2, 0), array![0x01]); // 1 + assert_encode_varint(pow(2, 7) - 1, array![0x7F]); // 127 + assert_encode_varint(pow(2, 7), array![0x80, 0x01]); // 128 + assert_encode_varint(pow(2, 14) - 1, array![0xFF, 0x7F]); // [255, 127] + assert_encode_varint(pow(2, 14), array![0x80, 0x80, 0x01]); // [128, 128, 1] + assert_encode_varint(pow(2, 21) - 1, array![0xFF, 0xFF, 0x7F]); // [255, 255, 127] + assert_encode_varint(pow(2, 21), array![0x80, 0x80, 0x80, 0x01]); // [128, 128, 128, 1] + assert_encode_varint(pow(2, 28) - 1, array![0xFF, 0xFF, 0xFF, 0x7F]); // [255, 255, 255, 127] + assert_encode_varint( + pow(2, 28), array![0x80, 0x80, 0x80, 0x80, 0x01] + ); // [128, 128, 128, 128, 1] + assert_encode_varint( + 0xffffffff, array![0xFF, 0xFF, 0xFF, 0xFF, 0x0F] + ); // [255, 255, 255, 255, 15] +} + +#[test] +fn test_encode_varint_u64_default() { + assert_eq!(encode_varint_to_byte_array(0), "\x00"); + let mut index = 0; + assert_eq!(decode_varint_from_byte_array(@"\x00", ref index), 0); +} + +#[test] +fn test_encode_decode_varint_u64() { + let value = 0x1234567890ABCDEF; + let bytes = encode_varint_to_byte_array(value); + let hex = "ef9baf8589cf959a12"; + let bytes2 = hex_decode(@hex); + assert_eq!(bytes, bytes2, "invalid encoded bytes"); + let mut index = 0; + let decoded = decode_varint_from_byte_array(@bytes, ref index); + assert_eq!(decoded, value, "invalid decoded value"); +} + diff --git a/cairo-libs/packages/protobuf/src/varint.cairo b/cairo-libs/packages/protobuf/src/varint.cairo new file mode 100644 index 00000000..6bb8d4a2 --- /dev/null +++ b/cairo-libs/packages/protobuf/src/varint.cairo @@ -0,0 +1,91 @@ +use protobuf::errors::ProtobufErrors; + +/// Implements variable-value encoding based on LEB128 +#[inline] +pub fn encode_varint_to_u8_array(value: u32) -> Array { + let mut result: Array = ArrayTrait::new(); + let mut value = value; + for _ in 0 + ..10_u32 { + if value < 0x80 { + result.append(value.try_into().unwrap()); + break; + } else { + let remaining = (value & 0x7F) | 0x80; + result.append(remaining.try_into().unwrap()); + value /= 0x80; + }; + }; + result +} + +#[inline] +pub fn encode_varint_to_byte_array(value: u64) -> ByteArray { + let mut result: ByteArray = ""; + let mut value = value; + for _ in 0 + ..10_u32 { + if value < 0x80 { + result.append_byte(value.try_into().unwrap()); + break; + } else { + let remaining = (value & 0x7F) | 0x80; + result.append_byte(remaining.try_into().unwrap()); + value /= 0x80; + }; + }; + result +} + +/// Decodes a LEB128-encoded variable length integer from a slice that might contain a number of +/// ints, returning the list of values + number of bytes read. +#[inline] +pub fn decode_varint_from_u8_array(ref bytes: Array) -> (u64, u32) { + let len = bytes.len(); + assert(len > 0, ProtobufErrors::INVALID_VARINT_SIZE); + + let mut num_of_read = 0; + let mut value = 0; + let mut shift = 1; + while num_of_read < 10 { + let byte = bytes.pop_front().unwrap(); + assert(!(num_of_read == 9 && byte > 0x01), ProtobufErrors::OVERFLOWED_VARINT); + + num_of_read += 1; + if num_of_read == 1 && byte < 0x80 { + value = byte.clone().try_into().unwrap(); + break; + }; + + value = value | ((byte & 0x7F).into() * shift); // 0x7F == 0x0111_1111 + shift *= 0x80; // 0x80 == 0x1000_0000 + + if byte & 0x80 == 0 { + break; + } + }; + + (value, num_of_read) +} + +#[inline] +pub fn decode_varint_from_byte_array(bytes: @ByteArray, ref index: usize) -> u64 { + let mut value: u64 = 0; + let mut shift: u64 = 1; + let mut done = false; + while index < bytes.len() { + let byte = bytes[index]; + index += 1; + // 0x7F == 0x0111_1111 + value = value | ((byte & 0x7F).into() * shift); + if byte & 0x80 == 0 { + done = true; + break; + } + // 0x80 == 0x1000_0000 + shift *= 0x80; + }; + assert(done, ProtobufErrors::INVALID_VARINT); + value +} + From 9df73d1e328a58cb0406a6fb0c76f7275260389f Mon Sep 17 00:00:00 2001 From: Farhad Shabani Date: Wed, 5 Feb 2025 07:02:10 -0800 Subject: [PATCH 10/10] fix: taplo --- cairo-libs/packages/ics23/Scarb.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cairo-libs/packages/ics23/Scarb.toml b/cairo-libs/packages/ics23/Scarb.toml index c34b8211..2ec42895 100644 --- a/cairo-libs/packages/ics23/Scarb.toml +++ b/cairo-libs/packages/ics23/Scarb.toml @@ -24,5 +24,5 @@ alexandria_numeric = { workspace = true } protobuf = { workspace = true } [dev-dependencies] -cairo_test = { workspace = true } -snforge_std = { workspace = true } +cairo_test = { workspace = true } +snforge_std = { workspace = true }