diff --git a/Cargo.lock b/Cargo.lock index cb4d57fc..ef9b1bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4826,9 +4826,6 @@ version = "0.1.0" dependencies = [ "anyhow", "celestia-types", - "jmt", - "log", - "prism-errors", "prism-keys", "prism-serde", "rand", @@ -4904,6 +4901,7 @@ dependencies = [ "prism-errors", "prism-keys", "prism-storage", + "prism-tree", "serde", "sp1-sdk", "tokio", @@ -4929,6 +4927,7 @@ name = "prism-sp1" version = "0.1.0" dependencies = [ "prism-common", + "prism-tree", "sp1-zkvm", ] @@ -4968,6 +4967,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "prism-tree" +version = "0.1.0" +dependencies = [ + "anyhow", + "jmt", + "log", + "prism-common", + "prism-errors", + "prism-keys", + "prism-serde", + "serde", + "sha2 0.10.8", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index 33c71fed..c018660c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ default-members = [ "crates/da", "crates/serde", "crates/keys", + "crates/tree", ] members = [ @@ -40,6 +41,7 @@ members = [ "crates/da", "crates/serde", "crates/keys", + "crates/tree", ] resolver = "2" @@ -117,6 +119,7 @@ prism-prover = { path = "crates/node_types/prover" } prism-tests = { path = "crates/tests" } prism-keys = { path = "crates/keys" } prism-serde = { path = "crates/serde" } +prism-tree = { path = "crates/tree" } prism-lightclient = { path = "crates/node_types/lightclient" } [patch.crates-io] diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 20dfba28..dcb0f6e8 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -8,7 +8,6 @@ repository.workspace = true [dependencies] # prism -prism-errors.workspace = true prism-keys.workspace = true # serde @@ -18,13 +17,11 @@ serde.workspace = true # celestia celestia-types.workspace = true -# tree -jmt.workspace = true +# digest sha2.workspace = true # misc anyhow.workspace = true -log.workspace = true rand.workspace = true [features] diff --git a/crates/common/src/digest.rs b/crates/common/src/digest.rs index eeab671c..cc0d0af8 100644 --- a/crates/common/src/digest.rs +++ b/crates/common/src/digest.rs @@ -1,13 +1,12 @@ use anyhow::Result; -use jmt::RootHash; use serde::{Deserialize, Serialize}; -use crate::hasher::Hasher; use prism_serde::{ base64::FromBase64, hex::{FromHex, ToHex}, raw_or_hex, }; +use sha2::{Digest as _, Sha256}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)] pub struct Digest(#[serde(with = "raw_or_hex")] pub [u8; 32]); @@ -18,17 +17,17 @@ impl Digest { } pub fn hash(data: impl AsRef<[u8]>) -> Self { - let mut hasher = Hasher::new(); + let mut hasher = Sha256::new(); hasher.update(data.as_ref()); - Self(hasher.finalize()) + Self(hasher.finalize().into()) } pub fn hash_items(items: &[impl AsRef<[u8]>]) -> Self { - let mut hasher = Hasher::new(); + let mut hasher = Sha256::new(); for item in items { hasher.update(item.as_ref()); } - Self(hasher.finalize()) + Self(hasher.finalize().into()) } pub const fn zero() -> Self { @@ -52,18 +51,6 @@ impl From<[u8; N]> for Digest { } } -impl From for RootHash { - fn from(val: Digest) -> RootHash { - RootHash::from(val.0) - } -} - -impl From for Digest { - fn from(val: RootHash) -> Digest { - Digest(val.0) - } -} - impl AsRef<[u8]> for Digest { fn as_ref(&self) -> &[u8] { &self.0 diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index c6e4d4a2..a9882db6 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,12 +1,7 @@ pub mod digest; pub mod hashchain; -pub mod hasher; pub mod operation; pub mod transaction; -pub mod tree; - -#[macro_use] -extern crate log; #[cfg(feature = "test_utils")] pub mod transaction_builder; diff --git a/crates/common/src/tree/mod.rs b/crates/common/src/tree/mod.rs deleted file mode 100644 index bb5da3ef..00000000 --- a/crates/common/src/tree/mod.rs +++ /dev/null @@ -1,473 +0,0 @@ -use crate::hashchain::Hashchain; - -mod key_directory_tree; -mod proofs; -mod snarkable_tree; - -pub use key_directory_tree::*; -pub use proofs::*; -pub use snarkable_tree::*; - -/// Enumerates possible responses when fetching tree values -#[derive(Debug)] -pub enum HashchainResponse { - /// When a hashchain was found, provides the value and its corresponding membership-proof - Found(Hashchain, MembershipProof), - - /// When no hashchain was found for a specific key, provides the corresponding non-membership-proof - NotFound(NonMembershipProof), -} - -#[cfg(all(test, feature = "test_utils"))] -mod tests { - use std::sync::Arc; - - use jmt::{mock::MockTreeStore, KeyHash}; - use prism_keys::SigningKey; - - use super::{HashchainResponse::*, *}; - use crate::{digest::Digest, hasher::Hasher, transaction_builder::TransactionBuilder}; - - fn test_insert_and_get(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { - panic!("Processing transaction did not return the expected insert proof"); - }; - assert!(insert_proof.verify().is_ok()); - - let account_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - - let Proof::Insert(insert_proof) = tree.process_transaction(account_tx).unwrap() else { - panic!("Processing transaction did not return the expected insert proof"); - }; - assert!(insert_proof.verify().is_ok()); - - let Found(hashchain, membership_proof) = - tree.get(KeyHash::with::("acc_1")).unwrap() - else { - panic!("Expected hashchain to be found, but was not found.") - }; - - let test_hashchain = - tx_builder.get_hashchain("acc_1").expect("Getting builder hashchain should work"); - - assert_eq!(&hashchain, test_hashchain); - assert!(membership_proof.verify().is_ok()); - } - - #[test] - fn test_insert_and_get_ed25519() { - test_insert_and_get("ed25519"); - } - - #[test] - fn test_insert_and_get_secp256k1() { - test_insert_and_get("secp256k1"); - } - - #[test] - fn test_insert_and_get_secp256r1() { - test_insert_and_get("secp256r1"); - } - - fn test_insert_for_nonexistent_service_fails(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_signing_key = SigningKey::new_with_algorithm(algorithm); - - let invalid_account_tx = tx_builder - .create_account_with_random_key( - algorithm, - "acc_1", - "service_id_that_does_not_exist", - &service_signing_key, - ) - .build(); - - let insertion_result = tree.process_transaction(invalid_account_tx); - assert!(insertion_result.is_err()); - } - - #[test] - fn test_insert_for_nonexistent_service_fails_ed25519() { - test_insert_for_nonexistent_service_fails("ed25519"); - } - - #[test] - fn test_insert_for_nonexistent_service_fails_secp256k1() { - test_insert_for_nonexistent_service_fails("secp256k1"); - } - - #[test] - fn test_insert_for_nonexistent_service_fails_secp256r1() { - test_insert_for_nonexistent_service_fails("secp256r1"); - } - - fn test_insert_with_invalid_service_challenge_fails(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - - // The correct way was to use the key from service registration, - // but here we want things to break - let incorrect_service_signing_key = SigningKey::new_with_algorithm(algorithm); - - let initial_acc_signing_key = SigningKey::new_with_algorithm(algorithm); - - let acc_with_invalid_challenge_tx = tx_builder - .create_account( - "key_1", - "service_1", - &incorrect_service_signing_key, - initial_acc_signing_key, - ) - .build(); - - let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { - panic!("Processing service registration failed") - }; - assert!(insert_proof.verify().is_ok()); - - let create_account_result = tree.process_transaction(acc_with_invalid_challenge_tx); - assert!(create_account_result.is_err()); - } - - #[test] - fn test_insert_with_invalid_service_challenge_fails_ed25519() { - test_insert_with_invalid_service_challenge_fails("ed25519"); - } - - #[test] - fn test_insert_with_invalid_service_challenge_fails_secp256k1() { - test_insert_with_invalid_service_challenge_fails("secp256k1"); - } - - #[test] - fn test_insert_with_invalid_service_challenge_fails_secp256r1() { - test_insert_with_invalid_service_challenge_fails("secp256r1"); - } - - fn test_insert_duplicate_key(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let account_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - let account_with_same_id_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").build(); - - let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { - panic!("Processing service registration failed") - }; - assert!(insert_proof.verify().is_ok()); - - let Proof::Insert(insert_proof) = tree.process_transaction(account_tx).unwrap() else { - panic!("Processing Account creation failed") - }; - assert!(insert_proof.verify().is_ok()); - - let create_acc_with_same_id_result = tree.process_transaction(account_with_same_id_tx); - assert!(create_acc_with_same_id_result.is_err()); - } - - #[test] - fn test_insert_duplicate_key_ed25519() { - test_insert_duplicate_key("ed25519"); - } - - #[test] - fn test_insert_duplicate_key_secp256k1() { - test_insert_duplicate_key("secp256k1"); - } - - #[test] - fn test_insert_duplicate_key_secp256r1() { - test_insert_duplicate_key("secp256r1"); - } - - fn test_update_existing_key(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let acc_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - tree.process_transaction(acc_tx).unwrap(); - - let key_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); - - let Proof::Update(update_proof) = tree.process_transaction(key_tx).unwrap() else { - panic!("Processing key update failed") - }; - assert!(update_proof.verify().is_ok()); - - let get_result = tree.get(KeyHash::with::("acc_1")).unwrap(); - let test_hashchain = tx_builder.get_hashchain("acc_1").unwrap(); - - assert!(matches!(get_result, Found(hc, _) if &hc == test_hashchain)); - } - - #[test] - fn test_update_existing_key_ed25519() { - test_update_existing_key("ed25519"); - } - - #[test] - fn test_update_existing_key_secp256k1() { - test_update_existing_key("secp256k1"); - } - - #[test] - fn test_update_existing_key_secp256r1() { - test_update_existing_key("secp256r1"); - } - - fn test_update_non_existing_key(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - - // This is a signing key not known to the storage yet - let random_signing_key = SigningKey::new_with_algorithm(algorithm); - // This transaction shall be invalid, because it is signed with an unknown key - let invalid_key_tx = tx_builder.add_random_key(algorithm, "acc_1", &random_signing_key, 0).build(); - - let result = tree.process_transaction(invalid_key_tx); - assert!(result.is_err()); - } - - #[test] - fn test_update_non_existing_key_ed25519() { - test_update_non_existing_key("ed25519"); - } - - #[test] - fn test_update_non_existing_key_secp256k1() { - test_update_non_existing_key("secp256k1"); - } - - #[test] - fn test_update_non_existing_key_secp256r1() { - test_update_non_existing_key("secp256r1"); - } - - #[test] - fn test_get_non_existing_key() { - let tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - - let result = tree.get(KeyHash::with::("non_existing_id")).unwrap(); - - let NotFound(non_membership_proof) = result else { - panic!("Hashchain found for key while it was expected to be missing"); - }; - - assert!(non_membership_proof.verify().is_ok()); - } - - fn test_multiple_inserts_and_updates(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let acc1_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - let acc2_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - - tree.process_transaction(acc1_tx).unwrap(); - tree.process_transaction(acc2_tx).unwrap(); - - // Do insert and update accounts using the correct key indices - let key_1_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); - tree.process_transaction(key_1_tx).unwrap(); - - let data_1_tx = - tx_builder.add_unsigned_data_verified_with_root("acc_2", b"unsigned".to_vec()).commit(); - tree.process_transaction(data_1_tx).unwrap(); - - let data_2_tx = tx_builder - .add_randomly_signed_data_verified_with_root(algorithm, "acc_2", b"signed".to_vec()) - .commit(); - tree.process_transaction(data_2_tx).unwrap(); - - let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); - let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); - - let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); - let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); - - assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); - assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); - } - - #[test] - fn test_multiple_inserts_and_updates_ed25519() { - test_multiple_inserts_and_updates("ed25519"); - } - - #[test] - fn test_multiple_inserts_and_updates_secp256k1() { - test_multiple_inserts_and_updates("secp256k1"); - } - - #[test] - fn test_multiple_inserts_and_updates_secp256r1() { - test_multiple_inserts_and_updates("secp256r1"); - } - - fn test_interleaved_inserts_and_updates(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let acc1_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - let acc2_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - tree.process_transaction(acc1_tx).unwrap(); - - let add_key_to_1_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); - tree.process_transaction(add_key_to_1_tx).unwrap(); - - tree.process_transaction(acc2_tx).unwrap(); - - let add_key_to_2_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_2").commit(); - let last_proof = tree.process_transaction(add_key_to_2_tx).unwrap(); - - // Update account_2 using the correct key index - let Proof::Update(update_proof) = last_proof else { - panic!("Expetced insert proof for transaction"); - }; - - let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); - let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); - - let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); - let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); - - assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); - assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); - assert_eq!( - Digest::from(update_proof.new_root), - tree.get_commitment().unwrap() - ); - } - - #[test] - fn test_interleaved_inserts_and_updates_ed25519() { - test_interleaved_inserts_and_updates("ed25519"); - } - - #[test] - fn test_interleaved_inserts_and_updates_secp256k1() { - test_interleaved_inserts_and_updates("secp256k1"); - } - - #[test] - fn test_interleaved_inserts_and_updates_secp256r1() { - test_interleaved_inserts_and_updates("secp256r1"); - } - - fn test_root_hash_changes(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let account1_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - - let root_before = tree.get_current_root().unwrap(); - tree.process_transaction(account1_tx).unwrap(); - let root_after = tree.get_current_root().unwrap(); - - assert_ne!(root_before, root_after); - } - - #[test] - fn test_root_hash_changes_ed25519() { - test_root_hash_changes("ed25519"); - } - - #[test] - fn test_root_hash_changes_secp256k1() { - test_root_hash_changes("secp256k1"); - } - - #[test] - fn test_root_hash_changes_secp256r1() { - test_root_hash_changes("secp256r1"); - } - - fn test_batch_writing(algorithm: &str) { - let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); - let mut tx_builder = TransactionBuilder::new(); - - let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); - let account1_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); - let account2_tx = - tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); - - tree.process_transaction(service_tx).unwrap(); - - println!("Inserting acc_1"); - tree.process_transaction(account1_tx).unwrap(); - - println!("Tree state after first insert: {:?}", tree.get_commitment()); - - // Try to get the first value immediately - let get_result1 = tree.get(KeyHash::with::("acc_1")); - println!("Get result for key1 after first write: {:?}", get_result1); - - println!("Inserting acc_2"); - tree.process_transaction(account2_tx).unwrap(); - - println!("Tree state after 2nd insert: {:?}", tree.get_commitment()); - - // Try to get both values - let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); - let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); - - println!("Final get result for key1: {:?}", get_result1); - println!("Final get result for key2: {:?}", get_result2); - - let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); - let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); - - assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); - assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); - } - - #[test] - fn test_batch_writing_ed25519() { - test_batch_writing("ed25519"); - } - - #[test] - fn test_batch_writing_secp256k1() { - test_batch_writing("secp256k1"); - } - - #[test] - fn test_batch_writing_secp256r1() { - test_batch_writing("secp256r1"); - } -} diff --git a/crates/node_types/prover/Cargo.toml b/crates/node_types/prover/Cargo.toml index 799993a6..0d4e330d 100644 --- a/crates/node_types/prover/Cargo.toml +++ b/crates/node_types/prover/Cargo.toml @@ -24,6 +24,7 @@ anyhow = { workspace = true } jmt = { workspace = true } prism-common = { workspace = true, features = ["test_utils"] } prism-storage = { workspace = true } +prism-tree = { workspace = true } prism-errors = { workspace = true } prism-keys = { workspace = true } prism-da = { workspace = true } diff --git a/crates/node_types/prover/src/prover/mod.rs b/crates/node_types/prover/src/prover/mod.rs index 13edc885..a7a5b7c4 100644 --- a/crates/node_types/prover/src/prover/mod.rs +++ b/crates/node_types/prover/src/prover/mod.rs @@ -1,18 +1,16 @@ use anyhow::{anyhow, bail, Context, Result}; use prism_keys::{SigningKey, VerifyingKey}; use jmt::KeyHash; -use prism_common::{ - digest::Digest, - hashchain::Hashchain, - hasher::Hasher, - transaction::Transaction, - tree::{ - Batch, - HashchainResponse::{self, *}, - KeyDirectoryTree, Proof, SnarkableTree, - }, -}; +use prism_common::{digest::Digest, hashchain::Hashchain, transaction::Transaction}; use prism_errors::DataAvailabilityError; +use prism_storage::database::Database; +use prism_tree::{ + hasher::TreeHasher, + key_directory_tree::KeyDirectoryTree, + proofs::{Batch, Proof}, + snarkable_tree::SnarkableTree, + HashchainResponse::{self, *}, +}; use std::{self, collections::VecDeque, sync::Arc}; use tokio::{ sync::{broadcast, RwLock}, @@ -22,7 +20,6 @@ use tokio::{ use crate::webserver::{WebServer, WebServerConfig}; use prism_common::operation::Operation; use prism_da::{DataAvailabilityLayer, FinalizedEpoch}; -use prism_storage::Database; use sp1_sdk::{ProverClient, SP1ProvingKey, SP1Stdin, SP1VerifyingKey}; pub const PRISM_ELF: &[u8] = include_bytes!("../../../../../elf/riscv32im-succinct-zkvm-elf"); @@ -474,7 +471,7 @@ impl Prover { pub async fn get_hashchain(&self, id: &String) -> Result { let tree = self.tree.read().await; - let key_hash = KeyHash::with::(id); + let key_hash = KeyHash::with::(id); tree.get(key_hash) } diff --git a/crates/node_types/prover/src/prover/tests.rs b/crates/node_types/prover/src/prover/tests.rs index 2343da20..f5209711 100644 --- a/crates/node_types/prover/src/prover/tests.rs +++ b/crates/node_types/prover/src/prover/tests.rs @@ -1,11 +1,11 @@ use super::*; -use prism_common::{transaction_builder::TransactionBuilder, tree::Proof}; +use prism_common::transaction_builder::TransactionBuilder; use prism_keys::{SigningKey, VerifyingKey}; use std::{self, sync::Arc, time::Duration}; use tokio::spawn; use prism_da::memory::InMemoryDataAvailabilityLayer; -use prism_storage::{inmemory::InMemoryDatabase, Database}; +use prism_storage::inmemory::InMemoryDatabase; // Helper function to create a test prover instance async fn create_test_prover(algorithm: &str) -> Arc { diff --git a/crates/node_types/prover/src/webserver.rs b/crates/node_types/prover/src/webserver.rs index f9a2723f..81a69f3b 100644 --- a/crates/node_types/prover/src/webserver.rs +++ b/crates/node_types/prover/src/webserver.rs @@ -11,9 +11,12 @@ use jmt::proof::{SparseMerkleNode, SparseMerkleProof}; use prism_common::{ digest::Digest, hashchain::{Hashchain, HashchainEntry}, - hasher::Hasher, transaction::Transaction, - tree::{HashchainResponse, Proof, UpdateProof}, +}; +use prism_tree::{ + hasher::TreeHasher, + proofs::{Proof, UpdateProof}, + HashchainResponse, }; use serde::{Deserialize, Serialize}; use std::{self, sync::Arc}; @@ -80,13 +83,13 @@ pub struct JmtProofResponse { pub siblings: Vec, } -impl From> for JmtProofResponse { - fn from(proof: SparseMerkleProof) -> Self { - let leaf_hash = proof.leaf().map(|node| node.hash::()).map(Digest::new); +impl From> for JmtProofResponse { + fn from(proof: SparseMerkleProof) -> Self { + let leaf_hash = proof.leaf().map(|node| node.hash::()).map(Digest::new); let sibling_hashes = proof .siblings() .iter() - .map(SparseMerkleNode::hash::) + .map(SparseMerkleNode::hash::) .map(Digest::new) .collect(); Self { diff --git a/crates/tree/Cargo.toml b/crates/tree/Cargo.toml new file mode 100644 index 00000000..0005baf8 --- /dev/null +++ b/crates/tree/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "prism-tree" +version.workspace = true +authors.workspace = true +edition.workspace = true +description.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +readme.workspace = true + +[dependencies] +jmt.workspace = true +sha2.workspace = true +serde.workspace = true +log.workspace = true +anyhow.workspace = true +prism-common = { workspace = true, features = ["test_utils"] } +prism-errors.workspace = true +prism-keys.workspace = true +prism-serde.workspace = true + +[features] +default = [] diff --git a/crates/common/src/hasher.rs b/crates/tree/src/hasher.rs similarity index 73% rename from crates/common/src/hasher.rs rename to crates/tree/src/hasher.rs index 75dc6b3c..be3b0d47 100644 --- a/crates/common/src/hasher.rs +++ b/crates/tree/src/hasher.rs @@ -2,23 +2,23 @@ use jmt::SimpleHasher; use serde::{ser::SerializeTupleStruct, Deserialize, Serialize}; #[derive(Debug, Clone, Default)] -pub struct Hasher(sha2::Sha256); +pub struct TreeHasher(sha2::Sha256); -impl Hasher { - pub fn new() -> Self { +impl SimpleHasher for TreeHasher { + fn new() -> Self { Self(sha2::Sha256::new()) } - pub fn update(&mut self, data: &[u8]) { + fn update(&mut self, data: &[u8]) { self.0.update(data); } - pub fn finalize(self) -> [u8; 32] { + fn finalize(self) -> [u8; 32] { self.0.finalize() } } -impl Serialize for Hasher { +impl Serialize for TreeHasher { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -27,7 +27,7 @@ impl Serialize for Hasher { } } -impl<'de> Deserialize<'de> for Hasher { +impl<'de> Deserialize<'de> for TreeHasher { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -35,7 +35,7 @@ impl<'de> Deserialize<'de> for Hasher { struct Sha256WrapperVisitor; impl<'de> serde::de::Visitor<'de> for Sha256WrapperVisitor { - type Value = Hasher; + type Value = TreeHasher; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a Sha256Wrapper") @@ -45,24 +45,10 @@ impl<'de> Deserialize<'de> for Hasher { where A: serde::de::SeqAccess<'de>, { - Ok(Hasher::default()) + Ok(TreeHasher::default()) } } deserializer.deserialize_tuple_struct("Sha256Wrapper", 0, Sha256WrapperVisitor) } } - -impl SimpleHasher for Hasher { - fn new() -> Self { - Self::new() - } - - fn update(&mut self, data: &[u8]) { - self.update(data); - } - - fn finalize(self) -> [u8; 32] { - self.finalize() - } -} diff --git a/crates/common/src/tree/key_directory_tree.rs b/crates/tree/src/key_directory_tree.rs similarity index 84% rename from crates/common/src/tree/key_directory_tree.rs rename to crates/tree/src/key_directory_tree.rs index 9ca02cce..c7fd2483 100644 --- a/crates/common/src/tree/key_directory_tree.rs +++ b/crates/tree/src/key_directory_tree.rs @@ -4,12 +4,12 @@ use jmt::{ storage::{NodeBatch, TreeReader, TreeUpdateBatch, TreeWriter}, JellyfishMerkleTree, KeyHash, RootHash, }; +use prism_common::digest::Digest; use std::sync::Arc; -use crate::{digest::Digest, hasher::Hasher}; +use crate::hasher::TreeHasher; -pub const SPARSE_MERKLE_PLACEHOLDER_HASH: Digest = - Digest::new(*b"SPARSE_MERKLE_PLACEHOLDER_HASH__"); +pub const SPARSE_MERKLE_PLACEHOLDER_HASH: KeyHash = KeyHash(*b"SPARSE_MERKLE_PLACEHOLDER_HASH__"); /// Wraps a [`JellyfishMerkleTree`] to provide a key-value store for [`Hashchain`]s with batched insertions. /// This is prism's primary data structure for storing and retrieving [`Hashchain`]s. @@ -17,7 +17,7 @@ pub struct KeyDirectoryTree where S: TreeReader + TreeWriter, { - pub(crate) jmt: JellyfishMerkleTree, Hasher>, + pub(crate) jmt: JellyfishMerkleTree, TreeHasher>, pub(crate) epoch: u64, pending_batch: Option, db: Arc, @@ -30,7 +30,7 @@ where pub fn new(store: Arc) -> Self { let tree = Self { db: store.clone(), - jmt: JellyfishMerkleTree::, Hasher>::new(store), + jmt: JellyfishMerkleTree::, TreeHasher>::new(store), pending_batch: None, epoch: 0, }; @@ -48,7 +48,7 @@ where } Self { db: store.clone(), - jmt: JellyfishMerkleTree::, Hasher>::new(store), + jmt: JellyfishMerkleTree::, TreeHasher>::new(store), pending_batch: None, epoch, } diff --git a/crates/tree/src/lib.rs b/crates/tree/src/lib.rs new file mode 100644 index 00000000..70ecf12f --- /dev/null +++ b/crates/tree/src/lib.rs @@ -0,0 +1,20 @@ +pub mod hasher; +pub mod key_directory_tree; +pub mod proofs; +pub mod snarkable_tree; + +use prism_common::hashchain::Hashchain; +use proofs::{MembershipProof, NonMembershipProof}; + +/// Enumerates possible responses when fetching tree values +#[derive(Debug)] +pub enum HashchainResponse { + /// When a hashchain was found, provides the value and its corresponding membership-proof + Found(Hashchain, MembershipProof), + + /// When no hashchain was found for a specific key, provides the corresponding non-membership-proof + NotFound(NonMembershipProof), +} + +#[cfg(test)] +mod tests; diff --git a/crates/common/src/tree/proofs.rs b/crates/tree/src/proofs.rs similarity index 82% rename from crates/common/src/tree/proofs.rs rename to crates/tree/src/proofs.rs index e9957022..e1d64109 100644 --- a/crates/common/src/tree/proofs.rs +++ b/crates/tree/src/proofs.rs @@ -3,15 +3,14 @@ use jmt::{ proof::{SparseMerkleProof, UpdateMerkleProof}, KeyHash, RootHash, }; -use prism_serde::binary::ToBinary; -use serde::{Deserialize, Serialize}; -use std::convert::Into; - -use crate::{ +use prism_common::{ digest::Digest, hashchain::{Hashchain, HashchainEntry}, - hasher::Hasher, }; +use prism_serde::binary::ToBinary; +use serde::{Deserialize, Serialize}; + +use crate::hasher::TreeHasher; #[derive(Serialize, Deserialize)] /// Represents a contiguous stream of [`Proof`]s leading from [`Batch::prev_root`] to [`Batch::new_root`]. @@ -43,7 +42,7 @@ pub struct InsertProof { /// Post-insertion root hash of the tree pub new_root: Digest, /// Proof that the new hashchain is correctly inserted into the tree - pub membership_proof: SparseMerkleProof, + pub membership_proof: SparseMerkleProof, /// The new hashchain entry that was inserted. pub new_entry: HashchainEntry, @@ -58,7 +57,7 @@ impl InsertProof { let serialized_hashchain = hashchain.encode_to_bytes()?; self.membership_proof.clone().verify_existence( - self.new_root.into(), + RootHash(self.new_root.0), self.non_membership_proof.key, serialized_hashchain, )?; @@ -70,17 +69,17 @@ impl InsertProof { #[derive(Debug, Clone, Serialize, Deserialize)] /// Represents an update proof for an existing [`Hashchain`], updating it with a new [`HashchainEntry`]. pub struct UpdateProof { - pub old_root: RootHash, - pub new_root: RootHash, + pub old_root: Digest, + pub new_root: Digest, pub key: KeyHash, pub old_hashchain: Hashchain, pub new_entry: HashchainEntry, /// Inclusion proof of [`UpdateProof::old_hashchain`] - pub inclusion_proof: SparseMerkleProof, + pub inclusion_proof: SparseMerkleProof, /// Update proof for [`UpdateProof::key`] to be updated with [`UpdateProof::new_entry`] - pub update_proof: UpdateMerkleProof, + pub update_proof: UpdateMerkleProof, } impl UpdateProof { @@ -89,7 +88,11 @@ impl UpdateProof { // Verify existence of old value. // Otherwise, any arbitrary hashchain could be set as old_hashchain. let old_serialized_hashchain = self.old_hashchain.encode_to_bytes()?; - self.inclusion_proof.verify_existence(self.old_root, self.key, old_serialized_hashchain)?; + self.inclusion_proof.verify_existence( + RootHash(self.old_root.0), + self.key, + old_serialized_hashchain, + )?; let mut hashchain_after_update = self.old_hashchain.clone(); // Append the new entry and verify it's validity @@ -98,8 +101,8 @@ impl UpdateProof { // Ensure the update proof corresponds to the new hashchain value let new_serialized_hashchain = hashchain_after_update.encode_to_bytes()?; self.update_proof.clone().verify_update( - self.old_root, - self.new_root, + RootHash(self.old_root.0), + RootHash(self.new_root.0), vec![(self.key, Some(new_serialized_hashchain))], )?; @@ -110,7 +113,7 @@ impl UpdateProof { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MembershipProof { pub root: Digest, - pub proof: SparseMerkleProof, + pub proof: SparseMerkleProof, pub key: KeyHash, pub value: Hashchain, } @@ -118,19 +121,19 @@ pub struct MembershipProof { impl MembershipProof { pub fn verify(&self) -> Result<()> { let value = self.value.encode_to_bytes()?; - self.proof.verify_existence(self.root.into(), self.key, value) + self.proof.verify_existence(RootHash(self.root.0), self.key, value) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NonMembershipProof { pub root: Digest, - pub proof: SparseMerkleProof, + pub proof: SparseMerkleProof, pub key: KeyHash, } impl NonMembershipProof { pub fn verify(&self) -> Result<()> { - self.proof.verify_nonexistence(self.root.into(), self.key) + self.proof.verify_nonexistence(RootHash(self.root.0), self.key) } } diff --git a/crates/common/src/tree/snarkable_tree.rs b/crates/tree/src/snarkable_tree.rs similarity index 89% rename from crates/common/src/tree/snarkable_tree.rs rename to crates/tree/src/snarkable_tree.rs index f49edb71..fe53750f 100644 --- a/crates/common/src/tree/snarkable_tree.rs +++ b/crates/tree/src/snarkable_tree.rs @@ -3,17 +3,22 @@ use jmt::{ storage::{TreeReader, TreeWriter}, KeyHash, }; +use log::debug; use prism_errors::DatabaseError; use prism_serde::binary::{FromBinary, ToBinary}; -use std::convert::Into; -use crate::{ +use prism_common::{ digest::Digest, hashchain::{Hashchain, HashchainEntry}, - hasher::Hasher, operation::{Operation, ServiceChallenge, ServiceChallengeInput}, transaction::Transaction, - tree::{HashchainResponse::*, *}, +}; + +use crate::{ + hasher::TreeHasher, + key_directory_tree::KeyDirectoryTree, + proofs::{InsertProof, MembershipProof, NonMembershipProof, Proof, UpdateProof}, + HashchainResponse::{self, *}, }; /// Represents a tree that can be used to verifiably store and retrieve [`Hashchain`]s. @@ -28,12 +33,12 @@ pub trait SnarkableTree: Send + Sync { impl SnarkableTree for KeyDirectoryTree where - S: Send + Sync + TreeReader + TreeWriter, + S: TreeReader + TreeWriter + Send + Sync, { fn process_transaction(&mut self, transaction: Transaction) -> Result { match &transaction.entry.operation { Operation::AddKey { .. } | Operation::RevokeKey { .. } | Operation::AddData { .. } => { - let key_hash = KeyHash::with::(&transaction.id); + let key_hash = KeyHash::with::(&transaction.id); debug!("updating hashchain for user id {}", transaction.id); let proof = self.update(key_hash, transaction.entry)?; @@ -51,7 +56,7 @@ where "Id of transaction needs to be equal to operation id" ); - let account_key_hash = KeyHash::with::(id); + let account_key_hash = KeyHash::with::(id); // Verify that the account doesn't already exist if matches!(self.get(account_key_hash)?, Found(_, _)) { @@ -61,7 +66,7 @@ where ))); } - let service_key_hash = KeyHash::with::(service_id); + let service_key_hash = KeyHash::with::(service_id); let Found(service_hashchain, _) = self.get(service_key_hash)? else { bail!("Failed to get hashchain for service ID {}", service_id); @@ -98,7 +103,7 @@ where "Id of transaction needs to be equal to operation id" ); - let key_hash = KeyHash::with::(id); + let key_hash = KeyHash::with::(id); debug!("creating new hashchain for service id {}", id); @@ -109,13 +114,13 @@ where } fn insert(&mut self, key: KeyHash, entry: HashchainEntry) -> Result { - let old_root = self.get_current_root()?; + let old_root = self.get_commitment()?; let (None, non_membership_merkle_proof) = self.jmt.get_with_proof(key, self.epoch)? else { bail!("Key already exists"); }; let non_membership_proof = NonMembershipProof { - root: old_root.into(), + root: old_root, proof: non_membership_merkle_proof, key, }; @@ -133,7 +138,7 @@ where let (_, membership_proof) = self.jmt.get_with_proof(key, self.epoch)?; Ok(InsertProof { - new_root: new_root.into(), + new_root: Digest(new_root.0), new_entry: entry, non_membership_proof, membership_proof, @@ -163,8 +168,8 @@ where self.write_batch()?; Ok(UpdateProof { - old_root, - new_root, + old_root: Digest(old_root.0), + new_root: Digest(new_root.0), inclusion_proof, old_hashchain, key, @@ -174,7 +179,7 @@ where } fn get(&self, key: KeyHash) -> Result { - let root = self.get_current_root()?.into(); + let root = self.get_commitment()?; let (value, proof) = self.jmt.get_with_proof(key, self.epoch)?; match value { diff --git a/crates/tree/src/tests.rs b/crates/tree/src/tests.rs new file mode 100644 index 00000000..7eccee8a --- /dev/null +++ b/crates/tree/src/tests.rs @@ -0,0 +1,459 @@ +use std::sync::Arc; + +use jmt::{mock::MockTreeStore, KeyHash}; +use prism_common::transaction_builder::TransactionBuilder; +use prism_keys::SigningKey; + +use crate::{ + hasher::TreeHasher, key_directory_tree::KeyDirectoryTree, proofs::Proof, + snarkable_tree::SnarkableTree, HashchainResponse::*, +}; + +fn test_insert_and_get(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { + panic!("Processing transaction did not return the expected insert proof"); + }; + assert!(insert_proof.verify().is_ok()); + + let account_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + + let Proof::Insert(insert_proof) = tree.process_transaction(account_tx).unwrap() else { + panic!("Processing transaction did not return the expected insert proof"); + }; + assert!(insert_proof.verify().is_ok()); + + let Found(hashchain, membership_proof) = + tree.get(KeyHash::with::("acc_1")).unwrap() + else { + panic!("Expected hashchain to be found, but was not found.") + }; + + let test_hashchain = + tx_builder.get_hashchain("acc_1").expect("Getting builder hashchain should work"); + + assert_eq!(&hashchain, test_hashchain); + assert!(membership_proof.verify().is_ok()); +} + +#[test] +fn test_insert_and_get_ed25519() { + test_insert_and_get("ed25519"); +} + +#[test] +fn test_insert_and_get_secp256k1() { + test_insert_and_get("secp256k1"); +} + +#[test] +fn test_insert_and_get_secp256r1() { + test_insert_and_get("secp256r1"); +} + +fn test_insert_for_nonexistent_service_fails(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_signing_key = SigningKey::new_with_algorithm(algorithm); + + let invalid_account_tx = tx_builder + .create_account_with_random_key( + algorithm, + "acc_1", + "service_id_that_does_not_exist", + &service_signing_key, + ) + .build(); + + let insertion_result = tree.process_transaction(invalid_account_tx); + assert!(insertion_result.is_err()); +} + +#[test] +fn test_insert_for_nonexistent_service_fails_ed25519() { + test_insert_for_nonexistent_service_fails("ed25519"); +} + +#[test] +fn test_insert_for_nonexistent_service_fails_secp256k1() { + test_insert_for_nonexistent_service_fails("secp256k1"); +} + +#[test] +fn test_insert_for_nonexistent_service_fails_secp256r1() { + test_insert_for_nonexistent_service_fails("secp256r1"); +} + +fn test_insert_with_invalid_service_challenge_fails(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + + // The correct way was to use the key from service registration, + // but here we want things to break + let incorrect_service_signing_key = SigningKey::new_with_algorithm(algorithm); + + let initial_acc_signing_key = SigningKey::new_with_algorithm(algorithm); + + let acc_with_invalid_challenge_tx = tx_builder + .create_account( + "key_1", + "service_1", + &incorrect_service_signing_key, + initial_acc_signing_key, + ) + .build(); + + let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { + panic!("Processing service registration failed") + }; + assert!(insert_proof.verify().is_ok()); + + let create_account_result = tree.process_transaction(acc_with_invalid_challenge_tx); + assert!(create_account_result.is_err()); +} + +#[test] +fn test_insert_with_invalid_service_challenge_fails_ed25519() { + test_insert_with_invalid_service_challenge_fails("ed25519"); +} + +#[test] +fn test_insert_with_invalid_service_challenge_fails_secp256k1() { + test_insert_with_invalid_service_challenge_fails("secp256k1"); +} + +#[test] +fn test_insert_with_invalid_service_challenge_fails_secp256r1() { + test_insert_with_invalid_service_challenge_fails("secp256r1"); +} + +fn test_insert_duplicate_key(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let account_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + let account_with_same_id_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").build(); + + let Proof::Insert(insert_proof) = tree.process_transaction(service_tx).unwrap() else { + panic!("Processing service registration failed") + }; + assert!(insert_proof.verify().is_ok()); + + let Proof::Insert(insert_proof) = tree.process_transaction(account_tx).unwrap() else { + panic!("Processing Account creation failed") + }; + assert!(insert_proof.verify().is_ok()); + + let create_acc_with_same_id_result = tree.process_transaction(account_with_same_id_tx); + assert!(create_acc_with_same_id_result.is_err()); +} + +#[test] +fn test_insert_duplicate_key_ed25519() { + test_insert_duplicate_key("ed25519"); +} + +#[test] +fn test_insert_duplicate_key_secp256k1() { + test_insert_duplicate_key("secp256k1"); +} + +#[test] +fn test_insert_duplicate_key_secp256r1() { + test_insert_duplicate_key("secp256r1"); +} + +fn test_update_existing_key(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let acc_tx = tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + tree.process_transaction(acc_tx).unwrap(); + + let key_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); + + let Proof::Update(update_proof) = tree.process_transaction(key_tx).unwrap() else { + panic!("Processing key update failed") + }; + assert!(update_proof.verify().is_ok()); + + let get_result = tree.get(KeyHash::with::("acc_1")).unwrap(); + let test_hashchain = tx_builder.get_hashchain("acc_1").unwrap(); + + assert!(matches!(get_result, Found(hc, _) if &hc == test_hashchain)); +} + +#[test] +fn test_update_existing_key_ed25519() { + test_update_existing_key("ed25519"); +} + +#[test] +fn test_update_existing_key_secp256k1() { + test_update_existing_key("secp256k1"); +} + +#[test] +fn test_update_existing_key_secp256r1() { + test_update_existing_key("secp256r1"); +} + +fn test_update_non_existing_key(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + + // This is a signing key not known to the storage yet + let random_signing_key = SigningKey::new_with_algorithm(algorithm); + // This transaction shall be invalid, because it is signed with an unknown key + let invalid_key_tx = tx_builder.add_random_key(algorithm, "acc_1", &random_signing_key, 0).build(); + + let result = tree.process_transaction(invalid_key_tx); + assert!(result.is_err()); +} + +#[test] +fn test_update_non_existing_key_ed25519() { + test_update_non_existing_key("ed25519"); +} + +#[test] +fn test_update_non_existing_key_secp256k1() { + test_update_non_existing_key("secp256k1"); +} + +#[test] +fn test_update_non_existing_key_secp256r1() { + test_update_non_existing_key("secp256r1"); +} + +fn test_get_non_existing_key(algorithm: &str) { + let tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + + let result = tree.get(KeyHash::with::("non_existing_id")).unwrap(); + + let NotFound(non_membership_proof) = result else { + panic!("Hashchain found for key while it was expected to be missing"); + }; + + assert!(non_membership_proof.verify().is_ok()); +} + +#[test] +fn test_get_non_existing_key_ed25519() { + test_get_non_existing_key("ed25519"); +} + +#[test] +fn test_get_non_existing_key_secp256k1() { + test_get_non_existing_key("secp256k1"); +} + +#[test] +fn test_get_non_existing_key_secp256r1() { + test_get_non_existing_key("secp256r1"); +} + +fn test_multiple_inserts_and_updates(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let acc1_tx = tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + let acc2_tx = tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + + tree.process_transaction(acc1_tx).unwrap(); + tree.process_transaction(acc2_tx).unwrap(); + + // Do insert and update accounts using the correct key indices + let key_1_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); + tree.process_transaction(key_1_tx).unwrap(); + + let data_1_tx = + tx_builder.add_unsigned_data_verified_with_root(algorithm, "acc_2", b"unsigned".to_vec()).commit(); + tree.process_transaction(data_1_tx).unwrap(); + + let data_2_tx = tx_builder + .add_randomly_signed_data_verified_with_root(algorithm, "acc_2", b"signed".to_vec()) + .commit(); + tree.process_transaction(data_2_tx).unwrap(); + + let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); + let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); + + let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); + let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); + + assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); + assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); +} + +#[test] +fn test_multiple_inserts_and_updates_ed25519() { + test_multiple_inserts_and_updates("ed25519"); +} + +#[test] +fn test_multiple_inserts_and_updates_secp256k1() { + test_multiple_inserts_and_updates("secp256k1"); +} + +#[test] +fn test_multiple_inserts_and_updates_secp256r1() { + test_multiple_inserts_and_updates("secp256r1"); +} + +fn test_interleaved_inserts_and_updates(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let acc1_tx = tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + let acc2_tx = tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + tree.process_transaction(acc1_tx).unwrap(); + + let add_key_to_1_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_1").commit(); + tree.process_transaction(add_key_to_1_tx).unwrap(); + + tree.process_transaction(acc2_tx).unwrap(); + + let add_key_to_2_tx = tx_builder.add_random_key_verified_with_root(algorithm, "acc_2").commit(); + let last_proof = tree.process_transaction(add_key_to_2_tx).unwrap(); + + // Update account_2 using the correct key index + let Proof::Update(update_proof) = last_proof else { + panic!("Expetced insert proof for transaction"); + }; + + let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); + let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); + + let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); + let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); + + assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); + assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); + assert_eq!(update_proof.new_root, tree.get_commitment().unwrap()); +} + +#[test] +fn test_interleaved_inserts_and_updates_ed25519() { + test_interleaved_inserts_and_updates("ed25519"); +} + +#[test] +fn test_interleaved_inserts_and_updates_secp256k1() { + test_interleaved_inserts_and_updates("secp256k1"); +} + +#[test] +fn test_interleaved_inserts_and_updates_secp256r1() { + test_interleaved_inserts_and_updates("secp256r1"); +} + +fn test_root_hash_changes(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let account1_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + + let root_before = tree.get_current_root().unwrap(); + tree.process_transaction(account1_tx).unwrap(); + let root_after = tree.get_current_root().unwrap(); + + assert_ne!(root_before, root_after); +} + +#[test] +fn test_root_hash_changes_ed25519() { + test_root_hash_changes("ed25519"); +} + +#[test] +fn test_root_hash_changes_secp256k1() { + test_root_hash_changes("secp256k1"); +} + +#[test] +fn test_root_hash_changes_secp256r1() { + test_root_hash_changes("secp256r1"); +} + +fn test_batch_writing(algorithm: &str) { + let mut tree = KeyDirectoryTree::new(Arc::new(MockTreeStore::default())); + let mut tx_builder = TransactionBuilder::new(); + + let service_tx = tx_builder.register_service_with_random_keys(algorithm, "service_1").commit(); + let account1_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_1", "service_1").commit(); + let account2_tx = + tx_builder.create_account_with_random_key_signed(algorithm, "acc_2", "service_1").commit(); + + tree.process_transaction(service_tx).unwrap(); + + println!("Inserting acc_1"); + tree.process_transaction(account1_tx).unwrap(); + + println!("Tree state after first insert: {:?}", tree.get_commitment()); + + // Try to get the first value immediately + let get_result1 = tree.get(KeyHash::with::("acc_1")); + println!("Get result for key1 after first write: {:?}", get_result1); + + println!("Inserting acc_2"); + tree.process_transaction(account2_tx).unwrap(); + + println!("Tree state after 2nd insert: {:?}", tree.get_commitment()); + + // Try to get both values + let get_result1 = tree.get(KeyHash::with::("acc_1")).unwrap(); + let get_result2 = tree.get(KeyHash::with::("acc_2")).unwrap(); + + println!("Final get result for key1: {:?}", get_result1); + println!("Final get result for key2: {:?}", get_result2); + + let test_hashchain_acc1 = tx_builder.get_hashchain("acc_1").unwrap(); + let test_hashchain_acc2 = tx_builder.get_hashchain("acc_2").unwrap(); + + assert!(matches!(get_result1, Found(hc, _) if &hc == test_hashchain_acc1)); + assert!(matches!(get_result2, Found(hc, _) if &hc == test_hashchain_acc2)); +} + +#[test] +fn test_batch_writing_ed25519() { + test_batch_writing("ed25519"); +} + +#[test] +fn test_batch_writing_secp256k1() { + test_batch_writing("secp256k1"); +} + +#[test] +fn test_batch_writing_secp256r1() { + test_batch_writing("secp256r1"); +} diff --git a/crates/zk/sp1/Cargo.toml b/crates/zk/sp1/Cargo.toml index 593f4140..9d31d519 100644 --- a/crates/zk/sp1/Cargo.toml +++ b/crates/zk/sp1/Cargo.toml @@ -8,4 +8,5 @@ repository.workspace = true [dependencies] prism-common = { workspace = true } +prism-tree = { workspace = true } sp1-zkvm = { workspace = true } diff --git a/crates/zk/sp1/src/main.rs b/crates/zk/sp1/src/main.rs index 5669ef0b..2b0e0041 100644 --- a/crates/zk/sp1/src/main.rs +++ b/crates/zk/sp1/src/main.rs @@ -1,10 +1,8 @@ #![no_main] sp1_zkvm::entrypoint!(main); -use prism_common::{ - digest::Digest, - tree::{Batch, Proof}, -}; +use prism_common::digest::Digest; +use prism_tree::proofs::{Batch, Proof}; pub fn main() { let batch = sp1_zkvm::io::read::(); @@ -14,12 +12,12 @@ pub fn main() { for proof in batch.proofs.iter() { match proof { Proof::Update(p) => { - assert_eq!(current, Digest::new(p.old_root.into())); + assert_eq!(current, Digest::new(p.old_root.0)); assert!(p.verify().is_ok()); - current = Digest::new(p.new_root.into()); + current = Digest::new(p.new_root.0); } Proof::Insert(p) => { - assert_eq!(current, p.non_membership_proof.root); + assert_eq!(current, Digest::new(p.non_membership_proof.root.0)); assert!(p.verify().is_ok()); current = p.new_root; } diff --git a/elf/riscv32im-succinct-zkvm-elf b/elf/riscv32im-succinct-zkvm-elf index 5ca57022..5a5a7978 100755 Binary files a/elf/riscv32im-succinct-zkvm-elf and b/elf/riscv32im-succinct-zkvm-elf differ diff --git a/justfile b/justfile index 417b8b87..ac1c6715 100644 --- a/justfile +++ b/justfile @@ -1,18 +1,21 @@ # Define the path to your docker-compose.yml file DOCKER_COMPOSE_FILE := "ci/docker-compose.yml" +# Helper function to use correct docker compose command +docker_compose_cmd := if `uname -s` == "Linux" { "docker compose" } else { "docker-compose" } + celestia-up: #!/usr/bin/env bash set -euo pipefail echo "Cleaning up any existing Docker resources..." - docker-compose -f {{DOCKER_COMPOSE_FILE}} down -v --remove-orphans + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} down -v --remove-orphans echo "Building Docker images..." - docker-compose -f {{DOCKER_COMPOSE_FILE}} build + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} build echo "Spinning up a fresh Docker Compose stack..." - docker-compose -f {{DOCKER_COMPOSE_FILE}} up -d --force-recreate --renew-anon-volumes + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} up -d --force-recreate --renew-anon-volumes echo "Waiting for services to be ready..." timeout=120 @@ -21,7 +24,7 @@ celestia-up: bridge_node_ready=false while true; do - logs=$(docker-compose -f {{DOCKER_COMPOSE_FILE}} logs) + logs=$( {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} logs ) if [[ $logs == *"Configuration finished. Running a light node"* ]]; then light_node_ready=true @@ -43,7 +46,7 @@ celestia-up: if [ $elapsed -ge $timeout ]; then echo "Timeout waiting for services to be ready. Check the logs for more information." - docker-compose -f {{DOCKER_COMPOSE_FILE}} logs + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} logs exit 1 fi @@ -51,14 +54,13 @@ celestia-up: sleep 5 done - echo "Celestia stack is up and running!" celestia-down: - docker-compose -f {{DOCKER_COMPOSE_FILE}} down -v --remove-orphans + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} down -v --remove-orphans celestia-logs: - docker-compose -f {{DOCKER_COMPOSE_FILE}} logs -f + {{docker_compose_cmd}} -f {{DOCKER_COMPOSE_FILE}} logs -f # Command to run integration tests with a fresh Docker setup integration-test: @@ -123,6 +125,19 @@ install-deps: exit 1; \ fi + # On Linux, ensure essential packages are installed + if [ "$OS" = "Linux" ]; then \ + for package in build-essential pkg-config libssl-dev libclang-dev clang; do \ + if ! dpkg -s $package > /dev/null 2>&1; then \ + echo "Installing $package..."; \ + sudo apt update; \ + sudo apt install $package -y; \ + else \ + echo "$package is already installed."; \ + fi; \ + done; \ + fi + # Install Redis if not present if ! command -v redis-server > /dev/null; then \ echo "Installing Redis..."; \ @@ -141,7 +156,7 @@ install-deps: echo "Redis is already installed."; \ fi - if ! command -v cargo prove > /dev/null; then \ + if ! cargo prove --version > /dev/null 2>&1; then \ echo "Installing SP1..." curl -L https://sp1.succinct.xyz | bash; \ source ~/.bashrc || source ~/.bash_profile || source ~/.zshrc; \ @@ -149,7 +164,7 @@ install-deps: echo "Running sp1up to install SP1 toolchain..." sp1up - if command -v cargo prove > /dev/null; then \ + if cargo prove --version > /dev/null 2>&1; then \ echo "SP1 installation successful!"; \ cargo prove --version; \ else \