use crate::hash::{Hash, hash, keyed_hash};
use crate::{PublicKey, SecretKey, SignatureBytes};
use rand::Rng as _;
use thiserror::Error;
pub type Commitment = Hash;
#[derive(Debug, Clone)]
pub struct CommitmentOpening {
pub value: Vec<u8>,
pub blinding: [u8; 32],
}
#[derive(Debug, Error)]
pub enum CommitmentError {
#[error("Invalid commitment opening")]
InvalidOpening,
#[error("Commitment verification failed")]
VerificationFailed,
#[error("Invalid proof")]
InvalidProof,
}
pub fn commit(value: &[u8]) -> (Commitment, CommitmentOpening) {
let mut blinding = [0u8; 32];
rand::rng().fill_bytes(&mut blinding);
let mut data = Vec::with_capacity(value.len() + 32);
data.extend_from_slice(value);
data.extend_from_slice(&blinding);
let commitment = hash(&data);
let opening = CommitmentOpening {
value: value.to_vec(),
blinding,
};
(commitment, opening)
}
pub fn verify_commitment(
commitment: &Commitment,
opening: &CommitmentOpening,
) -> Result<(), CommitmentError> {
let mut data = Vec::with_capacity(opening.value.len() + 32);
data.extend_from_slice(&opening.value);
data.extend_from_slice(&opening.blinding);
let computed = hash(&data);
if &computed == commitment {
Ok(())
} else {
Err(CommitmentError::VerificationFailed)
}
}
#[derive(Debug, Clone)]
pub struct ChunkPossessionProof {
pub challenge: [u8; 32],
pub response: Hash,
}
impl ChunkPossessionProof {
pub fn generate(chunk_data: &[u8], challenge: &[u8; 32]) -> Self {
let response = keyed_hash(challenge, chunk_data);
Self {
challenge: *challenge,
response,
}
}
pub fn verify(&self, chunk_data: &[u8]) -> Result<(), CommitmentError> {
let expected = keyed_hash(&self.challenge, chunk_data);
if expected == self.response {
Ok(())
} else {
Err(CommitmentError::InvalidProof)
}
}
}
#[derive(Debug, Clone)]
pub struct ChunkChallenge {
pub nonce: [u8; 32],
pub chunk_index: u64,
pub expected_hash: Hash,
}
impl ChunkChallenge {
pub fn new(chunk_index: u64, expected_hash: Hash) -> Self {
let mut nonce = [0u8; 32];
rand::rng().fill_bytes(&mut nonce);
Self {
nonce,
chunk_index,
expected_hash,
}
}
pub fn verify_proof(
&self,
chunk_data: &[u8],
proof: &ChunkPossessionProof,
) -> Result<(), CommitmentError> {
let chunk_hash = hash(chunk_data);
if chunk_hash != self.expected_hash {
return Err(CommitmentError::InvalidProof);
}
if proof.challenge != self.nonce {
return Err(CommitmentError::InvalidProof);
}
proof.verify(chunk_data)
}
}
#[derive(Debug, Clone)]
pub struct BandwidthProofCommitment {
pub chunk_commitment: Commitment,
pub timestamp: i64,
pub chunk_index: u64,
}
impl BandwidthProofCommitment {
pub fn new(chunk_data: &[u8], chunk_index: u64, timestamp: i64) -> (Self, CommitmentOpening) {
let (commitment, opening) = commit(chunk_data);
let bw_commitment = Self {
chunk_commitment: commitment,
timestamp,
chunk_index,
};
(bw_commitment, opening)
}
pub fn verify(
&self,
opening: &CommitmentOpening,
expected_chunk_data: &[u8],
) -> Result<(), CommitmentError> {
verify_commitment(&self.chunk_commitment, opening)?;
if opening.value != expected_chunk_data {
return Err(CommitmentError::VerificationFailed);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct KeyPossessionProof {
pub public_key: PublicKey,
pub challenge: [u8; 32],
pub signature: SignatureBytes,
}
impl KeyPossessionProof {
pub fn generate(
secret_key: &SecretKey,
challenge: &[u8; 32],
) -> Result<Self, crate::SigningError> {
use crate::signing::KeyPair;
let keypair = KeyPair::from_secret_key(secret_key)?;
let public_key = keypair.public_key();
let signature = keypair.sign(challenge);
Ok(Self {
public_key,
challenge: *challenge,
signature,
})
}
pub fn verify(&self) -> Result<(), CommitmentError> {
use crate::signing::verify;
verify(&self.public_key, &self.challenge, &self.signature)
.map_err(|_| CommitmentError::InvalidProof)
}
}
pub fn generate_challenge() -> [u8; 32] {
let mut challenge = [0u8; 32];
rand::rng().fill_bytes(&mut challenge);
challenge
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_commit_verify() {
let value = b"test data to commit";
let (commitment, opening) = commit(value);
assert!(verify_commitment(&commitment, &opening).is_ok());
let mut wrong_opening = opening.clone();
wrong_opening.value = b"wrong data".to_vec();
assert!(verify_commitment(&commitment, &wrong_opening).is_err());
}
#[test]
fn test_chunk_possession_proof() {
let chunk_data = b"chunk data that needs to be proven";
let challenge = generate_challenge();
let proof = ChunkPossessionProof::generate(chunk_data, &challenge);
assert!(proof.verify(chunk_data).is_ok());
assert!(proof.verify(b"wrong data").is_err());
}
#[test]
fn test_chunk_challenge() {
let chunk_data = b"test chunk data";
let chunk_hash = hash(chunk_data);
let challenge = ChunkChallenge::new(0, chunk_hash);
let proof = ChunkPossessionProof::generate(chunk_data, &challenge.nonce);
assert!(challenge.verify_proof(chunk_data, &proof).is_ok());
assert!(challenge.verify_proof(b"wrong chunk", &proof).is_err());
}
#[test]
fn test_bandwidth_proof_commitment() {
let chunk_data = b"bandwidth proof chunk";
let (commitment, opening) = BandwidthProofCommitment::new(chunk_data, 0, 1234567890);
assert!(commitment.verify(&opening, chunk_data).is_ok());
assert!(commitment.verify(&opening, b"wrong data").is_err());
}
#[test]
fn test_key_possession_proof() {
use crate::signing::KeyPair;
let keypair = KeyPair::generate();
let secret = keypair.secret_key();
let challenge = generate_challenge();
let proof = KeyPossessionProof::generate(&secret, &challenge).unwrap();
assert!(proof.verify().is_ok());
let mut bad_proof = proof.clone();
bad_proof.signature[0] ^= 1;
assert!(bad_proof.verify().is_err());
}
#[test]
fn test_different_blindings() {
let value = b"same value";
let (c1, _) = commit(value);
let (c2, _) = commit(value);
assert_ne!(c1, c2);
}
}