Skip to content

Commit

Permalink
feat: da verifier (#129)
Browse files Browse the repository at this point in the history
* Added doc

* test_verifier WIP

* test_verifier done

* Update

* Added doc

* test_rollup_inclusion_proofs WIP

* test_rollup_inclusion_proofs done

* TODOs comments

* Scarb fmt
  • Loading branch information
thomas192 authored Mar 26, 2024
1 parent 52d5cb7 commit ab995e5
Show file tree
Hide file tree
Showing 6 changed files with 1,215 additions and 104 deletions.
1 change: 0 additions & 1 deletion src/blobstreamx.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ mod blobstreamx {
data_root_bytes.append_u256(data_root.data_root);

let (is_proof_valid, _) = merkle_tree::verify(root, @proof, @data_root_bytes);

is_proof_valid
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/tree/binary/merkle_tree.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use blobstream_sn::tree::binary::hasher::{leaf_digest, node_digest};
use blobstream_sn::tree::binary::merkle_proof::BinaryMerkleProof;
use blobstream_sn::tree::utils::{path_length_from_key, get_split_point};

#[derive(Copy, Drop, PartialEq)]
#[derive(Copy, Drop, PartialEq, Debug)]
enum ErrorCodes {
NoError,
InvalidNumberOfSideNodes,
Expand Down
166 changes: 151 additions & 15 deletions src/verifier/da_verifier.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// The DAVerifier verifies that some shares, which were posted on Celestia, were committed to
/// by the BlobstreamX smart contract.
mod DAVerifier {
use alexandria_bytes::{Bytes, BytesTrait};
use blobstream_sn::interfaces::{IDAOracleDispatcher, IDAOracleDispatcherTrait};
Expand All @@ -8,9 +10,15 @@ mod DAVerifier {
};
use blobstream_sn::tree::namespace::{Namespace, NamespaceValueTrait};
use blobstream_sn::verifier::types::{SharesProof, AttestationProof};
use core::traits::TryInto;

// TODO: Error naming & other naming: to enum?
// I am not sure what is better practice, so far we have been using mostly
// modules and consts for errors in piltover and here
// I can make the change but I suppose we should stick to only one

// TODO: data_root_tuple -> data_root & data_root_tuple_root -> data_root?
// not sure what you mean here
mod Error {
const NoError: felt252 = 'NoError';
// The shares to the rows proof is invalid.
Expand All @@ -29,16 +37,24 @@ mod DAVerifier {
const UnequalDataLengthAndNumberOfSharesProofs: felt252 = 'UnequalDLandNSP';
// The number of leaves in the binary merkle proof is not divisible by 4.
const InvalidNumberOfLeavesInProof: felt252 = 'InvalidNumberOfLeavesInProof';
// The provided range is invalid.
const InvalidRange: felt252 = 'InvalidRange';
// The provided range is out of bounds.
const OutOfBoundsRange: felt252 = 'OutOfBoundsRange';
}

/// Verifies that the shares, which were posted to Celestia, were committed to by the Blobstream smart contract.
///
/// # Arguments
///
/// * `bridge` - The Blobstream smart contract instance.
/// * `shares_proof` - The proof of the shares to the data root tuple root.
/// * `root` - The data root of the block that contains the shares.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, Error::NoError otherwise.
fn verify_shares_to_data_root_tuple_root(
bridge: IDAOracleDispatcher, shares_proof: SharesProof, root: u256
) -> (bool, felt252) {
// checking that the data root was committed to by the Blobstream smart contract.
// check that the data root was committed to by the Blobstream smart contract.
let (success, error) = verify_multi_row_roots_to_data_root_tuple_root(
bridge,
shares_proof.row_roots.span(),
Expand All @@ -49,7 +65,6 @@ mod DAVerifier {
if !success {
return (false, error);
}

return verify_shares_to_data_root_tuple_root_proof(
shares_proof.data.span(),
shares_proof.share_proofs.span(),
Expand All @@ -60,6 +75,21 @@ mod DAVerifier {
);
}

/// Verifies the shares to data root tuple root proof.
///
/// # Arguments
///
/// * `data` - The data that needs to be proven.
/// * `share_proofs` - The share to the row roots proof.
/// * `namespace` - The namespace of the shares.
/// * `row_roots` - The row roots where the shares belong.
/// * `row_proofs` - The proofs of the rowRoots to the data root.
/// * `root` - The data root of the block that contains the shares.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, Error::NoError otherwise.
fn verify_shares_to_data_root_tuple_root_proof(
data: Span<Bytes>,
share_proofs: Span<NamespaceMerkleMultiproof>,
Expand All @@ -68,6 +98,7 @@ mod DAVerifier {
row_proofs: Span<BinaryMerkleProof>,
root: u256
) -> (bool, felt252) {
// check that the rows roots commit to the data root
let (success, error) = verify_multi_row_roots_to_data_root_tuple_root_proof(
row_roots, row_proofs, root
);
Expand All @@ -79,17 +110,23 @@ mod DAVerifier {
return (false, Error::UnequalShareProofsAndRowRootsNumber);
}

//TODO: to u32?
let mut number_of_shares_in_proofs: u256 = 0;
// TODO: to u32?
// currently max square size is 128 => extended square size 256
// max number of shares = 256 * 256 = 65,536 => fits in a u32
// to the extent of my understanding of the celestia protocol key values
// should then also fit inside a u32
// then, why are NamespaceMerkleMultiproof key fields u256 ?
let mut number_of_shares_in_proofs: u32 = 0;
let mut i: u32 = 0;
while i < share_proofs
.len() {
number_of_shares_in_proofs += *share_proofs.at(i).end_key
- *share_proofs.at(i).begin_key;
// should begin_key and end_key fields really be u256 ?
let diff = *share_proofs.at(i).end_key - *share_proofs.at(i).begin_key;
number_of_shares_in_proofs += diff.try_into().unwrap();
i += 1;
};

if data.len().into() != number_of_shares_in_proofs {
if data.len() != number_of_shares_in_proofs {
return (false, Error::UnequalDataLengthAndNumberOfSharesProofs);
}

Expand All @@ -100,6 +137,7 @@ mod DAVerifier {
.len() {
let shares_used: u256 = *share_proofs.at(i).end_key - *share_proofs.at(i).begin_key;
// TODO: Do i need to check for errors here & Span
// may be solved if NamespaceMerkleMultiproof used u32 fields
let s: Span<Bytes> = data.slice(cursor, cursor + shares_used.try_into().unwrap());
if !NamespaceMerkleTree::verify_multi(
*row_roots.at(i), share_proofs.at(i), namespace, s
Expand All @@ -118,9 +156,20 @@ mod DAVerifier {
return (true, Error::NoError);
}

// Verifies that a row/column root, from a Celestia block,
// was committed to by the Blobstream smart contract.
// Returns (success, error).
/// Verifies that a row/column root, from a Celestia block, was committed to by the Blobstream smart contract.
///
/// # Arguments
///
/// * `bridge` - The Blobstream smart contract instance.
/// * `row_root` - The row/column root to be proven.
/// * `row_proof` - The proof of the row/column root to the data root.
/// * `attestation_proof` - The proof of the data root tuple to the data root tuple root that was posted to the Blobstream contract.
/// * `root` - The data root of the block that contains the row.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, Error::NoError otherwise.
fn verify_row_root_to_data_root_tuple_root(
bridge: IDAOracleDispatcher,
row_root: NamespaceNode,
Expand All @@ -130,6 +179,10 @@ mod DAVerifier {
) -> (bool, felt252) {
// check that the data root was commited to by the Blobstream smart contract
// TODO: safe unwrap?
// a choice was made to use a u64 instead of a u256 (from the solidity contract)
// for the `state_proof_nonce` in the blobstreamx contract
// I suppose the `commit_nonce` field should actually be a u64 then which would
// solve the problem
if !bridge
.verify_attestation(
attestation_proof.commit_nonce.try_into().unwrap(),
Expand All @@ -139,9 +192,22 @@ mod DAVerifier {
return (false, Error::InvalidDataRootTupleToDataRootTupleRootProof);
}

// check that the row root commits to the data root
return verify_row_root_to_data_root_tuple_root_proof(row_root, row_proof, root);
}

/// Verifies that a row/column root proof, from a Celestia block, to its corresponding data root.
///
/// # Arguments
///
/// * `row_root` - The row/column root to be proven.
/// * `row_proof` - The proof of the row/column root to the data root.
/// * `root` - The data root of the block that contains the row.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, ErrorCodes.NoError otherwise.
fn verify_row_root_to_data_root_tuple_root_proof(
row_root: NamespaceNode, row_proof: BinaryMerkleProof, root: u256
) -> (bool, felt252) {
Expand All @@ -157,6 +223,20 @@ mod DAVerifier {
return (true, Error::NoError);
}

/// Verifies that a set of rows/columns, from a Celestia block, were committed to by the Blobstream smart contract.
///
/// # Arguments
///
/// * `bridge` - The Blobstream smart contract instance.
/// * `row_roots` - The set of row/column roots to be proved.
/// * `row_proofs` - The set of proofs of the _rowRoots in the same order.
/// * `attestation_proof` - The proof of the data root tuple to the data root tuple root that was posted to the Blobstream contract.
/// * `root` - The data root of the block that contains the rows.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, Error::NoError otherwise.
fn verify_multi_row_roots_to_data_root_tuple_root(
bridge: IDAOracleDispatcher,
row_roots: Span<NamespaceNode>,
Expand All @@ -174,9 +254,22 @@ mod DAVerifier {
return (false, Error::InvalidDataRootTupleToDataRootTupleRootProof);
}

// check that the rows roots commit to the data root
return verify_multi_row_roots_to_data_root_tuple_root_proof(row_roots, row_proofs, root);
}

/// Verifies the proof of a set of rows/columns, from a Celestia block, to their corresponding data root.
///
/// # Arguments
///
/// * `row_roots` - The set of row/column roots to be proved.
/// * `row_proofs` - The set of proofs of the _rowRoots in the same order.
/// * `root` - The data root of the block that contains the rows.
///
/// # Returns
///
/// * `true` if the proof is valid, `false` otherwise.
/// * An error code if the proof is invalid, Error::NoError otherwise.
fn verify_multi_row_roots_to_data_root_tuple_root_proof(
row_roots: Span<NamespaceNode>, row_proofs: Span<BinaryMerkleProof>, root: u256
) -> (bool, felt252) {
Expand All @@ -197,6 +290,7 @@ mod DAVerifier {
error = Error::InvalidRowToDataRootProof;
break;
}
i += 1;
};
if error != Error::NoError {
return (false, error);
Expand All @@ -205,15 +299,57 @@ mod DAVerifier {
return (true, Error::NoError);
}

/// Computes the Celestia block square size from a row/column root to data root binary Merkle proof.
///
/// Note: The provided proof is not authenticated to the Blobstream smart contract. It is the user's responsibility
/// to verify that the proof is valid and was successfully committed to using
/// the `verify_row_root_to_data_root_tuple_root()` function.
/// Note: The minimum square size is 1. Thus, we don't expect the proof to have number of leaves equal to 0.
///
/// # Arguments
///
/// * `proof` - The proof of the row/column root to the data root.
///
/// # Returns
///
/// * The square size of the corresponding block.
/// * An error code if the `proof` is invalid, `Error::NoError` otherwise.
fn compute_square_size_from_row_proof(proof: BinaryMerkleProof) -> (u256, felt252) {
if proof.num_leaves % 4 != 0 {
return (0, Error::InvalidNumberOfLeavesInProof);
}
return (proof.num_leaves / 4, Error::NoError);
}

/// Computes the Celestia block square size from a shares to row/column root proof.
///
/// Note: The provided proof is not authenticated to the Blobstream smart contract. It is the user's responsibility
/// to verify that the proof is valid and that the shares were successfully committed to using
/// the `verify_shares_to_data_root_tuple_root()` function.
/// Note: The minimum square size is 1. Thus, we don't expect the proof to be devoid of any side nodes.
///
/// # Arguments
///
/// * `proof` - The proof of the shares to the row/column root.
///
/// # Returns
///
/// * The square size of the corresponding block.
fn compute_square_size_from_share_proof(proof: NamespaceMerkleMultiproof) -> u256 {
let extended_square_row_size = proof.side_nodes.len();
// TODO
// `i` could actually fit in a u8
// currently max square size is 128 => extended square size is 256
// max number of side nodes in a a binary tree with 256 leaves is log2(256) = 8
let mut i: u32 = 0;
// same for `extended_square_row_size`, but defining them as u32 may provide some
// flexibility if celestia protocol changes
let mut extended_square_row_size: u32 = 1;
while i < proof.side_nodes.len() {
extended_square_row_size *= 2;
i += 1;
};
// we divide the extended square row size by 2 because the square size is the
// the size of the row of the original square size.
return extended_square_row_size.into() / 2;
}
}
Loading

0 comments on commit ab995e5

Please sign in to comment.