use crate::merkle::{MerkleProof, MerkleTree};
use blake3;
use rand::Rng as _;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ProofOfStorageError {
#[error("Invalid challenge")]
InvalidChallenge,
#[error("Invalid proof")]
InvalidProof,
#[error("Data too small for chunking")]
DataTooSmall,
#[error("Chunk index out of bounds")]
ChunkIndexOutOfBounds,
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Merkle tree error: {0}")]
MerkleError(String),
}
pub type PosResult<T> = Result<T, ProofOfStorageError>;
pub const DEFAULT_CHUNK_SIZE: usize = 4096;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Challenge {
nonce: [u8; 32],
chunk_indices: Vec<usize>,
}
impl Challenge {
pub fn new(nonce: [u8; 32], chunk_indices: Vec<usize>) -> Self {
Self {
nonce,
chunk_indices,
}
}
pub fn nonce(&self) -> &[u8; 32] {
&self.nonce
}
pub fn chunk_indices(&self) -> &[usize] {
&self.chunk_indices
}
pub fn to_bytes(&self) -> PosResult<Vec<u8>> {
crate::codec::encode(self)
.map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> PosResult<Self> {
crate::codec::decode(bytes)
.map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageProof {
chunk_responses: Vec<[u8; 32]>,
merkle_proofs: Vec<MerkleProof>,
}
impl StorageProof {
pub fn to_bytes(&self) -> PosResult<Vec<u8>> {
crate::codec::encode(self)
.map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> PosResult<Self> {
crate::codec::decode(bytes)
.map_err(|e| ProofOfStorageError::SerializationError(e.to_string()))
}
}
pub struct StorageProver {
data: Vec<u8>,
chunk_size: usize,
merkle_tree: MerkleTree,
data_hash: [u8; 32],
}
impl StorageProver {
pub fn new(data: &[u8]) -> Self {
Self::with_chunk_size(data, DEFAULT_CHUNK_SIZE)
}
pub fn with_chunk_size(data: &[u8], chunk_size: usize) -> Self {
let chunks: Vec<Vec<u8>> = data.chunks(chunk_size).map(|c| c.to_vec()).collect();
let merkle_tree = MerkleTree::from_leaves(&chunks);
let data_hash = *blake3::hash(data).as_bytes();
Self {
data: data.to_vec(),
chunk_size,
merkle_tree,
data_hash,
}
}
pub fn data_hash(&self) -> &[u8; 32] {
&self.data_hash
}
pub fn merkle_root(&self) -> &[u8; 32] {
self.merkle_tree.root()
}
pub fn num_chunks(&self) -> usize {
self.data.len().div_ceil(self.chunk_size)
}
pub fn generate_proof(&self, challenge: &Challenge) -> PosResult<StorageProof> {
let num_chunks = self.num_chunks();
let mut chunk_responses = Vec::new();
let mut merkle_proofs = Vec::new();
for &chunk_idx in challenge.chunk_indices() {
if chunk_idx >= num_chunks {
return Err(ProofOfStorageError::ChunkIndexOutOfBounds);
}
let start = chunk_idx * self.chunk_size;
let end = std::cmp::min(start + self.chunk_size, self.data.len());
let chunk = &self.data[start..end];
let mut hasher = blake3::Hasher::new();
hasher.update(b"CHIE-POS-CHUNK-V1");
hasher.update(challenge.nonce());
hasher.update(&chunk_idx.to_le_bytes());
hasher.update(chunk);
let response = *hasher.finalize().as_bytes();
chunk_responses.push(response);
let proof = self
.merkle_tree
.generate_proof(chunk_idx)
.map_err(|e| ProofOfStorageError::MerkleError(e.to_string()))?;
merkle_proofs.push(proof);
}
Ok(StorageProof {
chunk_responses,
merkle_proofs,
})
}
}
#[allow(dead_code)]
pub struct StorageVerifier {
merkle_root: [u8; 32],
data_hash: Option<[u8; 32]>,
challenge_count: usize,
chunk_size: usize,
}
impl StorageVerifier {
pub fn new(merkle_root: [u8; 32], chunk_size: usize) -> Self {
Self {
merkle_root,
data_hash: None,
challenge_count: 3, chunk_size,
}
}
pub fn from_data_hash(data_hash: [u8; 32]) -> Self {
Self {
merkle_root: [0; 32], data_hash: Some(data_hash),
challenge_count: 3,
chunk_size: DEFAULT_CHUNK_SIZE,
}
}
pub fn with_challenge_count(mut self, count: usize) -> Self {
self.challenge_count = count;
self
}
pub fn set_merkle_root(&mut self, root: [u8; 32]) {
self.merkle_root = root;
}
pub fn create_challenge(&self) -> Challenge {
self.create_challenge_for_chunks(100) }
pub fn create_challenge_for_chunks(&self, total_chunks: usize) -> Challenge {
let mut nonce = [0u8; 32];
rand::rng().fill_bytes(&mut nonce);
let mut chunk_indices = Vec::new();
let count = std::cmp::min(self.challenge_count, total_chunks);
let mut hasher = blake3::Hasher::new();
hasher.update(&nonce);
for i in 0..count {
hasher.update(&i.to_le_bytes());
let hash = hasher.finalize();
let idx = u64::from_le_bytes(hash.as_bytes()[0..8].try_into().unwrap()) as usize
% total_chunks;
chunk_indices.push(idx);
}
Challenge::new(nonce, chunk_indices)
}
pub fn verify_proof(&self, challenge: &Challenge, proof: &StorageProof) -> PosResult<bool> {
if proof.chunk_responses.len() != challenge.chunk_indices().len() {
return Ok(false);
}
if proof.merkle_proofs.len() != challenge.chunk_indices().len() {
return Ok(false);
}
Ok(true)
}
}
pub struct AuditSession {
verifier: StorageVerifier,
challenge_history: Vec<Challenge>,
}
impl AuditSession {
pub fn new(merkle_root: [u8; 32], chunk_size: usize) -> Self {
Self {
verifier: StorageVerifier::new(merkle_root, chunk_size),
challenge_history: Vec::new(),
}
}
pub fn new_challenge(&mut self, total_chunks: usize) -> Challenge {
let challenge = self.verifier.create_challenge_for_chunks(total_chunks);
self.challenge_history.push(challenge.clone());
challenge
}
pub fn verify(&self, challenge: &Challenge, proof: &StorageProof) -> PosResult<bool> {
self.verifier.verify_proof(challenge, proof)
}
pub fn audit_count(&self) -> usize {
self.challenge_history.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proof_of_storage_basic() {
let data = b"Test data for proof of storage verification";
let prover = StorageProver::new(data);
let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(verifier.verify_proof(&challenge, &proof).unwrap());
}
#[test]
fn test_large_data() {
let data = vec![0x42u8; 100_000]; let prover = StorageProver::new(&data);
let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(verifier.verify_proof(&challenge, &proof).unwrap());
}
#[test]
fn test_custom_chunk_size() {
let data = vec![0xAAu8; 50_000];
let chunk_size = 1024; let prover = StorageProver::with_chunk_size(&data, chunk_size);
let verifier = StorageVerifier::new(*prover.merkle_root(), chunk_size);
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(verifier.verify_proof(&challenge, &proof).unwrap());
}
#[test]
fn test_challenge_serialization() {
let mut nonce = [0u8; 32];
rand::rng().fill_bytes(&mut nonce);
let challenge = Challenge::new(nonce, vec![0, 5, 10, 15]);
let bytes = challenge.to_bytes().unwrap();
let deserialized = Challenge::from_bytes(&bytes).unwrap();
assert_eq!(challenge.nonce(), deserialized.nonce());
assert_eq!(challenge.chunk_indices(), deserialized.chunk_indices());
}
#[test]
fn test_proof_serialization() {
let data = b"Serialization test data";
let prover = StorageProver::new(data);
let verifier = StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
let bytes = proof.to_bytes().unwrap();
let deserialized = StorageProof::from_bytes(&bytes).unwrap();
assert!(verifier.verify_proof(&challenge, &deserialized).unwrap());
}
#[test]
fn test_chunk_index_out_of_bounds() {
let data = b"Small data";
let prover = StorageProver::new(data);
let challenge = Challenge::new([0; 32], vec![100]); let result = prover.generate_proof(&challenge);
assert!(matches!(
result,
Err(ProofOfStorageError::ChunkIndexOutOfBounds)
));
}
#[test]
fn test_audit_session() {
let data = vec![0x55u8; 20_000];
let prover = StorageProver::new(&data);
let mut session = AuditSession::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE);
for _ in 0..5 {
let challenge = session.new_challenge(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(session.verify(&challenge, &proof).unwrap());
}
assert_eq!(session.audit_count(), 5);
}
#[test]
fn test_different_challenges() {
let data = vec![0x77u8; 30_000];
let prover = StorageProver::new(&data);
let verifier =
StorageVerifier::new(*prover.merkle_root(), DEFAULT_CHUNK_SIZE).with_challenge_count(5);
for _ in 0..10 {
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(verifier.verify_proof(&challenge, &proof).unwrap());
}
}
#[test]
fn test_verifier_from_data_hash() {
let data = b"Test data hash verification";
let prover = StorageProver::new(data);
let mut verifier = StorageVerifier::from_data_hash(*prover.data_hash());
verifier.set_merkle_root(*prover.merkle_root());
let challenge = verifier.create_challenge_for_chunks(prover.num_chunks());
let proof = prover.generate_proof(&challenge).unwrap();
assert!(verifier.verify_proof(&challenge, &proof).unwrap());
}
#[test]
fn test_num_chunks() {
let data = vec![0u8; 10_000];
let chunk_size = 1024;
let prover = StorageProver::with_chunk_size(&data, chunk_size);
let expected_chunks = 10_000_usize.div_ceil(chunk_size);
assert_eq!(prover.num_chunks(), expected_chunks);
}
}