Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/common/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use anyhow::Result;
use celestia_types::Blob;
use prism_keys::{Signature, SigningKey, VerifyingKey};
use prism_serde::binary::{FromBinary, ToBinary};
Expand Down
2 changes: 2 additions & 0 deletions crates/tree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ jmt.workspace = true
sha2.workspace = true

# serde
bincode.workspace = true
prism-serde.workspace = true
serde.workspace = true

# misc
anyhow.workspace = true
tracing.workspace = true
thiserror.workspace = true

[dev-dependencies]
paste = { workspace = true }
Expand Down
110 changes: 78 additions & 32 deletions crates/tree/src/proofs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::collections::HashMap;

use anyhow::{bail, Context, Result};
use jmt::{
proof::{SparseMerkleNode, SparseMerkleProof, UpdateMerkleProof},
KeyHash, RootHash,
proof::{SparseMerkleNode, SparseMerkleProof, UpdateMerkleProof},
};
use prism_common::{
account::Account,
Expand All @@ -13,6 +12,7 @@ use prism_common::{
};
use prism_serde::binary::ToBinary;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::hasher::TreeHasher;

Expand All @@ -38,7 +38,7 @@ impl Batch {
}
}

pub fn verify(&self) -> Result<()> {
pub fn verify(&self) -> Result<(), ProofError> {
let mut root = self.prev_root;
for proof in &self.proofs {
match proof {
Expand All @@ -51,7 +51,9 @@ impl Batch {
.get(service_id)
.and_then(|service_proof| service_proof.service_challenge());
if service_challenge.is_none() {
bail!("Service proof for {} is missing from batch for CreateAccount verification", service_id);
return Err(ProofError::MissingServiceChallenge(
service_id.to_string(),
));
}
service_challenge
}
Expand All @@ -72,7 +74,10 @@ impl Batch {
for (id, service_proof) in &self.service_proofs {
let keyhash = KeyHash::with::<TreeHasher>(&id);
let serialized_account = service_proof.service.encode_to_bytes()?;
service_proof.proof.verify_existence(RootHash(root.0), keyhash, serialized_account)?;
service_proof
.proof
.verify_existence(RootHash(root.0), keyhash, serialized_account)
.map_err(|e| ProofError::ExistenceError(e.to_string()))?;
}

Ok(())
Expand Down Expand Up @@ -119,11 +124,14 @@ pub struct InsertProof {

impl InsertProof {
/// The method called in circuit to verify the state transition to the new root.
pub fn verify(&self, service_challenge: Option<&ServiceChallenge>) -> Result<()> {
self.non_membership_proof.verify_nonexistence().context("Invalid NonMembershipProof")?;

pub fn verify(&self, service_challenge: Option<&ServiceChallenge>) -> Result<(), ProofError> {
self.non_membership_proof
.verify_nonexistence()
.map_err(|e| ProofError::NonexistenceError(e.to_string()))?;
let mut account = Account::default();
account.process_transaction(&self.tx)?;
account
.process_transaction(&self.tx)
.map_err(|e| ProofError::TransactionError(e.to_string()))?;

// If we are creating an account, we need to additionally verify the service challenge
if let Operation::CreateAccount {
Expand All @@ -136,21 +144,26 @@ impl InsertProof {
let hash = Digest::hash_items(&[id.as_bytes(), service_id.as_bytes(), &key.to_bytes()]);

if service_challenge.is_none() {
bail!("Service challenge is missing for CreateAccount verification");
return Err(ProofError::MissingServiceChallenge(service_id.to_string()));
}

let ServiceChallenge::Signed(challenge_vk) = service_challenge.unwrap();
let ServiceChallengeInput::Signed(challenge_signature) = challenge;
challenge_vk.verify_signature(hash, challenge_signature)?;
challenge_vk
.verify_signature(hash, challenge_signature)
.map_err(|e| ProofError::VerificationError(e.to_string()))?;
}

let serialized_account = account.encode_to_bytes()?;

self.membership_proof.clone().verify_existence(
RootHash(self.new_root.0),
self.non_membership_proof.key,
serialized_account,
)?;
self.membership_proof
.clone()
.verify_existence(
RootHash(self.new_root.0),
self.non_membership_proof.key,
serialized_account,
)
.map_err(|e| ProofError::VerificationError(e.to_string()))?;

Ok(())
}
Expand All @@ -175,26 +188,29 @@ pub struct UpdateProof {

impl UpdateProof {
/// The method called in circuit to verify the state transition to the new root.
pub fn verify(&self) -> Result<()> {
pub fn verify(&self) -> Result<(), ProofError> {
// Verify existence of old value.
// 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_account,
)?;
self.inclusion_proof
.verify_existence(RootHash(self.old_root.0), self.key, old_serialized_account)
.map_err(|e| ProofError::VerificationError(e.to_string()))?;

let mut new_account = self.old_account.clone();
new_account.process_transaction(&self.tx)?;
new_account
.process_transaction(&self.tx)
.map_err(|e| ProofError::TransactionError(e.to_string()))?;

// 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_account))],
)?;
self.update_proof
.clone()
.verify_update(
RootHash(self.old_root.0),
RootHash(self.new_root.0),
vec![(self.key, Some(new_serialized_account))],
)
.map_err(|e| ProofError::AccountError(e.to_string()))?;

Ok(())
}
Expand All @@ -208,13 +224,17 @@ pub struct MerkleProof {
}

impl MerkleProof {
pub fn verify_existence(&self, value: &Account) -> Result<()> {
pub fn verify_existence(&self, value: &Account) -> Result<(), ProofError> {
let value = value.encode_to_bytes()?;
self.proof.verify_existence(RootHash(self.root.0), self.key, value)
self.proof
.verify_existence(RootHash(self.root.0), self.key, value)
.map_err(|e| ProofError::VerificationError(e.to_string()))
}

pub fn verify_nonexistence(&self) -> Result<()> {
self.proof.verify_nonexistence(RootHash(self.root.0), self.key)
pub fn verify_nonexistence(&self) -> Result<(), ProofError> {
self.proof
.verify_nonexistence(RootHash(self.root.0), self.key)
.map_err(|e| ProofError::VerificationError(e.to_string()))
}
}

Expand All @@ -240,3 +260,29 @@ pub struct HashedMerkleProof {
pub leaf: Option<Digest>,
pub siblings: Vec<Digest>,
}

#[derive(Error, Clone, Debug)]
pub enum ProofError {
#[error("service proof is missing from batch for create account verification: {0}")]
MissingServiceProof(String),
#[error("service challenge is missing for create account verification: {0}")]
MissingServiceChallenge(String),
#[error("encoding error: {0}")]
EncodingError(String),
#[error("account update error: {0}")]
AccountError(String),
#[error("proof verification error: {0}")]
VerificationError(String),
#[error("existence error: {0}")]
ExistenceError(String),
#[error("nonexistence error: {0}")]
NonexistenceError(String),
#[error("Transaction error: {0}")]
TransactionError(String),
}

impl From<bincode::Error> for ProofError {
fn from(err: bincode::Error) -> Self {
ProofError::EncodingError(err.to_string())
}
}
Loading