1use alloc::vec::Vec;
14use serde::{Deserialize, Serialize};
15use sha2::{Digest, Sha256};
16
17use crate::hash::Hash;
18use crate::mpc::MpcTree;
19use crate::seal::SealRef;
20use crate::tagged_hash::csv_tagged_hash;
21
22pub const COMMITMENT_VERSION: u8 = 2;
27
28#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub struct Commitment {
55 pub version: u8,
57
58 pub protocol_id: [u8; 32],
65
66 pub mpc_root: Hash,
75
76 pub contract_id: Hash,
83
84 pub previous_commitment: Hash,
91
92 pub transition_payload_hash: Hash,
98
99 pub seal_id: Hash,
106
107 pub domain_separator: [u8; 32],
113}
114
115impl Commitment {
116 pub fn new(
122 protocol_id: [u8; 32],
123 mpc_tree: &MpcTree,
124 contract_id: Hash,
125 previous_commitment: Hash,
126 transition_payload_hash: Hash,
127 seal_ref: &SealRef,
128 domain_separator: [u8; 32],
129 ) -> Self {
130 let seal_hash = {
131 let mut hasher = Sha256::new();
132 hasher.update(seal_ref.to_vec());
133 let result = hasher.finalize();
134 let mut array = [0u8; 32];
135 array.copy_from_slice(&result);
136 Hash::new(array)
137 };
138
139 let mpc_root = mpc_tree.root();
140
141 Self {
142 version: COMMITMENT_VERSION,
143 protocol_id,
144 mpc_root,
145 contract_id,
146 previous_commitment,
147 transition_payload_hash,
148 seal_id: seal_hash,
149 domain_separator,
150 }
151 }
152
153 pub fn simple(
158 contract_id: Hash,
159 previous_commitment: Hash,
160 transition_payload_hash: Hash,
161 seal_ref: &SealRef,
162 domain_separator: [u8; 32],
163 ) -> Self {
164 let seal_hash = {
165 let mut hasher = Sha256::new();
166 hasher.update(seal_ref.to_vec());
167 let result = hasher.finalize();
168 let mut array = [0u8; 32];
169 array.copy_from_slice(&result);
170 Hash::new(array)
171 };
172
173 let mut protocol_id = [0u8; 32];
175 protocol_id[..4].copy_from_slice(&domain_separator[..4]);
176
177 let mpc_root = {
179 let mut hasher = Sha256::new();
180 hasher.update(b"csv-empty-mpc-root");
181 let result = hasher.finalize();
182 let mut array = [0u8; 32];
183 array.copy_from_slice(&result);
184 Hash::new(array)
185 };
186
187 Self {
188 version: COMMITMENT_VERSION,
189 protocol_id,
190 mpc_root,
191 contract_id,
192 previous_commitment,
193 transition_payload_hash,
194 seal_id: seal_hash,
195 domain_separator,
196 }
197 }
198
199 #[deprecated(since = "0.2.0", note = "Use `Commitment::simple` instead")]
201 pub fn v1(
202 contract_id: Hash,
203 previous_commitment: Hash,
204 transition_payload_hash: Hash,
205 seal_ref: &SealRef,
206 domain_separator: [u8; 32],
207 ) -> Self {
208 Self::simple(
209 contract_id,
210 previous_commitment,
211 transition_payload_hash,
212 seal_ref,
213 domain_separator,
214 )
215 }
216
217 pub fn hash(&self) -> Hash {
219 let mut hasher = Sha256::new();
220 self.hash_into(&mut hasher);
221 let result = hasher.finalize();
222 let mut array = [0u8; 32];
223 array.copy_from_slice(&result);
224 Hash::new(array)
225 }
226
227 fn hash_into(&self, hasher: &mut Sha256) {
228 hasher.update(csv_tagged_hash("commitment-version", &[self.version]));
230 hasher.update(csv_tagged_hash("commitment-protocol-id", &self.protocol_id));
231 hasher.update(csv_tagged_hash(
232 "commitment-mpc-root",
233 self.mpc_root.as_bytes(),
234 ));
235 hasher.update(csv_tagged_hash(
236 "commitment-contract-id",
237 self.contract_id.as_bytes(),
238 ));
239 hasher.update(csv_tagged_hash(
240 "commitment-prev",
241 self.previous_commitment.as_bytes(),
242 ));
243 hasher.update(csv_tagged_hash(
244 "commitment-payload",
245 self.transition_payload_hash.as_bytes(),
246 ));
247 hasher.update(csv_tagged_hash("commitment-seal", self.seal_id.as_bytes()));
248 hasher.update(csv_tagged_hash("commitment-domain", &self.domain_separator));
249 }
250
251 pub fn version(&self) -> u8 {
253 self.version
254 }
255
256 pub fn contract_id(&self) -> Hash {
258 self.contract_id
259 }
260
261 pub fn seal_id(&self) -> Hash {
263 self.seal_id
264 }
265
266 pub fn domain_separator(&self) -> [u8; 32] {
268 self.domain_separator
269 }
270
271 pub fn to_canonical_bytes(&self) -> Vec<u8> {
273 let mut out = Vec::with_capacity(1 + 32 * 7);
274 out.push(self.version);
275 out.extend_from_slice(&self.protocol_id);
276 out.extend_from_slice(self.mpc_root.as_bytes());
277 out.extend_from_slice(self.contract_id.as_bytes());
278 out.extend_from_slice(self.previous_commitment.as_bytes());
279 out.extend_from_slice(self.transition_payload_hash.as_bytes());
280 out.extend_from_slice(self.seal_id.as_bytes());
281 out.extend_from_slice(&self.domain_separator);
282 out
283 }
284
285 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
289 if bytes.is_empty() {
290 return Err("Empty commitment bytes");
291 }
292
293 let version = bytes[0];
294 if version != COMMITMENT_VERSION {
295 return Err("Unsupported commitment version");
296 }
297
298 let min_len = 1 + 32 * 7;
301 if bytes.len() < min_len {
302 return Err("Commitment bytes too short");
303 }
304
305 let mut protocol_id = [0u8; 32];
306 protocol_id.copy_from_slice(&bytes[1..33]);
307 let mut mpc_root = [0u8; 32];
308 mpc_root.copy_from_slice(&bytes[33..65]);
309 let mut contract_id = [0u8; 32];
310 contract_id.copy_from_slice(&bytes[65..97]);
311 let mut previous_commitment = [0u8; 32];
312 previous_commitment.copy_from_slice(&bytes[97..129]);
313 let mut transition_payload_hash = [0u8; 32];
314 transition_payload_hash.copy_from_slice(&bytes[129..161]);
315 let mut seal_id = [0u8; 32];
316 seal_id.copy_from_slice(&bytes[161..193]);
317 let mut domain_separator = [0u8; 32];
318 domain_separator.copy_from_slice(&bytes[193..225]);
319
320 Ok(Self {
321 version,
322 protocol_id,
323 mpc_root: Hash::new(mpc_root),
324 contract_id: Hash::new(contract_id),
325 previous_commitment: Hash::new(previous_commitment),
326 transition_payload_hash: Hash::new(transition_payload_hash),
327 seal_id: Hash::new(seal_id),
328 domain_separator,
329 })
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use crate::mpc::MpcTree;
337
338 fn test_commitment_commitment() -> Commitment {
339 Commitment::simple(
340 Hash::new([1u8; 32]),
341 Hash::new([2u8; 32]),
342 Hash::new([3u8; 32]),
343 &SealRef::new(vec![4u8; 16], Some(42)).unwrap(),
344 [5u8; 32],
345 )
346 }
347
348 fn test_mpc_commitment() -> Commitment {
349 let protocol_id = [10u8; 32];
350 let mpc_tree = MpcTree::from_pairs(&[
351 (protocol_id, Hash::new([20u8; 32])),
352 ([20u8; 32], Hash::new([30u8; 32])),
353 ]);
354 Commitment::new(
355 protocol_id,
356 &mpc_tree,
357 Hash::new([1u8; 32]),
358 Hash::new([2u8; 32]),
359 Hash::new([3u8; 32]),
360 &SealRef::new(vec![4u8; 16], Some(42)).unwrap(),
361 [5u8; 32],
362 )
363 }
364
365 #[test]
370 fn test_commitment_creation() {
371 let c = test_commitment_commitment();
372 assert_eq!(c.version(), COMMITMENT_VERSION);
373 }
374
375 #[test]
376 fn test_commitment_hash_deterministic() {
377 let c1 = test_commitment_commitment();
378 let c2 = test_commitment_commitment();
379 assert_eq!(c1.hash(), c2.hash());
380 }
381
382 #[test]
383 fn test_commitment_canonical_roundtrip() {
384 let c = test_commitment_commitment();
385 let bytes = c.to_canonical_bytes();
386 let restored = Commitment::from_canonical_bytes(&bytes).unwrap();
387 assert_eq!(c.hash(), restored.hash());
388 }
389
390 #[test]
395 fn test_mpc_creation() {
396 let c = test_mpc_commitment();
397 assert_eq!(c.version(), COMMITMENT_VERSION);
398 }
399
400 #[test]
401 fn test_mpc_hash_deterministic() {
402 let c1 = test_mpc_commitment();
403 let c2 = test_mpc_commitment();
404 assert_eq!(c1.hash(), c2.hash());
405 }
406
407 #[test]
408 fn test_mpc_canonical_roundtrip() {
409 let c = test_mpc_commitment();
410 let bytes = c.to_canonical_bytes();
411 let restored = Commitment::from_canonical_bytes(&bytes).unwrap();
412 assert_eq!(c.hash(), restored.hash());
413 }
414
415 #[test]
416 fn test_mpc_contains_mpc_root() {
417 let protocol_id = [10u8; 32];
418 let mpc_tree = MpcTree::from_pairs(&[
419 (protocol_id, Hash::new([20u8; 32])),
420 ([20u8; 32], Hash::new([30u8; 32])),
421 ]);
422 let _expected_root = mpc_tree.root();
423
424 let seal = SealRef::new(vec![4u8; 16], Some(42)).unwrap();
425 let c = Commitment::new(
426 protocol_id,
427 &mpc_tree,
428 Hash::new([1u8; 32]),
429 Hash::new([2u8; 32]),
430 Hash::new([3u8; 32]),
431 &seal,
432 [5u8; 32],
433 );
434
435 let different_tree = MpcTree::from_pairs(&[(protocol_id, Hash::new([99u8; 32]))]);
437 let c_different = Commitment::new(
438 protocol_id,
439 &different_tree,
440 Hash::new([1u8; 32]),
441 Hash::new([2u8; 32]),
442 Hash::new([3u8; 32]),
443 &seal,
444 [5u8; 32],
445 );
446
447 assert_ne!(c.hash(), c_different.hash());
448 }
449
450 #[test]
451 fn test_mpc_differs_by_protocol_id() {
452 let mpc_tree = MpcTree::from_pairs(&[([10u8; 32], Hash::new([20u8; 32]))]);
453 let seal = SealRef::new(vec![4u8; 16], Some(42)).unwrap();
454
455 let c1 = Commitment::new(
456 [10u8; 32],
457 &mpc_tree,
458 Hash::new([1u8; 32]),
459 Hash::new([2u8; 32]),
460 Hash::new([3u8; 32]),
461 &seal,
462 [5u8; 32],
463 );
464
465 let c2 = Commitment::new(
466 [11u8; 32],
467 &mpc_tree,
468 Hash::new([1u8; 32]),
469 Hash::new([2u8; 32]),
470 Hash::new([3u8; 32]),
471 &seal,
472 [5u8; 32],
473 );
474
475 assert_ne!(c1.hash(), c2.hash());
476 }
477
478 #[test]
483 fn test_commitment_v2_different_hashes() {
484 let v1 = test_commitment_commitment();
485 let v2 = test_mpc_commitment();
486 assert_ne!(v1.hash(), v2.hash());
487 }
488
489 #[test]
490 fn test_commitment_v2_same_contract_different_versions() {
491 let v1 = test_commitment_commitment();
492 let v2 = test_mpc_commitment();
493 assert_eq!(v1.contract_id(), v2.contract_id());
495 assert_ne!(v1.hash(), v2.hash());
497 }
498
499 #[test]
504 fn test_commitment_accessors() {
505 let v1 = test_commitment_commitment();
506 assert_eq!(v1.contract_id(), Hash::new([1u8; 32]));
507 assert_eq!(v1.domain_separator(), [5u8; 32]);
508
509 let v2 = test_mpc_commitment();
510 assert_eq!(v2.contract_id(), Hash::new([1u8; 32]));
511 assert_eq!(v2.domain_separator(), [5u8; 32]);
512 }
513
514 #[test]
519 fn test_from_bytes_empty() {
520 assert!(Commitment::from_canonical_bytes(&[]).is_err());
521 }
522
523 #[test]
524 fn test_from_bytes_unknown_version() {
525 let mut bytes = vec![99u8];
526 bytes.resize(225, 0);
527 assert!(Commitment::from_canonical_bytes(&bytes).is_err());
528 }
529
530 #[test]
531 fn test_from_bytes_unsupported_version() {
532 assert!(Commitment::from_canonical_bytes(&[1, 0, 0]).is_err()); }
534
535 #[test]
536 fn test_from_bytes_too_short() {
537 assert!(Commitment::from_canonical_bytes(&[2, 0, 0]).is_err());
538 }
539
540 #[test]
545 fn test_commitment_with_multi_protocol_mpc() {
546 let proto_a = [0xAA; 32];
548 let proto_b = [0xBB; 32];
549 let proto_c = [0xCC; 32];
550
551 let mpc_tree = MpcTree::from_pairs(&[
552 (proto_a, Hash::new([1u8; 32])),
553 (proto_b, Hash::new([2u8; 32])),
554 (proto_c, Hash::new([3u8; 32])),
555 ]);
556
557 let seal = SealRef::new(vec![0xDD; 16], Some(1)).unwrap();
559 let commitment_a = Commitment::new(
560 proto_a,
561 &mpc_tree,
562 Hash::new([10u8; 32]),
563 Hash::zero(),
564 Hash::new([11u8; 32]),
565 &seal,
566 [0xEE; 32],
567 );
568
569 assert_eq!(commitment_a.mpc_root, mpc_tree.root());
571
572 let proof = mpc_tree.prove(proto_a).unwrap();
574 assert!(proof.verify(&mpc_tree.root()));
575 }
576}