1use crate::hash::{Hash, hash, keyed_hash};
9use crate::{PublicKey, SecretKey, SignatureBytes};
10use rand::RngCore;
11use thiserror::Error;
12
13pub type Commitment = Hash;
15
16#[derive(Debug, Clone)]
18pub struct CommitmentOpening {
19 pub value: Vec<u8>,
21 pub blinding: [u8; 32],
23}
24
25#[derive(Debug, Error)]
27pub enum CommitmentError {
28 #[error("Invalid commitment opening")]
29 InvalidOpening,
30
31 #[error("Commitment verification failed")]
32 VerificationFailed,
33
34 #[error("Invalid proof")]
35 InvalidProof,
36}
37
38pub fn commit(value: &[u8]) -> (Commitment, CommitmentOpening) {
42 let mut blinding = [0u8; 32];
43 rand::thread_rng().fill_bytes(&mut blinding);
44
45 let mut data = Vec::with_capacity(value.len() + 32);
46 data.extend_from_slice(value);
47 data.extend_from_slice(&blinding);
48
49 let commitment = hash(&data);
50
51 let opening = CommitmentOpening {
52 value: value.to_vec(),
53 blinding,
54 };
55
56 (commitment, opening)
57}
58
59pub fn verify_commitment(
61 commitment: &Commitment,
62 opening: &CommitmentOpening,
63) -> Result<(), CommitmentError> {
64 let mut data = Vec::with_capacity(opening.value.len() + 32);
65 data.extend_from_slice(&opening.value);
66 data.extend_from_slice(&opening.blinding);
67
68 let computed = hash(&data);
69
70 if &computed == commitment {
71 Ok(())
72 } else {
73 Err(CommitmentError::VerificationFailed)
74 }
75}
76
77#[derive(Debug, Clone)]
82pub struct ChunkPossessionProof {
83 pub challenge: [u8; 32],
85 pub response: Hash,
87}
88
89impl ChunkPossessionProof {
90 pub fn generate(chunk_data: &[u8], challenge: &[u8; 32]) -> Self {
96 let response = keyed_hash(challenge, chunk_data);
98
99 Self {
100 challenge: *challenge,
101 response,
102 }
103 }
104
105 pub fn verify(&self, chunk_data: &[u8]) -> Result<(), CommitmentError> {
110 let expected = keyed_hash(&self.challenge, chunk_data);
111
112 if expected == self.response {
113 Ok(())
114 } else {
115 Err(CommitmentError::InvalidProof)
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct ChunkChallenge {
123 pub nonce: [u8; 32],
125 pub chunk_index: u64,
127 pub expected_hash: Hash,
129}
130
131impl ChunkChallenge {
132 pub fn new(chunk_index: u64, expected_hash: Hash) -> Self {
134 let mut nonce = [0u8; 32];
135 rand::thread_rng().fill_bytes(&mut nonce);
136
137 Self {
138 nonce,
139 chunk_index,
140 expected_hash,
141 }
142 }
143
144 pub fn verify_proof(
146 &self,
147 chunk_data: &[u8],
148 proof: &ChunkPossessionProof,
149 ) -> Result<(), CommitmentError> {
150 let chunk_hash = hash(chunk_data);
152 if chunk_hash != self.expected_hash {
153 return Err(CommitmentError::InvalidProof);
154 }
155
156 if proof.challenge != self.nonce {
158 return Err(CommitmentError::InvalidProof);
159 }
160
161 proof.verify(chunk_data)
163 }
164}
165
166#[derive(Debug, Clone)]
170pub struct BandwidthProofCommitment {
171 pub chunk_commitment: Commitment,
173 pub timestamp: i64,
175 pub chunk_index: u64,
177}
178
179impl BandwidthProofCommitment {
180 pub fn new(chunk_data: &[u8], chunk_index: u64, timestamp: i64) -> (Self, CommitmentOpening) {
182 let (commitment, opening) = commit(chunk_data);
183
184 let bw_commitment = Self {
185 chunk_commitment: commitment,
186 timestamp,
187 chunk_index,
188 };
189
190 (bw_commitment, opening)
191 }
192
193 pub fn verify(
195 &self,
196 opening: &CommitmentOpening,
197 expected_chunk_data: &[u8],
198 ) -> Result<(), CommitmentError> {
199 verify_commitment(&self.chunk_commitment, opening)?;
201
202 if opening.value != expected_chunk_data {
204 return Err(CommitmentError::VerificationFailed);
205 }
206
207 Ok(())
208 }
209}
210
211#[derive(Debug, Clone)]
213pub struct KeyPossessionProof {
214 pub public_key: PublicKey,
216 pub challenge: [u8; 32],
218 pub signature: SignatureBytes,
220}
221
222impl KeyPossessionProof {
223 pub fn generate(
225 secret_key: &SecretKey,
226 challenge: &[u8; 32],
227 ) -> Result<Self, crate::SigningError> {
228 use crate::signing::KeyPair;
229
230 let keypair = KeyPair::from_secret_key(secret_key)?;
231 let public_key = keypair.public_key();
232 let signature = keypair.sign(challenge);
233
234 Ok(Self {
235 public_key,
236 challenge: *challenge,
237 signature,
238 })
239 }
240
241 pub fn verify(&self) -> Result<(), CommitmentError> {
243 use crate::signing::verify;
244
245 verify(&self.public_key, &self.challenge, &self.signature)
246 .map_err(|_| CommitmentError::InvalidProof)
247 }
248}
249
250pub fn generate_challenge() -> [u8; 32] {
252 let mut challenge = [0u8; 32];
253 rand::thread_rng().fill_bytes(&mut challenge);
254 challenge
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_commit_verify() {
263 let value = b"test data to commit";
264 let (commitment, opening) = commit(value);
265
266 assert!(verify_commitment(&commitment, &opening).is_ok());
267
268 let mut wrong_opening = opening.clone();
270 wrong_opening.value = b"wrong data".to_vec();
271 assert!(verify_commitment(&commitment, &wrong_opening).is_err());
272 }
273
274 #[test]
275 fn test_chunk_possession_proof() {
276 let chunk_data = b"chunk data that needs to be proven";
277 let challenge = generate_challenge();
278
279 let proof = ChunkPossessionProof::generate(chunk_data, &challenge);
280 assert!(proof.verify(chunk_data).is_ok());
281
282 assert!(proof.verify(b"wrong data").is_err());
284 }
285
286 #[test]
287 fn test_chunk_challenge() {
288 let chunk_data = b"test chunk data";
289 let chunk_hash = hash(chunk_data);
290 let challenge = ChunkChallenge::new(0, chunk_hash);
291
292 let proof = ChunkPossessionProof::generate(chunk_data, &challenge.nonce);
293 assert!(challenge.verify_proof(chunk_data, &proof).is_ok());
294
295 assert!(challenge.verify_proof(b"wrong chunk", &proof).is_err());
297 }
298
299 #[test]
300 fn test_bandwidth_proof_commitment() {
301 let chunk_data = b"bandwidth proof chunk";
302 let (commitment, opening) = BandwidthProofCommitment::new(chunk_data, 0, 1234567890);
303
304 assert!(commitment.verify(&opening, chunk_data).is_ok());
305 assert!(commitment.verify(&opening, b"wrong data").is_err());
306 }
307
308 #[test]
309 fn test_key_possession_proof() {
310 use crate::signing::KeyPair;
311
312 let keypair = KeyPair::generate();
313 let secret = keypair.secret_key();
314 let challenge = generate_challenge();
315
316 let proof = KeyPossessionProof::generate(&secret, &challenge).unwrap();
317 assert!(proof.verify().is_ok());
318
319 let mut bad_proof = proof.clone();
321 bad_proof.signature[0] ^= 1;
322 assert!(bad_proof.verify().is_err());
323 }
324
325 #[test]
326 fn test_different_blindings() {
327 let value = b"same value";
328
329 let (c1, _) = commit(value);
330 let (c2, _) = commit(value);
331
332 assert_ne!(c1, c2);
334 }
335}