use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::hash::Hash;
use crate::mpc::MpcTree;
use crate::seal::SealRef;
use crate::tagged_hash::csv_tagged_hash;
pub const COMMITMENT_VERSION: u8 = 2;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Commitment {
pub version: u8,
pub protocol_id: [u8; 32],
pub mpc_root: Hash,
pub contract_id: Hash,
pub previous_commitment: Hash,
pub transition_payload_hash: Hash,
pub seal_id: Hash,
pub domain_separator: [u8; 32],
}
impl Commitment {
pub fn new(
protocol_id: [u8; 32],
mpc_tree: &MpcTree,
contract_id: Hash,
previous_commitment: Hash,
transition_payload_hash: Hash,
seal_ref: &SealRef,
domain_separator: [u8; 32],
) -> Self {
let seal_hash = {
let mut hasher = Sha256::new();
hasher.update(seal_ref.to_vec());
let result = hasher.finalize();
let mut array = [0u8; 32];
array.copy_from_slice(&result);
Hash::new(array)
};
let mpc_root = mpc_tree.root();
Self {
version: COMMITMENT_VERSION,
protocol_id,
mpc_root,
contract_id,
previous_commitment,
transition_payload_hash,
seal_id: seal_hash,
domain_separator,
}
}
pub fn simple(
contract_id: Hash,
previous_commitment: Hash,
transition_payload_hash: Hash,
seal_ref: &SealRef,
domain_separator: [u8; 32],
) -> Self {
let seal_hash = {
let mut hasher = Sha256::new();
hasher.update(seal_ref.to_vec());
let result = hasher.finalize();
let mut array = [0u8; 32];
array.copy_from_slice(&result);
Hash::new(array)
};
let mut protocol_id = [0u8; 32];
protocol_id[..4].copy_from_slice(&domain_separator[..4]);
let mpc_root = {
let mut hasher = Sha256::new();
hasher.update(b"csv-empty-mpc-root");
let result = hasher.finalize();
let mut array = [0u8; 32];
array.copy_from_slice(&result);
Hash::new(array)
};
Self {
version: COMMITMENT_VERSION,
protocol_id,
mpc_root,
contract_id,
previous_commitment,
transition_payload_hash,
seal_id: seal_hash,
domain_separator,
}
}
#[deprecated(since = "0.2.0", note = "Use `Commitment::simple` instead")]
pub fn v1(
contract_id: Hash,
previous_commitment: Hash,
transition_payload_hash: Hash,
seal_ref: &SealRef,
domain_separator: [u8; 32],
) -> Self {
Self::simple(
contract_id,
previous_commitment,
transition_payload_hash,
seal_ref,
domain_separator,
)
}
pub fn hash(&self) -> Hash {
let mut hasher = Sha256::new();
self.hash_into(&mut hasher);
let result = hasher.finalize();
let mut array = [0u8; 32];
array.copy_from_slice(&result);
Hash::new(array)
}
fn hash_into(&self, hasher: &mut Sha256) {
hasher.update(csv_tagged_hash("commitment-version", &[self.version]));
hasher.update(csv_tagged_hash("commitment-protocol-id", &self.protocol_id));
hasher.update(csv_tagged_hash(
"commitment-mpc-root",
self.mpc_root.as_bytes(),
));
hasher.update(csv_tagged_hash(
"commitment-contract-id",
self.contract_id.as_bytes(),
));
hasher.update(csv_tagged_hash(
"commitment-prev",
self.previous_commitment.as_bytes(),
));
hasher.update(csv_tagged_hash(
"commitment-payload",
self.transition_payload_hash.as_bytes(),
));
hasher.update(csv_tagged_hash("commitment-seal", self.seal_id.as_bytes()));
hasher.update(csv_tagged_hash("commitment-domain", &self.domain_separator));
}
pub fn version(&self) -> u8 {
self.version
}
pub fn contract_id(&self) -> Hash {
self.contract_id
}
pub fn seal_id(&self) -> Hash {
self.seal_id
}
pub fn domain_separator(&self) -> [u8; 32] {
self.domain_separator
}
pub fn to_canonical_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(1 + 32 * 7);
out.push(self.version);
out.extend_from_slice(&self.protocol_id);
out.extend_from_slice(self.mpc_root.as_bytes());
out.extend_from_slice(self.contract_id.as_bytes());
out.extend_from_slice(self.previous_commitment.as_bytes());
out.extend_from_slice(self.transition_payload_hash.as_bytes());
out.extend_from_slice(self.seal_id.as_bytes());
out.extend_from_slice(&self.domain_separator);
out
}
pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
if bytes.is_empty() {
return Err("Empty commitment bytes");
}
let version = bytes[0];
if version != COMMITMENT_VERSION {
return Err("Unsupported commitment version");
}
let min_len = 1 + 32 * 7;
if bytes.len() < min_len {
return Err("Commitment bytes too short");
}
let mut protocol_id = [0u8; 32];
protocol_id.copy_from_slice(&bytes[1..33]);
let mut mpc_root = [0u8; 32];
mpc_root.copy_from_slice(&bytes[33..65]);
let mut contract_id = [0u8; 32];
contract_id.copy_from_slice(&bytes[65..97]);
let mut previous_commitment = [0u8; 32];
previous_commitment.copy_from_slice(&bytes[97..129]);
let mut transition_payload_hash = [0u8; 32];
transition_payload_hash.copy_from_slice(&bytes[129..161]);
let mut seal_id = [0u8; 32];
seal_id.copy_from_slice(&bytes[161..193]);
let mut domain_separator = [0u8; 32];
domain_separator.copy_from_slice(&bytes[193..225]);
Ok(Self {
version,
protocol_id,
mpc_root: Hash::new(mpc_root),
contract_id: Hash::new(contract_id),
previous_commitment: Hash::new(previous_commitment),
transition_payload_hash: Hash::new(transition_payload_hash),
seal_id: Hash::new(seal_id),
domain_separator,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mpc::MpcTree;
fn test_commitment_commitment() -> Commitment {
Commitment::simple(
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&SealRef::new(vec![4u8; 16], Some(42)).unwrap(),
[5u8; 32],
)
}
fn test_mpc_commitment() -> Commitment {
let protocol_id = [10u8; 32];
let mpc_tree = MpcTree::from_pairs(&[
(protocol_id, Hash::new([20u8; 32])),
([20u8; 32], Hash::new([30u8; 32])),
]);
Commitment::new(
protocol_id,
&mpc_tree,
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&SealRef::new(vec![4u8; 16], Some(42)).unwrap(),
[5u8; 32],
)
}
#[test]
fn test_commitment_creation() {
let c = test_commitment_commitment();
assert_eq!(c.version(), COMMITMENT_VERSION);
}
#[test]
fn test_commitment_hash_deterministic() {
let c1 = test_commitment_commitment();
let c2 = test_commitment_commitment();
assert_eq!(c1.hash(), c2.hash());
}
#[test]
fn test_commitment_canonical_roundtrip() {
let c = test_commitment_commitment();
let bytes = c.to_canonical_bytes();
let restored = Commitment::from_canonical_bytes(&bytes).unwrap();
assert_eq!(c.hash(), restored.hash());
}
#[test]
fn test_mpc_creation() {
let c = test_mpc_commitment();
assert_eq!(c.version(), COMMITMENT_VERSION);
}
#[test]
fn test_mpc_hash_deterministic() {
let c1 = test_mpc_commitment();
let c2 = test_mpc_commitment();
assert_eq!(c1.hash(), c2.hash());
}
#[test]
fn test_mpc_canonical_roundtrip() {
let c = test_mpc_commitment();
let bytes = c.to_canonical_bytes();
let restored = Commitment::from_canonical_bytes(&bytes).unwrap();
assert_eq!(c.hash(), restored.hash());
}
#[test]
fn test_mpc_contains_mpc_root() {
let protocol_id = [10u8; 32];
let mpc_tree = MpcTree::from_pairs(&[
(protocol_id, Hash::new([20u8; 32])),
([20u8; 32], Hash::new([30u8; 32])),
]);
let _expected_root = mpc_tree.root();
let seal = SealRef::new(vec![4u8; 16], Some(42)).unwrap();
let c = Commitment::new(
protocol_id,
&mpc_tree,
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&seal,
[5u8; 32],
);
let different_tree = MpcTree::from_pairs(&[(protocol_id, Hash::new([99u8; 32]))]);
let c_different = Commitment::new(
protocol_id,
&different_tree,
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&seal,
[5u8; 32],
);
assert_ne!(c.hash(), c_different.hash());
}
#[test]
fn test_mpc_differs_by_protocol_id() {
let mpc_tree = MpcTree::from_pairs(&[([10u8; 32], Hash::new([20u8; 32]))]);
let seal = SealRef::new(vec![4u8; 16], Some(42)).unwrap();
let c1 = Commitment::new(
[10u8; 32],
&mpc_tree,
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&seal,
[5u8; 32],
);
let c2 = Commitment::new(
[11u8; 32],
&mpc_tree,
Hash::new([1u8; 32]),
Hash::new([2u8; 32]),
Hash::new([3u8; 32]),
&seal,
[5u8; 32],
);
assert_ne!(c1.hash(), c2.hash());
}
#[test]
fn test_commitment_v2_different_hashes() {
let v1 = test_commitment_commitment();
let v2 = test_mpc_commitment();
assert_ne!(v1.hash(), v2.hash());
}
#[test]
fn test_commitment_v2_same_contract_different_versions() {
let v1 = test_commitment_commitment();
let v2 = test_mpc_commitment();
assert_eq!(v1.contract_id(), v2.contract_id());
assert_ne!(v1.hash(), v2.hash());
}
#[test]
fn test_commitment_accessors() {
let v1 = test_commitment_commitment();
assert_eq!(v1.contract_id(), Hash::new([1u8; 32]));
assert_eq!(v1.domain_separator(), [5u8; 32]);
let v2 = test_mpc_commitment();
assert_eq!(v2.contract_id(), Hash::new([1u8; 32]));
assert_eq!(v2.domain_separator(), [5u8; 32]);
}
#[test]
fn test_from_bytes_empty() {
assert!(Commitment::from_canonical_bytes(&[]).is_err());
}
#[test]
fn test_from_bytes_unknown_version() {
let mut bytes = vec![99u8];
bytes.resize(225, 0);
assert!(Commitment::from_canonical_bytes(&bytes).is_err());
}
#[test]
fn test_from_bytes_unsupported_version() {
assert!(Commitment::from_canonical_bytes(&[1, 0, 0]).is_err()); }
#[test]
fn test_from_bytes_too_short() {
assert!(Commitment::from_canonical_bytes(&[2, 0, 0]).is_err());
}
#[test]
fn test_commitment_with_multi_protocol_mpc() {
let proto_a = [0xAA; 32];
let proto_b = [0xBB; 32];
let proto_c = [0xCC; 32];
let mpc_tree = MpcTree::from_pairs(&[
(proto_a, Hash::new([1u8; 32])),
(proto_b, Hash::new([2u8; 32])),
(proto_c, Hash::new([3u8; 32])),
]);
let seal = SealRef::new(vec![0xDD; 16], Some(1)).unwrap();
let commitment_a = Commitment::new(
proto_a,
&mpc_tree,
Hash::new([10u8; 32]),
Hash::zero(),
Hash::new([11u8; 32]),
&seal,
[0xEE; 32],
);
assert_eq!(commitment_a.mpc_root, mpc_tree.root());
let proof = mpc_tree.prove(proto_a).unwrap();
assert!(proof.verify(&mpc_tree.root()));
}
}