diff --git a/crates/common/src/account.rs b/crates/common/src/account.rs new file mode 100644 index 00000000..755b99d7 --- /dev/null +++ b/crates/common/src/account.rs @@ -0,0 +1,194 @@ +use anyhow::{anyhow, Result}; +use prism_keys::{Signature, SigningKey, VerifyingKey}; +use prism_serde::raw_or_b64; +use serde::{Deserialize, Serialize}; + +use crate::{ + operation::{Operation, ServiceChallenge}, + transaction::Transaction, +}; + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +pub struct SignedData(pub VerifyingKey, #[serde(with = "raw_or_b64")] pub Vec); + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)] +/// Represents an account or service on prism, making up the values of our state +/// tree. +pub struct Account { + /// The unique identifier for the account. + id: String, + + /// The transaction nonce for the account. + nonce: u64, + + /// The current set of valid keys for the account. Any of these keys can be + /// used to sign transactions. + valid_keys: Vec, + + /// Arbitrary signed data associated with the account, used for bookkeeping + /// externally signed data from keys that don't live on Prism. + signed_data: Vec, + + /// The service challenge for the account, if it is a service. + service_challenge: Option, +} + +impl Account { + pub fn id(&self) -> &str { + &self.id + } + + pub fn nonce(&self) -> u64 { + self.nonce + } + + pub fn valid_keys(&self) -> &[VerifyingKey] { + &self.valid_keys + } + + pub fn signed_data(&self) -> &[SignedData] { + &self.signed_data + } + + pub fn service_challenge(&self) -> Option<&ServiceChallenge> { + self.service_challenge.as_ref() + } + + /// Creates a [`Transaction`] that can be used to update or create the + /// account. The transaction produced could be invalid, and will be + /// validated before being processed. + pub fn prepare_transaction( + &self, + account_id: String, + operation: Operation, + sk: &SigningKey, + ) -> Result { + let vk = sk.verifying_key(); + + let mut tx = Transaction { + id: account_id, + nonce: self.nonce, + operation, + signature: Signature::Placeholder, + vk, + }; + + tx.sign(sk)?; + + Ok(tx) + } + + /// Validates and processes an incoming [`Transaction`], updating the account state. + pub fn process_transaction(&mut self, tx: &Transaction) -> Result<()> { + self.validate_transaction(tx)?; + self.process_operation(&tx.operation)?; + self.nonce += 1; + Ok(()) + } + + /// Validates a transaction against the current account state. Please note + /// that the operation must be validated separately. + fn validate_transaction(&self, tx: &Transaction) -> Result<()> { + if tx.nonce != self.nonce { + return Err(anyhow!( + "Nonce does not match. {} != {}", + tx.nonce, + self.nonce + )); + } + + match tx.operation { + Operation::CreateAccount { .. } | Operation::RegisterService { .. } => {} + _ => { + if tx.id != self.id { + return Err(anyhow!("Transaction ID does not match account ID")); + } + if !self.valid_keys.contains(&tx.vk) { + return Err(anyhow!("Invalid key")); + } + } + } + + let msg = tx.get_signature_payload()?; + tx.vk.verify_signature(&msg, &tx.signature)?; + + Ok(()) + } + + /// Validates an operation against the current account state. + fn validate_operation(&self, operation: &Operation) -> Result<()> { + match operation { + Operation::AddKey { key } => { + if self.valid_keys.contains(key) { + return Err(anyhow!("Key already exists")); + } + } + Operation::RevokeKey { key } => { + if !self.valid_keys.contains(key) { + return Err(anyhow!("Key does not exist")); + } + } + Operation::AddData { + data, + data_signature, + } => { + // we only need to do a single signature verification if the + // user signs transaction and data with their own key + if !self.valid_keys().contains(&data_signature.verifying_key) { + data_signature + .verifying_key + .verify_signature(data, &data_signature.signature)?; + } + } + Operation::CreateAccount { .. } | Operation::RegisterService { .. } => { + if !self.is_empty() { + return Err(anyhow!("Account already exists")); + } + } + } + Ok(()) + } + + /// Processes an operation, updating the account state. Should only be run + /// in the context of a transaction. + fn process_operation(&mut self, operation: &Operation) -> Result<()> { + self.validate_operation(operation)?; + + match operation { + Operation::AddKey { key } => { + self.valid_keys.push(key.clone()); + } + Operation::RevokeKey { key } => { + self.valid_keys.retain(|k| k != key); + } + Operation::AddData { + data, + data_signature, + } => { + self.signed_data.push(SignedData( + data_signature.verifying_key.clone(), + data.clone(), + )); + } + Operation::CreateAccount { id, key, .. } => { + self.id = id.clone(); + self.valid_keys.push(key.clone()); + } + Operation::RegisterService { + id, + creation_gate, + key, + } => { + self.id = id.clone(); + self.valid_keys.push(key.clone()); + self.service_challenge = Some(creation_gate.clone()); + } + } + + Ok(()) + } + + pub fn is_empty(&self) -> bool { + self.nonce == 0 + } +} diff --git a/crates/common/src/hashchain.rs b/crates/common/src/hashchain.rs deleted file mode 100644 index 47f17670..00000000 --- a/crates/common/src/hashchain.rs +++ /dev/null @@ -1,375 +0,0 @@ -use anyhow::{anyhow, bail, ensure, Result}; -use prism_keys::{Signature, SigningKey, VerifyingKey}; -use prism_serde::binary::ToBinary; -use serde::{Deserialize, Serialize}; -use std::ops::{Deref, DerefMut}; - -use crate::{ - digest::Digest, - operation::{ - HashchainSignatureBundle, Operation, ServiceChallenge, ServiceChallengeInput, - SignatureBundle, - }, -}; - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -pub struct Hashchain { - pub entries: Vec, -} - -impl IntoIterator for Hashchain { - type Item = HashchainEntry; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.entries.into_iter() - } -} - -impl<'a> IntoIterator for &'a Hashchain { - type Item = &'a HashchainEntry; - type IntoIter = std::slice::Iter<'a, HashchainEntry>; - - fn into_iter(self) -> Self::IntoIter { - self.entries.iter() - } -} - -impl<'a> IntoIterator for &'a mut Hashchain { - type Item = &'a mut HashchainEntry; - type IntoIter = std::slice::IterMut<'a, HashchainEntry>; - - fn into_iter(self) -> Self::IntoIter { - self.entries.iter_mut() - } -} - -impl Deref for Hashchain { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.entries - } -} - -impl DerefMut for Hashchain { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.entries - } -} - -impl Hashchain { - pub fn empty() -> Self { - Self { - entries: Vec::new(), - } - } - - pub fn from_entry(entry: HashchainEntry) -> Result { - let mut hc = Hashchain::empty(); - hc.add_entry(entry)?; - Ok(hc) - } - - pub fn get_key_at_index(&self, idx: usize) -> Result<&VerifyingKey> { - self.entries - .get(idx) - .and_then(|entry| entry.operation.get_public_key()) - .ok_or_else(|| anyhow!("No public key found at index {}", idx)) - } - - pub fn is_key_invalid(&self, key: &VerifyingKey) -> bool { - for entry in self.iter().rev() { - if let Some(entry_key) = entry.operation.get_public_key() { - if key.eq(entry_key) { - match entry.operation { - Operation::RevokeKey { .. } => return true, - Operation::AddKey { .. } - | Operation::CreateAccount { .. } - | Operation::RegisterService { .. } => return false, - _ => {} - } - } - } - } - true - } - - pub fn get(&self, idx: usize) -> &HashchainEntry { - &self.entries[idx] - } - - pub fn last_hash(&self) -> Digest { - self.last().map_or(Digest::zero(), |entry| entry.hash) - } - - /// Validates and adds a new entry to the hashchain. - /// This method is ran in circuit. - pub fn add_entry(&mut self, entry: HashchainEntry) -> Result<()> { - self.validate_new_entry(&entry)?; - self.entries.push(entry); - Ok(()) - } - - pub fn register_service( - &mut self, - id: String, - creation_gate: ServiceChallenge, - key: VerifyingKey, - signing_key: &SigningKey, - ) -> Result { - let entry = HashchainEntry::new_register_service(id, creation_gate, key, signing_key); - self.add_entry(entry.clone())?; - Ok(entry) - } - - pub fn create_account( - &mut self, - id: String, - service_id: String, - challenge: ServiceChallengeInput, - key: VerifyingKey, - signing_key: &SigningKey, - ) -> Result { - let entry = HashchainEntry::new_create_account(id, service_id, challenge, key, signing_key); - self.add_entry(entry.clone())?; - Ok(entry) - } - - pub fn add_key( - &mut self, - key: VerifyingKey, - signing_key: &SigningKey, - key_idx: usize, - ) -> Result { - let entry = HashchainEntry::new_add_key(key, self.last_hash(), signing_key, key_idx); - self.add_entry(entry.clone())?; - Ok(entry) - } - - pub fn revoke_key( - &mut self, - key: VerifyingKey, - signing_key: &SigningKey, - key_idx: usize, - ) -> Result { - let entry = HashchainEntry::new_revoke_key(key, self.last_hash(), signing_key, key_idx); - self.add_entry(entry.clone())?; - Ok(entry) - } - - pub fn add_data( - &mut self, - data: Vec, - data_signature: Option, - signing_key: &SigningKey, - key_idx: usize, - ) -> Result { - let entry = HashchainEntry::new_add_data( - data, - data_signature, - self.last_hash(), - signing_key, - key_idx, - ); - self.add_entry(entry.clone())?; - Ok(entry) - } - - /// Validates that the new entry is valid and can be added to the hashchain. - /// This method is ran in circuit. - fn validate_new_entry(&self, entry: &HashchainEntry) -> Result<()> { - entry.validate_operation()?; - - let last_hash = self.last_hash(); - if entry.previous_hash != last_hash { - bail!( - "Previous hash for new entry must be the last hash - prev: {}, last: {}", - entry.previous_hash, - last_hash - ) - } - - let verifying_key = self.verifying_key_for_entry(entry)?; - - match entry.operation { - Operation::CreateAccount { .. } | Operation::RegisterService { .. } => { - if !self.entries.is_empty() { - bail!("CreateAccount/RegisterService must be the first entry"); - } - } - Operation::AddData { .. } | Operation::AddKey { .. } | Operation::RevokeKey { .. } => { - if self.entries.is_empty() { - bail!("CreateAccount/RegisterService must be the first entry"); - } - - if self.is_key_invalid(verifying_key) { - bail!("Invalid key at index {}", &entry.signature_bundle.key_idx); - } - } - } - - entry.validate_hash()?; - entry.validate_signature(verifying_key) - } - - fn verifying_key_for_entry<'a>( - &'a self, - entry: &'a HashchainEntry, - ) -> Result<&'a VerifyingKey> { - match &entry.operation { - Operation::CreateAccount { key, .. } | Operation::RegisterService { key, .. } => { - Ok(key) - } - Operation::AddData { .. } | Operation::AddKey { .. } | Operation::RevokeKey { .. } => { - self.get_key_at_index(entry.signature_bundle.key_idx) - } - } - } - - pub fn is_empty(&self) -> bool { - self.entries.is_empty() - } - - pub fn len(&self) -> usize { - self.entries.len() - } -} - -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] -// A [`HashchainEntry`] represents a single entry in an account's hashchain. -// The value in the leaf of the corresponding account's node in the IMT is the hash of the last node in the hashchain. -pub struct HashchainEntry { - pub hash: Digest, - pub previous_hash: Digest, - pub operation: Operation, - pub signature_bundle: HashchainSignatureBundle, -} - -impl HashchainEntry { - pub fn new( - operation: Operation, - previous_hash: Digest, - signing_key: &SigningKey, - key_idx: usize, - ) -> Self { - let serialized_operation = - operation.encode_to_bytes().expect("Serializing operation should work"); - let hash = - Digest::hash_items(&[serialized_operation.as_slice(), &previous_hash.to_bytes()]); - - let signature_bundle = HashchainSignatureBundle { - signature: signing_key.sign(hash.as_ref()), - key_idx, - }; - - Self { - hash, - previous_hash, - operation, - signature_bundle, - } - } - - pub fn new_genesis(operation: Operation, signing_key: &SigningKey) -> Self { - Self::new(operation, Digest::zero(), signing_key, 0) - } - - pub fn new_register_service( - id: String, - creation_gate: ServiceChallenge, - key: VerifyingKey, - signing_key: &SigningKey, - ) -> Self { - let operation = Operation::RegisterService { - id, - creation_gate, - key, - }; - Self::new_genesis(operation, signing_key) - } - - pub fn new_create_account( - id: String, - service_id: String, - challenge: ServiceChallengeInput, - key: VerifyingKey, - signing_key: &SigningKey, - ) -> Self { - let operation = Operation::CreateAccount { - id, - service_id, - challenge, - key, - }; - Self::new_genesis(operation, signing_key) - } - - pub fn new_add_key( - key: VerifyingKey, - prev_hash: Digest, - signing_key: &SigningKey, - key_idx: usize, - ) -> Self { - let operation = Operation::AddKey { key }; - Self::new(operation, prev_hash, signing_key, key_idx) - } - - pub fn new_revoke_key( - key: VerifyingKey, - prev_hash: Digest, - signing_key: &SigningKey, - key_idx: usize, - ) -> Self { - let operation = Operation::RevokeKey { key }; - Self::new(operation, prev_hash, signing_key, key_idx) - } - - pub fn new_add_data( - data: Vec, - data_signature: Option, - prev_hash: Digest, - signing_key: &SigningKey, - key_idx: usize, - ) -> Self { - let operation = Operation::AddData { - data, - data_signature, - }; - Self::new(operation, prev_hash, signing_key, key_idx) - } - - pub fn validate_hash(&self) -> Result<()> { - let pristine_entry = self.without_signature(); - - let serialized_operation = pristine_entry.operation.encode_to_bytes()?; - let pristine_entry_hash = Digest::hash_items(&[ - serialized_operation.as_slice(), - &pristine_entry.previous_hash.to_bytes(), - ]); - - ensure!( - self.hash == pristine_entry_hash, - "Hashchain entry has incorrect hash" - ); - Ok(()) - } - - pub fn validate_signature(&self, verifying_key: &VerifyingKey) -> Result<()> { - verifying_key.verify_signature(self.hash.as_ref(), &self.signature_bundle.signature) - } - - pub fn validate_operation(&self) -> Result<()> { - self.operation.validate_basic() - } - - pub fn without_signature(&self) -> Self { - Self { - signature_bundle: HashchainSignatureBundle { - key_idx: self.signature_bundle.key_idx, - signature: Signature::Placeholder, - }, - ..self.clone() - } - } -} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index a9882db6..03d00aed 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,5 +1,5 @@ +pub mod account; pub mod digest; -pub mod hashchain; pub mod operation; pub mod transaction; diff --git a/crates/common/src/operation.rs b/crates/common/src/operation.rs index d62c424f..6bf772e1 100644 --- a/crates/common/src/operation.rs +++ b/crates/common/src/operation.rs @@ -27,7 +27,7 @@ pub enum Operation { AddData { #[serde(with = "raw_or_b64")] data: Vec, - data_signature: Option, + data_signature: SignatureBundle, }, /// Adds a key to an existing account. AddKey { key: VerifyingKey }, @@ -35,25 +35,6 @@ pub enum Operation { RevokeKey { key: VerifyingKey }, } -#[derive(Clone, Serialize, Deserialize, Default, Debug, PartialEq)] -/// Represents a signature bundle, which includes the index of the key -/// in the user's hashchain and the associated signature. -pub struct HashchainSignatureBundle { - /// Index of the key in the hashchain - pub key_idx: usize, - /// The actual signature - pub signature: Signature, -} - -impl HashchainSignatureBundle { - pub fn empty_with_idx(idx: usize) -> Self { - HashchainSignatureBundle { - key_idx: idx, - signature: Signature::Placeholder, - } - } -} - #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] /// Represents a signature including its. pub struct SignatureBundle { diff --git a/crates/common/src/transaction.rs b/crates/common/src/transaction.rs index 530c01e0..a9978f13 100644 --- a/crates/common/src/transaction.rs +++ b/crates/common/src/transaction.rs @@ -1,13 +1,46 @@ +use anyhow::{anyhow, Result}; use celestia_types::Blob; -use prism_serde::binary::FromBinary; +use prism_keys::{Signature, SigningKey, VerifyingKey}; +use prism_serde::binary::{FromBinary, ToBinary}; use serde::{Deserialize, Serialize}; -use crate::hashchain::HashchainEntry; +use crate::operation::Operation; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +/// Represents a prism transaction that can be applied to an account. pub struct Transaction { + /// The account id that this transaction is for pub id: String, - pub entry: HashchainEntry, + /// The [`Operation`] to be applied to the account + pub operation: Operation, + /// The nonce of the account at the time of this transaction + pub nonce: u64, + /// The signature of the transaction, signed by [`self::vk`]. + pub signature: Signature, + /// The verifying key of the signer of this transaction. This vk must be + /// included in the account's valid_keys set. + pub vk: VerifyingKey, +} + +impl Transaction { + /// Encodes the transaction to bytes to prepare for signing. + pub fn get_signature_payload(&self) -> Result> { + let mut tx = self.clone(); + tx.signature = Signature::Placeholder; + tx.encode_to_bytes().map_err(|e| anyhow!(e)) + } + + /// Signs the transaction with the given [`SigningKey`] and inserts the + /// signature into the transaction. + pub fn sign(&mut self, sk: &SigningKey) -> Result { + if let Signature::Placeholder = self.signature { + let sig = sk.sign(&self.get_signature_payload()?); + self.signature = sig.clone(); + Ok(sig) + } else { + Err(anyhow!("Transaction already signed")) + } + } } impl TryFrom<&Blob> for Transaction { diff --git a/crates/common/src/transaction_builder.rs b/crates/common/src/transaction_builder.rs index 8cba3807..7444d3e0 100644 --- a/crates/common/src/transaction_builder.rs +++ b/crates/common/src/transaction_builder.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use crate::{ + account::Account, digest::Digest, - hashchain::{Hashchain, HashchainEntry}, - operation::{ServiceChallenge, ServiceChallengeInput, SignatureBundle}, + operation::{Operation, ServiceChallenge, ServiceChallengeInput, SignatureBundle}, transaction::Transaction, }; use prism_keys::{SigningKey, VerifyingKey}; @@ -23,14 +23,10 @@ impl UncommittedTransaction<'_> { /// Commits and returns a transaction, updating the builder. Subsequent transactions /// built with the same builder will have the correct previous hash. pub fn commit(self) -> Transaction { - let hc = self - .builder - .hashchains - .entry(self.transaction.id.clone()) - .or_insert_with(Hashchain::empty); + let acc = self.builder.accounts.entry(self.transaction.id.clone()).or_default(); - hc.add_entry(self.transaction.entry.clone()) - .expect("Adding transaction entry to hashchain should work"); + acc.process_transaction(&self.transaction) + .expect("Adding transaction entry to account should work"); match self.post_commit_action { PostCommitAction::UpdateStorageOnly => (), @@ -53,8 +49,8 @@ impl UncommittedTransaction<'_> { } pub struct TransactionBuilder { - /// Simulated hashchain storage that is mutated when transactions are applied - hashchains: HashMap, + /// Simulated account storage that is mutated when transactions are applied + accounts: HashMap, /// Remembers private keys of services to simulate account creation via an external service service_keys: HashMap, /// Remembers private keys of accounts to simulate actions on behalf of these accounts @@ -63,12 +59,12 @@ pub struct TransactionBuilder { impl Default for TransactionBuilder { fn default() -> Self { - let hashchains = HashMap::new(); + let accounts = HashMap::new(); let service_keys = HashMap::new(); let account_keys = HashMap::new(); Self { - hashchains, + accounts, service_keys, account_keys, } @@ -80,8 +76,8 @@ impl TransactionBuilder { Self::default() } - pub fn get_hashchain(&self, id: &str) -> Option<&Hashchain> { - self.hashchains.get(id) + pub fn get_account(&self, id: &str) -> Option<&Account> { + self.accounts.get(id) } pub fn register_service_with_random_keys(&mut self, id: &str) -> UncommittedTransaction { @@ -97,18 +93,17 @@ impl TransactionBuilder { signing_key: SigningKey, ) -> UncommittedTransaction { let vk: VerifyingKey = signing_key.clone().into(); - let entry = HashchainEntry::new_register_service( - id.to_string(), - ServiceChallenge::from(challenge_key.clone()), - vk, - &signing_key, - ); + let op = Operation::RegisterService { + id: id.to_string(), + creation_gate: ServiceChallenge::Signed(challenge_key.verifying_key()), + key: vk.clone(), + }; + + let account = Account::default(); + let transaction = account.prepare_transaction(id.to_string(), op, &signing_key).unwrap(); UncommittedTransaction { - transaction: Transaction { - id: id.to_string(), - entry, - }, + transaction, builder: self, post_commit_action: PostCommitAction::RememberServiceKey(id.to_string(), challenge_key), } @@ -158,19 +153,18 @@ impl TransactionBuilder { let hash = Digest::hash_items(&[id.as_bytes(), service_id.as_bytes(), &vk.to_bytes()]); let signature = service_signing_key.sign(&hash.to_bytes()); - let entry = HashchainEntry::new_create_account( - id.to_string(), - service_id.to_string(), - ServiceChallengeInput::Signed(signature), - vk.clone(), - &signing_key, - ); + let op = Operation::CreateAccount { + id: id.to_string(), + service_id: service_id.to_string(), + challenge: ServiceChallengeInput::Signed(signature.clone()), + key: vk.clone(), + }; + + let account = Account::default(); + let transaction = account.prepare_transaction(id.to_string(), op, &signing_key).unwrap(); UncommittedTransaction { - transaction: Transaction { - id: id.to_string(), - entry, - }, + transaction, builder: self, post_commit_action: PostCommitAction::RememberAccountKey(id.to_string(), signing_key), } @@ -181,17 +175,12 @@ impl TransactionBuilder { panic!("No existing account key for {}", id) }; - self.add_random_key(id, &account_signing_key, 0) + self.add_random_key(id, &account_signing_key) } - pub fn add_random_key( - &mut self, - id: &str, - signing_key: &SigningKey, - key_idx: usize, - ) -> UncommittedTransaction { + pub fn add_random_key(&mut self, id: &str, signing_key: &SigningKey) -> UncommittedTransaction { let random_key = SigningKey::new_ed25519().into(); - self.add_key(id, random_key, signing_key, key_idx) + self.add_key(id, random_key, signing_key) } pub fn add_key_verified_with_root( @@ -203,7 +192,7 @@ impl TransactionBuilder { panic!("No existing account key for {}", id) }; - self.add_key(id, key, &account_signing_key, 0) + self.add_key(id, key, &account_signing_key) } pub fn add_key( @@ -211,17 +200,14 @@ impl TransactionBuilder { id: &str, key: VerifyingKey, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { - let last_hash = self.hashchains.get(id).map_or(Digest::zero(), Hashchain::last_hash); + let account = self.accounts.get(id).cloned().unwrap_or_default(); + let op = Operation::AddKey { key: key.clone() }; - let entry = HashchainEntry::new_add_key(key, last_hash, signing_key, key_idx); + let transaction = account.prepare_transaction(id.to_string(), op, signing_key).unwrap(); UncommittedTransaction { - transaction: Transaction { - id: id.to_string(), - entry, - }, + transaction, builder: self, post_commit_action: PostCommitAction::UpdateStorageOnly, } @@ -236,7 +222,7 @@ impl TransactionBuilder { panic!("No existing account key for {}", id) }; - self.revoke_key(id, key, &account_signing_key, 0) + self.revoke_key(id, key, &account_signing_key) } pub fn revoke_key( @@ -244,17 +230,14 @@ impl TransactionBuilder { id: &str, key: VerifyingKey, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { - let last_hash = self.hashchains.get(id).map_or(Digest::zero(), Hashchain::last_hash); + let account = self.accounts.get(id).cloned().unwrap_or_default(); + let op = Operation::RevokeKey { key: key.clone() }; - let entry = HashchainEntry::new_revoke_key(key, last_hash, signing_key, key_idx); + let transaction = account.prepare_transaction(id.to_string(), op, signing_key).unwrap(); UncommittedTransaction { - transaction: Transaction { - id: id.to_string(), - entry, - }, + transaction, builder: self, post_commit_action: PostCommitAction::UpdateStorageOnly, } @@ -265,10 +248,9 @@ impl TransactionBuilder { id: &str, value: Vec, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { let value_signing_key = SigningKey::new_ed25519(); - self.add_signed_data(id, value, &value_signing_key, signing_key, key_idx) + self.add_signed_data(id, value, &value_signing_key, signing_key) } pub fn add_randomly_signed_data_verified_with_root( @@ -286,13 +268,12 @@ impl TransactionBuilder { value: Vec, value_signing_key: &SigningKey, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { let value_signature_bundle = SignatureBundle { verifying_key: value_signing_key.verifying_key(), signature: value_signing_key.sign(&value), }; - self.add_pre_signed_data(id, value, value_signature_bundle, signing_key, key_idx) + self.add_pre_signed_data(id, value, value_signature_bundle, signing_key) } pub fn add_signed_data_verified_with_root( @@ -314,9 +295,8 @@ impl TransactionBuilder { value: Vec, value_signature: SignatureBundle, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { - self.add_data(id, value, Some(value_signature), signing_key, key_idx) + self.add_data(id, value, value_signature, signing_key) } pub fn add_pre_signed_data_verified_with_root( @@ -325,58 +305,69 @@ impl TransactionBuilder { value: Vec, value_signature: SignatureBundle, ) -> UncommittedTransaction { - self.add_data_verified_with_root(id, value, Some(value_signature)) + self.add_data_verified_with_root(id, value, value_signature) } - pub fn add_unsigned_data( + pub fn add_internally_signed_data( &mut self, id: &str, value: Vec, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { - self.add_data(id, value, None, signing_key, key_idx) + let bundle = SignatureBundle { + verifying_key: signing_key.verifying_key(), + signature: signing_key.sign(&value), + }; + self.add_data(id, value, bundle, signing_key) } - pub fn add_unsigned_data_verified_with_root( + pub fn add_internally_signed_data_verified_with_root( &mut self, id: &str, value: Vec, ) -> UncommittedTransaction { - self.add_data_verified_with_root(id, value, None) + let Some(account_signing_key) = self.account_keys.get(id).cloned() else { + panic!("No existing account key for {}", id) + }; + + let bundle = SignatureBundle { + verifying_key: account_signing_key.verifying_key(), + signature: account_signing_key.sign(&value), + }; + + self.add_data_verified_with_root(id, value, bundle) } fn add_data_verified_with_root( &mut self, id: &str, value: Vec, - value_signature: Option, + value_signature: SignatureBundle, ) -> UncommittedTransaction { let Some(account_signing_key) = self.account_keys.get(id).cloned() else { panic!("No existing account key for {}", id) }; - self.add_data(id, value, value_signature, &account_signing_key, 0) + self.add_data(id, value, value_signature, &account_signing_key) } fn add_data( &mut self, id: &str, data: Vec, - data_signature: Option, + data_signature: SignatureBundle, signing_key: &SigningKey, - key_idx: usize, ) -> UncommittedTransaction { - let last_hash = self.hashchains.get(id).map_or(Digest::zero(), Hashchain::last_hash); + let account = self.accounts.get(id).cloned().unwrap_or_default(); + let op = Operation::AddData { + data, + data_signature, + }; - let entry = - HashchainEntry::new_add_data(data, data_signature, last_hash, signing_key, key_idx); + let transaction = account.prepare_transaction(id.to_string(), op, signing_key).unwrap(); UncommittedTransaction { - transaction: Transaction { - id: id.to_string(), - entry, - }, + transaction, builder: self, post_commit_action: PostCommitAction::UpdateStorageOnly, } diff --git a/crates/node_types/prover/src/prover/mod.rs b/crates/node_types/prover/src/prover/mod.rs index 988da1da..12cf7bc3 100644 --- a/crates/node_types/prover/src/prover/mod.rs +++ b/crates/node_types/prover/src/prover/mod.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, bail, Context, Result}; use ed25519_consensus::{SigningKey, VerificationKey}; use jmt::KeyHash; use keystore_rs::create_signing_key; -use prism_common::{digest::Digest, hashchain::Hashchain, transaction::Transaction}; +use prism_common::{account::Account, digest::Digest, transaction::Transaction}; use prism_errors::DataAvailabilityError; use prism_storage::database::Database; use prism_tree::{ @@ -10,7 +10,7 @@ use prism_tree::{ key_directory_tree::KeyDirectoryTree, proofs::{Batch, Proof}, snarkable_tree::SnarkableTree, - HashchainResponse::{self, *}, + AccountResponse::{self, *}, }; use std::{self, collections::VecDeque, sync::Arc}; use tokio::{ @@ -454,7 +454,7 @@ impl Prover { tree.get_commitment().context("Failed to get commitment") } - pub async fn get_hashchain(&self, id: &String) -> Result { + pub async fn get_account(&self, id: &String) -> Result { let tree = self.tree.read().await; let key_hash = KeyHash::with::(id); @@ -476,19 +476,19 @@ impl Prover { bail!("Batcher is disabled, cannot queue transactions"); } - // validate against existing hashchain if necessary, including signature checks - match transaction.entry.operation { + // validate against existing account if necessary, including signature checks + match transaction.operation { Operation::RegisterService { .. } | Operation::CreateAccount { .. } => { - Hashchain::empty().add_entry(transaction.entry.clone())? + Account::default().process_transaction(&transaction)?; } Operation::AddKey { .. } | Operation::RevokeKey { .. } | Operation::AddData { .. } => { - let hc_response = self.get_hashchain(&transaction.id).await?; + let account_response = self.get_account(&transaction.id).await?; - let Found(mut hc, _) = hc_response else { - bail!("Hashchain not found for id: {}", transaction.id) + let Found(mut account, _) = account_response else { + bail!("Account not found for id: {}", transaction.id) }; - hc.add_entry(transaction.entry.clone())?; + account.process_transaction(&transaction)?; } }; diff --git a/crates/node_types/prover/src/prover/tests.rs b/crates/node_types/prover/src/prover/tests.rs index cf6618bc..90034736 100644 --- a/crates/node_types/prover/src/prover/tests.rs +++ b/crates/node_types/prover/src/prover/tests.rs @@ -77,9 +77,8 @@ async fn test_process_transactions() { let revoke_transaction = transaction_builder .revoke_key( "test_account", - create_account_transaction.entry.operation.get_public_key().cloned().unwrap(), + create_account_transaction.operation.get_public_key().cloned().unwrap(), &new_key, - 1, ) .commit(); let proof = prover.process_transaction(revoke_transaction).await.unwrap(); @@ -104,7 +103,7 @@ async fn test_execute_block_with_invalid_tx() { tx_builder.revoke_key_verified_with_root("account_id", new_key_vk).commit(), // and adding in same block. // both of these transactions are valid individually, but when processed together it will fail. - tx_builder.add_random_key("account_id", &new_key_1, 1).build(), + tx_builder.add_random_key("account_id", &new_key_1).build(), ]; let proofs = prover.execute_block(transactions).await.unwrap(); diff --git a/crates/node_types/prover/src/webserver.rs b/crates/node_types/prover/src/webserver.rs index 81a69f3b..95a9df7b 100644 --- a/crates/node_types/prover/src/webserver.rs +++ b/crates/node_types/prover/src/webserver.rs @@ -8,15 +8,11 @@ use axum::{ Json, Router, }; use jmt::proof::{SparseMerkleNode, SparseMerkleProof}; -use prism_common::{ - digest::Digest, - hashchain::{Hashchain, HashchainEntry}, - transaction::Transaction, -}; +use prism_common::{account::Account, digest::Digest, transaction::Transaction}; use prism_tree::{ hasher::TreeHasher, proofs::{Proof, UpdateProof}, - HashchainResponse, + AccountResponse, }; use serde::{Deserialize, Serialize}; use std::{self, sync::Arc}; @@ -55,10 +51,7 @@ pub struct EpochData { } #[derive(Deserialize, Debug, ToSchema)] -pub struct TransactionRequest { - pub id: String, - pub entry: HashchainEntry, -} +pub struct TransactionRequest(Transaction); #[derive(Serialize, Deserialize, ToSchema)] pub struct UpdateProofResponse(UpdateProof); @@ -73,7 +66,7 @@ pub struct UserKeyRequest { #[derive(Serialize, Deserialize, ToSchema)] pub struct UserKeyResponse { - pub hashchain: Option, + pub account: Option, pub proof: JmtProofResponse, } @@ -101,7 +94,7 @@ impl From> for JmtProofResponse { #[derive(OpenApi)] #[openapi( - paths(post_transaction, get_hashchain, get_commitment), + paths(post_transaction, get_account, get_commitment), components(schemas( TransactionRequest, EpochData, @@ -126,7 +119,7 @@ impl WebServer { let app = Router::new() .route("/transaction", post(post_transaction)) - .route("/get-hashchain", post(get_hashchain)) + .route("/get-account", post(get_account)) .route("/get-current-commitment", get(get_commitment)) .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) .layer(CorsLayer::permissive()) @@ -162,13 +155,9 @@ impl WebServer { )] async fn post_transaction( State(session): State>, - Json(update_input): Json, + Json(tx_request): Json, ) -> impl IntoResponse { - let transaction = Transaction { - id: update_input.id.clone(), - entry: update_input.entry.clone(), - }; - match session.validate_and_queue_update(transaction).await { + match session.validate_and_queue_update(tx_request.0).await { Ok(_) => ( StatusCode::OK, "Entry update queued for insertion into next epoch", @@ -182,48 +171,48 @@ async fn post_transaction( } } -/// The /get-hashchain endpoint returns all added keys for a given user id. +/// The /get-account endpoint returns all added keys for a given user id. /// /// If the ID is not found in the database, the endpoint will return a 400 response with the message "Could not calculate values". /// #[utoipa::path( post, - path = "/get-hashchain", + path = "/get-account", request_body = UserKeyRequest, responses( (status = 200, description = "Successfully retrieved valid keys", body = UpdateKeyResponse), (status = 400, description = "Bad request") ) )] -async fn get_hashchain( +async fn get_account( State(session): State>, Json(request): Json, ) -> impl IntoResponse { - let get_hashchain_result = session.get_hashchain(&request.id).await; - let Ok(hashchain_response) = get_hashchain_result else { + let get_account_result = session.get_account(&request.id).await; + let Ok(account_response) = get_account_result else { return ( StatusCode::INTERNAL_SERVER_ERROR, format!( - "Failed to retrieve hashchain or non-membership-proof: {}", - get_hashchain_result.unwrap_err() + "Failed to retrieve account or non-membership-proof: {}", + get_account_result.unwrap_err() ), ) .into_response(); }; - match hashchain_response { - HashchainResponse::Found(hashchain, membership_proof) => ( + match account_response { + AccountResponse::Found(account, membership_proof) => ( StatusCode::OK, Json(UserKeyResponse { - hashchain: Some(hashchain), + account: Some(*account), proof: JmtProofResponse::from(membership_proof.proof), }), ) .into_response(), - HashchainResponse::NotFound(non_membership_proof) => ( + AccountResponse::NotFound(non_membership_proof) => ( StatusCode::OK, Json(UserKeyResponse { - hashchain: None, + account: None, proof: JmtProofResponse::from(non_membership_proof.proof), }), ) diff --git a/crates/tree/src/key_directory_tree.rs b/crates/tree/src/key_directory_tree.rs index c7fd2483..549a9cf5 100644 --- a/crates/tree/src/key_directory_tree.rs +++ b/crates/tree/src/key_directory_tree.rs @@ -11,8 +11,8 @@ use crate::hasher::TreeHasher; 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. +/// Wraps a [`JellyfishMerkleTree`] to provide a key-value store for [`Account`]s with batched insertions. +/// This is prism's primary data structure for storing and retrieving [`Account`]s. pub struct KeyDirectoryTree where S: TreeReader + TreeWriter, diff --git a/crates/tree/src/lib.rs b/crates/tree/src/lib.rs index 70ecf12f..0b423206 100644 --- a/crates/tree/src/lib.rs +++ b/crates/tree/src/lib.rs @@ -3,17 +3,17 @@ pub mod key_directory_tree; pub mod proofs; pub mod snarkable_tree; -use prism_common::hashchain::Hashchain; -use proofs::{MembershipProof, NonMembershipProof}; +use prism_common::account::Account; +use proofs::MerkleProof; /// 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), +pub enum AccountResponse { + /// When an account was found, provides the value and its corresponding membership-proof + Found(Box, MerkleProof), - /// When no hashchain was found for a specific key, provides the corresponding non-membership-proof - NotFound(NonMembershipProof), + /// When no account was found for a specific key, provides the corresponding non-membership-proof + NotFound(MerkleProof), } #[cfg(test)] diff --git a/crates/tree/src/proofs.rs b/crates/tree/src/proofs.rs index e1d64109..272781f1 100644 --- a/crates/tree/src/proofs.rs +++ b/crates/tree/src/proofs.rs @@ -3,10 +3,7 @@ use jmt::{ proof::{SparseMerkleProof, UpdateMerkleProof}, KeyHash, RootHash, }; -use prism_common::{ - digest::Digest, - hashchain::{Hashchain, HashchainEntry}, -}; +use prism_common::{account::Account, digest::Digest, transaction::Transaction}; use prism_serde::binary::ToBinary; use serde::{Deserialize, Serialize}; @@ -29,7 +26,7 @@ pub enum Proof { } #[derive(Debug, Clone, Serialize, Deserialize)] -/// Represents an insertion proof for a newly created hashchain. +/// Represents an insertion proof for a newly created account. /// Currently, this proof is generated by /// [`crate::operation::Operation::CreateAccount`] and /// [`crate::operation::Operation::RegisterService`] operations. @@ -37,29 +34,31 @@ pub enum Proof { // the service, and then signature verification with the contained VK. pub struct InsertProof { /// Proof that the key does not already exist in the tree (i.e. it's not overwriting an existing key) - pub non_membership_proof: NonMembershipProof, + pub non_membership_proof: MerkleProof, /// Post-insertion root hash of the tree pub new_root: Digest, - /// Proof that the new hashchain is correctly inserted into the tree + /// Proof that the new account is correctly inserted into the tree pub membership_proof: SparseMerkleProof, - /// The new hashchain entry that was inserted. - pub new_entry: HashchainEntry, + /// The new account that was inserted. + pub tx: Transaction, } impl InsertProof { /// The method called in circuit to verify the state transition to the new root. pub fn verify(&self) -> Result<()> { - self.non_membership_proof.verify().context("Invalid NonMembershipProof")?; + self.non_membership_proof.verify_nonexistence().context("Invalid NonMembershipProof")?; + + let mut account = Account::default(); + account.process_transaction(&self.tx)?; - let hashchain = Hashchain::from_entry(self.new_entry.clone())?; - let serialized_hashchain = hashchain.encode_to_bytes()?; + let serialized_account = account.encode_to_bytes()?; self.membership_proof.clone().verify_existence( RootHash(self.new_root.0), self.non_membership_proof.key, - serialized_hashchain, + serialized_account, )?; Ok(()) @@ -67,18 +66,19 @@ impl InsertProof { } #[derive(Debug, Clone, Serialize, Deserialize)] -/// Represents an update proof for an existing [`Hashchain`], updating it with a new [`HashchainEntry`]. +/// Represents an update proof for an existing [`Account`]. pub struct UpdateProof { 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 old_account: Account, + pub tx: Transaction, + + /// Inclusion proof of [`UpdateProof::old_account`] pub inclusion_proof: SparseMerkleProof, - /// Update proof for [`UpdateProof::key`] to be updated with [`UpdateProof::new_entry`] + /// Update proof for [`UpdateProof::key`] to be updated with [`UpdateProof::tx`] pub update_proof: UpdateMerkleProof, } @@ -86,24 +86,23 @@ impl UpdateProof { /// The method called in circuit to verify the state transition to the new root. pub fn verify(&self) -> Result<()> { // 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()?; + // Otherwise, any arbitrary account could be set as old_account. + let old_serialized_account = self.old_account.encode_to_bytes()?; self.inclusion_proof.verify_existence( RootHash(self.old_root.0), self.key, - old_serialized_hashchain, + old_serialized_account, )?; - let mut hashchain_after_update = self.old_hashchain.clone(); - // Append the new entry and verify it's validity - hashchain_after_update.add_entry(self.new_entry.clone())?; + let mut new_account = self.old_account.clone(); + new_account.process_transaction(&self.tx)?; - // Ensure the update proof corresponds to the new hashchain value - let new_serialized_hashchain = hashchain_after_update.encode_to_bytes()?; + // Ensure the update proof corresponds to the new account value + let new_serialized_account = new_account.encode_to_bytes()?; self.update_proof.clone().verify_update( RootHash(self.old_root.0), RootHash(self.new_root.0), - vec![(self.key, Some(new_serialized_hashchain))], + vec![(self.key, Some(new_serialized_account))], )?; Ok(()) @@ -111,29 +110,19 @@ impl UpdateProof { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MembershipProof { +pub struct MerkleProof { pub root: Digest, pub proof: SparseMerkleProof, pub key: KeyHash, - pub value: Hashchain, } -impl MembershipProof { - pub fn verify(&self) -> Result<()> { - let value = self.value.encode_to_bytes()?; +impl MerkleProof { + pub fn verify_existence(&self, value: &Account) -> Result<()> { + let value = value.encode_to_bytes()?; 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 key: KeyHash, -} -impl NonMembershipProof { - pub fn verify(&self) -> Result<()> { + pub fn verify_nonexistence(&self) -> Result<()> { self.proof.verify_nonexistence(RootHash(self.root.0), self.key) } } diff --git a/crates/tree/src/snarkable_tree.rs b/crates/tree/src/snarkable_tree.rs index fe53750f..92d6264d 100644 --- a/crates/tree/src/snarkable_tree.rs +++ b/crates/tree/src/snarkable_tree.rs @@ -8,8 +8,8 @@ use prism_errors::DatabaseError; use prism_serde::binary::{FromBinary, ToBinary}; use prism_common::{ + account::Account, digest::Digest, - hashchain::{Hashchain, HashchainEntry}, operation::{Operation, ServiceChallenge, ServiceChallengeInput}, transaction::Transaction, }; @@ -17,18 +17,18 @@ use prism_common::{ use crate::{ hasher::TreeHasher, key_directory_tree::KeyDirectoryTree, - proofs::{InsertProof, MembershipProof, NonMembershipProof, Proof, UpdateProof}, - HashchainResponse::{self, *}, + proofs::{InsertProof, MerkleProof, Proof, UpdateProof}, + AccountResponse::{self, *}, }; -/// Represents a tree that can be used to verifiably store and retrieve [`Hashchain`]s. +/// Represents a tree that can be used to verifiably store and retrieve [`Account`]s. /// The methods of this trait are NOT run in circuit: they are used to create verifiable inputs for the circuit. /// This distinction is critical because the returned proofs must contain all information necessary to verify the operations. pub trait SnarkableTree: Send + Sync { fn process_transaction(&mut self, transaction: Transaction) -> Result; - fn insert(&mut self, key: KeyHash, entry: HashchainEntry) -> Result; - fn update(&mut self, key: KeyHash, entry: HashchainEntry) -> Result; - fn get(&self, key: KeyHash) -> Result; + fn insert(&mut self, key: KeyHash, tx: Transaction) -> Result; + fn update(&mut self, key: KeyHash, tx: Transaction) -> Result; + fn get(&self, key: KeyHash) -> Result; } impl SnarkableTree for KeyDirectoryTree @@ -36,12 +36,12 @@ where S: TreeReader + TreeWriter + Send + Sync, { fn process_transaction(&mut self, transaction: Transaction) -> Result { - match &transaction.entry.operation { + match &transaction.operation { Operation::AddKey { .. } | Operation::RevokeKey { .. } | Operation::AddData { .. } => { let key_hash = KeyHash::with::(&transaction.id); - debug!("updating hashchain for user id {}", transaction.id); - let proof = self.update(key_hash, transaction.entry)?; + debug!("updating account for user id {}", transaction.id); + let proof = self.update(key_hash, transaction)?; Ok(Proof::Update(Box::new(proof))) } @@ -68,33 +68,26 @@ where 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); + let Found(service_account, _) = self.get(service_key_hash)? else { + bail!("Failed to get account for service ID {}", service_id); }; - let Some(service_last_entry) = service_hashchain.last() else { - bail!("Service hashchain is empty, could not retrieve challenge key"); - }; - - let creation_gate = match &service_last_entry.operation { - Operation::RegisterService { creation_gate, .. } => creation_gate, - _ => { - bail!("Service hashchain's last entry was not a RegisterService operation") - } + let Some(service_challenge) = service_account.service_challenge() else { + bail!("Service account does not contain a service challenge"); }; // Hash and sign credentials that have been signed by the external service let hash = Digest::hash_items(&[id.as_bytes(), service_id.as_bytes(), &key.to_bytes()]); - let ServiceChallenge::Signed(service_pubkey) = creation_gate; + let ServiceChallenge::Signed(service_pubkey) = service_challenge; let ServiceChallengeInput::Signed(challenge_signature) = &challenge; service_pubkey.verify_signature(&hash.to_bytes(), challenge_signature)?; - debug!("creating new hashchain for user ID {}", id); + debug!("creating new account for user ID {}", id); - let insert_proof = self.insert(account_key_hash, transaction.entry)?; + let insert_proof = self.insert(account_key_hash, transaction)?; Ok(Proof::Insert(Box::new(insert_proof))) } Operation::RegisterService { id, .. } => { @@ -105,33 +98,34 @@ where let key_hash = KeyHash::with::(id); - debug!("creating new hashchain for service id {}", id); + debug!("creating new account for service id {}", id); - let insert_proof = self.insert(key_hash, transaction.entry)?; + let insert_proof = self.insert(key_hash, transaction)?; Ok(Proof::Insert(Box::new(insert_proof))) } } } - fn insert(&mut self, key: KeyHash, entry: HashchainEntry) -> Result { + fn insert(&mut self, key: KeyHash, transaction: Transaction) -> Result { 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 { + let non_membership_proof = MerkleProof { root: old_root, proof: non_membership_merkle_proof, key, }; - let hashchain = Hashchain::from_entry(entry.clone())?; - let serialized_hashchain = hashchain.encode_to_bytes()?; + let mut account = Account::default(); + account.process_transaction(&transaction)?; + let serialized_account = account.encode_to_bytes()?; // the update proof just contains another nm proof let (new_root, _, tree_update_batch) = self .jmt - .put_value_set_with_proof(vec![(key, Some(serialized_hashchain))], self.epoch + 1)?; + .put_value_set_with_proof(vec![(key, Some(serialized_account))], self.epoch + 1)?; self.queue_batch(tree_update_batch); self.write_batch()?; @@ -139,26 +133,26 @@ where Ok(InsertProof { new_root: Digest(new_root.0), - new_entry: entry, + tx: transaction, non_membership_proof, membership_proof, }) } - fn update(&mut self, key: KeyHash, entry: HashchainEntry) -> Result { + fn update(&mut self, key: KeyHash, transaction: Transaction) -> Result { let old_root = self.get_current_root()?; - let (Some(old_serialized_hashchain), inclusion_proof) = + let (Some(old_serialized_account), inclusion_proof) = self.jmt.get_with_proof(key, self.epoch)? else { bail!("Key does not exist"); }; - let old_hashchain = Hashchain::decode_from_bytes(&old_serialized_hashchain)?; + let old_account = Account::decode_from_bytes(&old_serialized_account)?; - let mut new_hashchain = old_hashchain.clone(); - new_hashchain.add_entry(entry.clone())?; + let mut new_account = old_account.clone(); + new_account.process_transaction(&transaction)?; - let serialized_value = new_hashchain.encode_to_bytes()?; + let serialized_value = new_account.encode_to_bytes()?; let (new_root, update_proof, tree_update_batch) = self.jmt.put_value_set_with_proof( vec![(key, Some(serialized_value.clone()))], @@ -171,30 +165,25 @@ where old_root: Digest(old_root.0), new_root: Digest(new_root.0), inclusion_proof, - old_hashchain, + old_account, key, update_proof, - new_entry: entry, + tx: transaction, }) } - fn get(&self, key: KeyHash) -> Result { + fn get(&self, key: KeyHash) -> Result { let root = self.get_commitment()?; let (value, proof) = self.jmt.get_with_proof(key, self.epoch)?; match value { Some(serialized_value) => { - let deserialized_value = Hashchain::decode_from_bytes(&serialized_value)?; - let membership_proof = MembershipProof { - root, - proof, - key, - value: deserialized_value.clone(), - }; - Ok(Found(deserialized_value, membership_proof)) + let deserialized_value = Account::decode_from_bytes(&serialized_value)?; + let membership_proof = MerkleProof { root, proof, key }; + Ok(Found(Box::new(deserialized_value), membership_proof)) } None => { - let non_membership_proof = NonMembershipProof { root, proof, key }; + let non_membership_proof = MerkleProof { root, proof, key }; Ok(NotFound(non_membership_proof)) } } diff --git a/crates/tree/src/tests.rs b/crates/tree/src/tests.rs index 1637ea90..c5acd138 100644 --- a/crates/tree/src/tests.rs +++ b/crates/tree/src/tests.rs @@ -6,7 +6,7 @@ use prism_keys::SigningKey; use crate::{ hasher::TreeHasher, key_directory_tree::KeyDirectoryTree, proofs::Proof, - snarkable_tree::SnarkableTree, HashchainResponse::*, + snarkable_tree::SnarkableTree, AccountResponse::*, }; #[test] @@ -28,17 +28,16 @@ fn test_insert_and_get() { }; assert!(insert_proof.verify().is_ok()); - let Found(hashchain, membership_proof) = - tree.get(KeyHash::with::("acc_1")).unwrap() + let Found(account, membership_proof) = tree.get(KeyHash::with::("acc_1")).unwrap() else { - panic!("Expected hashchain to be found, but was not found.") + panic!("Expected account to be found, but was not found.") }; - let test_hashchain = - tx_builder.get_hashchain("acc_1").expect("Getting builder hashchain should work"); + let test_account = + tx_builder.get_account("acc_1").expect("Getting builder account should work"); - assert_eq!(&hashchain, test_hashchain); - assert!(membership_proof.verify().is_ok()); + assert_eq!(*account, *test_account); + assert!(membership_proof.verify_existence(&account).is_ok()); } #[test] @@ -135,9 +134,9 @@ fn test_update_existing_key() { 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(); + let test_account = tx_builder.get_account("acc_1").unwrap(); - assert!(matches!(get_result, Found(hc, _) if &hc == test_hashchain)); + assert!(matches!(get_result, Found(acc, _) if *acc == *test_account)); } #[test] @@ -152,7 +151,7 @@ fn test_update_non_existing_key() { // This is a signing key not known to the storage yet let random_signing_key = SigningKey::new_ed25519(); // This transaction shall be invalid, because it is signed with an unknown key - let invalid_key_tx = tx_builder.add_random_key("acc_1", &random_signing_key, 0).build(); + let invalid_key_tx = tx_builder.add_random_key("acc_1", &random_signing_key).build(); let result = tree.process_transaction(invalid_key_tx); assert!(result.is_err()); @@ -165,10 +164,10 @@ fn test_get_non_existing_key() { 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"); + panic!("Account found for key while it was expected to be missing"); }; - assert!(non_membership_proof.verify().is_ok()); + assert!(non_membership_proof.verify_nonexistence().is_ok()); } #[test] @@ -189,8 +188,9 @@ fn test_multiple_inserts_and_updates() { let key_1_tx = tx_builder.add_random_key_verified_with_root("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(); + let data_1_tx = tx_builder + .add_internally_signed_data_verified_with_root("acc_2", b"unsigned".to_vec()) + .commit(); tree.process_transaction(data_1_tx).unwrap(); let data_2_tx = tx_builder @@ -201,11 +201,11 @@ fn test_multiple_inserts_and_updates() { 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(); + let test_acc1 = tx_builder.get_account("acc_1").unwrap(); + let test_acc2 = tx_builder.get_account("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!(matches!(get_result1, Found(acc, _) if *acc == *test_acc1)); + assert!(matches!(get_result2, Found(acc, _) if *acc == *test_acc2)); } #[test] @@ -236,11 +236,11 @@ fn test_interleaved_inserts_and_updates() { 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(); + let test_acc1 = tx_builder.get_account("acc_1").unwrap(); + let test_acc2 = tx_builder.get_account("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!(matches!(get_result1, Found(acc, _) if *acc == *test_acc1)); + assert!(matches!(get_result2, Found(acc, _) if *acc == *test_acc2)); assert_eq!(update_proof.new_root, tree.get_commitment().unwrap()); } @@ -296,9 +296,9 @@ fn test_batch_writing() { 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(); + let test_acc1 = tx_builder.get_account("acc_1").unwrap(); + let test_acc2 = tx_builder.get_account("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!(matches!(get_result1, Found(acc, _) if *acc == *test_acc1)); + assert!(matches!(get_result2, Found(acc, _) if *acc == *test_acc2)); } diff --git a/elf/riscv32im-succinct-zkvm-elf b/elf/riscv32im-succinct-zkvm-elf index aed78087..b507e056 100755 Binary files a/elf/riscv32im-succinct-zkvm-elf and b/elf/riscv32im-succinct-zkvm-elf differ