confium_tc_core/
commitment.rs1use rand_core::{OsRng, RngCore};
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
23pub struct Commitment {
24 pub hash: [u8; 32],
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30pub struct Decommitment {
31 pub randomness: [u8; 32],
33 pub value: Vec<u8>,
35}
36
37impl Commitment {
38 pub fn create(value: &[u8]) -> (Self, Decommitment) {
41 let mut randomness = [0u8; 32];
42 OsRng.fill_bytes(&mut randomness);
43 let hash = compute_hash(&randomness, value);
44 (
45 Self { hash },
46 Decommitment {
47 randomness,
48 value: value.to_vec(),
49 },
50 )
51 }
52
53 pub fn create_with_randomness(value: &[u8], randomness: [u8; 32]) -> (Self, Decommitment) {
56 let hash = compute_hash(&randomness, value);
57 (
58 Self { hash },
59 Decommitment {
60 randomness,
61 value: value.to_vec(),
62 },
63 )
64 }
65
66 pub fn verify(&self, decommitment: &Decommitment) -> bool {
68 let computed = compute_hash(&decommitment.randomness, &decommitment.value);
69 use subtle::ConstantTimeEq;
70 self.hash.ct_eq(&computed).into()
71 }
72
73 pub fn to_hex(&self) -> String {
75 hex::encode(self.hash)
76 }
77
78 pub fn from_hex(hex_str: &str) -> Result<Self, String> {
80 let bytes = hex::decode(hex_str).map_err(|e| e.to_string())?;
81 if bytes.len() != 32 {
82 return Err(format!("expected 32 bytes, got {}", bytes.len()));
83 }
84 let mut hash = [0u8; 32];
85 hash.copy_from_slice(&bytes);
86 Ok(Self { hash })
87 }
88}
89
90fn compute_hash(randomness: &[u8; 32], value: &[u8]) -> [u8; 32] {
91 let mut hasher = Sha256::new();
92 hasher.update(randomness);
93 hasher.update(value);
94 let result = hasher.finalize();
95 let mut hash = [0u8; 32];
96 hash.copy_from_slice(&result);
97 hash
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn create_and_verify_round_trips() {
106 let value = b"my secret nonce";
107 let (commitment, decommitment) = Commitment::create(value);
108 assert!(commitment.verify(&decommitment));
109 }
110
111 #[test]
112 fn commitment_hides_value() {
113 let (commitment_a, _) = Commitment::create(b"value A");
114 let (commitment_b, _) = Commitment::create(b"value B");
115 assert_ne!(commitment_a.hash, commitment_b.hash);
118 }
119
120 #[test]
121 fn tampered_value_rejected() {
122 let (_, mut decommitment) = Commitment::create(b"original");
123 let commitment = Commitment {
124 hash: compute_hash(&decommitment.randomness, b"original"),
125 };
126 decommitment.value = b"tampered".to_vec();
127 assert!(!commitment.verify(&decommitment));
128 }
129
130 #[test]
131 fn tampered_randomness_rejected() {
132 let (commitment, mut decommitment) = Commitment::create(b"value");
133 decommitment.randomness[0] ^= 0xFF;
134 assert!(!commitment.verify(&decommitment));
135 }
136
137 #[test]
138 fn same_value_different_randomness() {
139 let value = b"deterministic value";
140 let (c1, _) = Commitment::create_with_randomness(value, [0xAA; 32]);
141 let (c2, _) = Commitment::create_with_randomness(value, [0xBB; 32]);
142 assert_ne!(c1.hash, c2.hash);
143 }
144
145 #[test]
146 fn deterministic_creation_reproducible() {
147 let value = b"test";
148 let r = [0x42; 32];
149 let (c1, d1) = Commitment::create_with_randomness(value, r);
150 let (c2, d2) = Commitment::create_with_randomness(value, r);
151 assert_eq!(c1, c2);
152 assert_eq!(d1, d2);
153 assert!(c1.verify(&d1));
154 }
155
156 #[test]
157 fn hex_round_trip() {
158 let (commitment, _) = Commitment::create(b"value");
159 let hex = commitment.to_hex();
160 let recovered = Commitment::from_hex(&hex).unwrap();
161 assert_eq!(commitment, recovered);
162 }
163
164 #[test]
165 fn from_hex_rejects_wrong_length() {
166 assert!(Commitment::from_hex("00").is_err());
167 assert!(Commitment::from_hex(&"ab".repeat(31)).is_err());
168 assert!(Commitment::from_hex(&"ab".repeat(33)).is_err());
169 }
170
171 #[test]
172 fn empty_value_commits() {
173 let (commitment, decommitment) = Commitment::create(b"");
174 assert!(commitment.verify(&decommitment));
175 }
176
177 #[test]
178 fn large_value_commits() {
179 let value = vec![0xFFu8; 100_000];
180 let (commitment, decommitment) = Commitment::create(&value);
181 assert!(commitment.verify(&decommitment));
182 }
183
184 #[test]
185 fn verify_uses_constant_time_comparison() {
186 let (commitment, decommitment) = Commitment::create(b"value");
187 assert!(commitment.verify(&decommitment));
189 }
190}