Skip to content

Commit

Permalink
MerkleVerifier
Browse files Browse the repository at this point in the history
  • Loading branch information
spapinistarkware committed Jun 20, 2024
1 parent 9e0451a commit e830f42
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 76 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cairo-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ jobs:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: "nightly-2024-04-24"
scarb-version: "nightly-2024-06-01"
- run: scarb fmt --check
- run: scarb test
2 changes: 1 addition & 1 deletion stwo_cairo_verifier/.tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
scarb nightly-2024-04-24
scarb nightly-2024-06-01
1 change: 1 addition & 0 deletions stwo_cairo_verifier/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod fields;
mod utils;
mod vcs;

pub use fields::BaseField;
Expand Down
71 changes: 71 additions & 0 deletions stwo_cairo_verifier/src/utils.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use core::array::SpanTrait;
use core::traits::PanicDestruct;
use core::option::OptionTrait;
use core::box::BoxTrait;
use core::dict::Felt252DictEntryTrait;
use core::dict::Felt252DictTrait;
use core::iter::Iterator;

#[generate_trait]
pub impl DictImpl<T, +Felt252DictValue<T>, +PanicDestruct<T>> of DictTrait<T> {
fn replace(ref self: Felt252Dict<T>, key: felt252, new_value: T) -> T {
let (entry, value) = self.entry(key);
self = entry.finalize(new_value);
value
}
}

#[generate_trait]
pub impl OptBoxImpl<T> of OptBoxTrait<T> {
fn as_unboxed(self: Option<Box<T>>) -> Option<T> {
match self {
Option::Some(value) => Option::Some(value.unbox()),
Option::None => Option::None,
}
}
}

#[generate_trait]
pub impl ArrayImpl<T, +Copy<T>, +Drop<T>> of ArrayExTrait<T> {
fn pop_n(ref self: Array<T>, mut n: usize) -> Array<T> {
let mut res = array![];
while n != 0 {
if let Option::Some(value) = self.pop_front() {
res.append(value);
} else {
break;
}
n -= 1;
};
res
}
fn max<+PartialOrd<T>>(mut self: @Array<T>) -> Option<@T> {
self.span().max()
}
}

#[generate_trait]
pub impl SpanImpl<T> of SpanExTrait<T> {
fn next_if_eq<+PartialEq<T>>(ref self: Span<T>, other: @T) -> Option<@T> {
if let Option::Some(value) = self.get(0) {
if value.unbox() == other {
return self.pop_front();
}
}
Option::None
}

fn max<+PartialOrd<T>, +Copy<T>>(mut self: Span<T>) -> Option<@T> {
let mut max = self.pop_front()?;
loop {
if let Option::Some(next) = self.pop_front() {
if *next > *max {
max = next;
}
} else {
break;
}
};
Option::Some(max)
}
}
76 changes: 2 additions & 74 deletions stwo_cairo_verifier/src/vcs.cairo
Original file line number Diff line number Diff line change
@@ -1,74 +1,2 @@
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<BaseField>,
) -> 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<BaseField>,
) -> Self::Hash {
let mut hash_array: Array<felt252> = 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
);
}
}
mod hasher;
mod verifier;
81 changes: 81 additions & 0 deletions stwo_cairo_verifier/src/vcs/hasher.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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<BaseField>,
) -> Self::Hash;
}

// 8 M31 elements fit in a hash, since 31*8 = 242 < 252.
const M31_ELEMENETS_IN_HASH: usize = 8;
const M31_ELEMENETS_IN_HASH_MINUS1: usize = M31_ELEMENETS_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<BaseField>,
) -> Self::Hash {
let mut hash_array: Array<felt252> = 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_ELEMENETS_IN_HASH_MINUS1
- ((column_values.len() + M31_ELEMENETS_IN_HASH_MINUS1) % M31_ELEMENETS_IN_HASH);
while pad_len > 0 {
column_values.append(core::num::traits::Zero::zero());
pad_len = M31_ELEMENETS_IN_HASH_MINUS1
- ((column_values.len() + M31_ELEMENETS_IN_HASH_MINUS1) % M31_ELEMENETS_IN_HASH);
};

while !column_values.is_empty() {
let mut word = 0;
// Hash M31_ELEMENETS_IN_HASH = 8 M31 elements into a single field element.
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();
hash_array.append(word);
};

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)]),
2552053700073128806553921687214114320458351061521275103654266875084493044716
);

assert_eq!(
PoseidonMerkleHasher::hash_node(Option::Some((1, 2)), array![m31(3)]),
159358216886023795422515519110998391754567506678525778721401012606792642769
);
}
}
Loading

0 comments on commit e830f42

Please sign in to comment.