diff --git a/.github/workflows/cairo-ci.yaml b/.github/workflows/cairo-ci.yaml index d939916d..eb19383e 100644 --- a/.github/workflows/cairo-ci.yaml +++ b/.github/workflows/cairo-ci.yaml @@ -25,6 +25,6 @@ jobs: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 with: - scarb-version: "2.6.4" + scarb-version: "nightly-2024-04-24" - run: scarb fmt --check - run: scarb test diff --git a/stwo_cairo_verifier/.tool-versions b/stwo_cairo_verifier/.tool-versions new file mode 100644 index 00000000..13f722f4 --- /dev/null +++ b/stwo_cairo_verifier/.tool-versions @@ -0,0 +1 @@ +scarb nightly-2024-04-24 diff --git a/stwo_cairo_verifier/src/fields.cairo b/stwo_cairo_verifier/src/fields.cairo index 57547036..146e65d6 100644 --- a/stwo_cairo_verifier/src/fields.cairo +++ b/stwo_cairo_verifier/src/fields.cairo @@ -1,2 +1,4 @@ pub mod m31; pub mod cm31; + +pub type BaseField = m31::M31; diff --git a/stwo_cairo_verifier/src/fields/m31.cairo b/stwo_cairo_verifier/src/fields/m31.cairo index 595743e3..20d97db4 100644 --- a/stwo_cairo_verifier/src/fields/m31.cairo +++ b/stwo_cairo_verifier/src/fields/m31.cairo @@ -8,7 +8,7 @@ const P64NZ: NonZero = 0x7fffffff; #[derive(Copy, Drop, Debug, PartialEq, Eq)] pub struct M31 { - inner: u32 + pub inner: u32 } #[generate_trait] diff --git a/stwo_cairo_verifier/src/lib.cairo b/stwo_cairo_verifier/src/lib.cairo index e95030d6..583a65af 100644 --- a/stwo_cairo_verifier/src/lib.cairo +++ b/stwo_cairo_verifier/src/lib.cairo @@ -1,3 +1,6 @@ mod fields; +mod vcs; + +pub use fields::BaseField; fn main() {} diff --git a/stwo_cairo_verifier/src/vcs.cairo b/stwo_cairo_verifier/src/vcs.cairo new file mode 100644 index 00000000..7fb23b0c --- /dev/null +++ b/stwo_cairo_verifier/src/vcs.cairo @@ -0,0 +1,74 @@ +use core::array::ArrayTrait; +use core::option::OptionTrait; +use core::poseidon::poseidon_hash_span; +use stwo_cairo_verifier::BaseField; + +// A Merkle node hash is a hash of: +// [left_child_hash, right_child_hash], column0_value, column1_value, ... +// "[]" denotes optional values. +// The largest Merkle layer has no left and right child hashes. The rest of the layers have +// children hashes. +// At each layer, the tree may have multiple columns of the same length as the layer. +// Each node in that layer contains one value from each column. +pub trait MerkleHasher { + type Hash; + // Hashes a single Merkle node. + fn hash_node( + children_hashes: Option<(Self::Hash, Self::Hash)>, column_values: Array, + ) -> Self::Hash; +} + +// 8 M31 elements fit in a hash, since 31*8 = 242 < 252. +const M31_ELS_IN_HASH: usize = 8; +const M31_ELS_IN_HASH_MINUS1: usize = M31_ELS_IN_HASH - 1; +const M31_IN_HASH_SHIFT: felt252 = 0x80000000; // 2**31. +pub impl PoseidonMerkleHasher of MerkleHasher { + type Hash = felt252; + + fn hash_node( + children_hashes: Option<(Self::Hash, Self::Hash)>, mut column_values: Array, + ) -> Self::Hash { + let mut hash_array: Array = Default::default(); + if let Option::Some((x, y)) = children_hashes { + hash_array.append(x); + hash_array.append(y); + } + + // Pad column_values to a multiple of 8. + let mut pad_len = M31_ELS_IN_HASH_MINUS1 + - ((column_values.len() + M31_ELS_IN_HASH_MINUS1) % M31_ELS_IN_HASH); + while pad_len > 0 { + column_values.append(core::num::traits::Zero::zero()); + pad_len = M31_ELS_IN_HASH_MINUS1 + - ((column_values.len() + M31_ELS_IN_HASH_MINUS1) % M31_ELS_IN_HASH); + }; + + while !column_values.is_empty() { + let mut word = 0; + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + word = word * M31_IN_HASH_SHIFT + column_values.pop_front().unwrap().inner.into(); + }; + + poseidon_hash_span(hash_array.span()) + } +} + +#[cfg(test)] +mod tests { + use super::PoseidonMerkleHasher; + use stwo_cairo_verifier::fields::m31::{m31}; + + #[test] + fn test_m31() { + assert_eq!( + PoseidonMerkleHasher::hash_node(Option::None, array![m31(0), m31(1)]), + 973835572668429495915136902981656666590582180872133591629269551720657739196 + ); + } +}