1use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum DigestAlg {
12 Blake3,
13 P2pandaOperation,
14 Sha256,
15 William3,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct Digest {
21 pub alg: DigestAlg,
22 pub bytes: Vec<u8>,
23}
24
25impl Digest {
26 pub fn blake3(bytes: &[u8]) -> Self {
28 Self {
29 alg: DigestAlg::Blake3,
30 bytes: blake3::hash(bytes).as_bytes().to_vec(),
31 }
32 }
33
34 pub fn p2panda_operation(bytes: [u8; 32]) -> Self {
36 Self {
37 alg: DigestAlg::P2pandaOperation,
38 bytes: bytes.to_vec(),
39 }
40 }
41
42 pub fn as_32(&self) -> Result<[u8; 32], ProofTypeError> {
44 self.bytes
45 .as_slice()
46 .try_into()
47 .map_err(|_| ProofTypeError::WrongDigestLength(self.bytes.len()))
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum CommitmentScheme {
54 DigestV1,
56 MerkleV1,
57 SparseMerkleV1,
58 MmrV1,
59 AccumulatorV1,
60 VectorCommitmentV1,
61 PsiCardinalityV1,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum CommitmentDomain {
67 MootAdmins,
68 MootMods,
69 MootStakeholders,
70 MootMembers,
71 TesseraReceipts,
72 StorageChunks,
73 StorageCheckpoints,
74 MeshJobResults,
75 DelegationSet,
76}
77
78#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub struct Commitment {
81 pub scheme: CommitmentScheme,
82 pub domain: CommitmentDomain,
83 pub version: u16,
84 pub digest: Digest,
85}
86
87impl Commitment {
88 pub fn direct(domain: CommitmentDomain, bytes: &[u8]) -> Self {
90 Self {
91 scheme: CommitmentScheme::DigestV1,
92 domain,
93 version: 1,
94 digest: Digest::blake3(bytes),
95 }
96 }
97
98 pub fn verifies_direct(&self, domain: CommitmentDomain, bytes: &[u8]) -> bool {
100 self.scheme == CommitmentScheme::DigestV1
101 && self.domain == domain
102 && self.version == 1
103 && self.digest == Digest::blake3(bytes)
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
109pub struct BlobRef {
110 pub digest: Digest,
111 pub byte_len: u64,
112}
113
114impl BlobRef {
115 pub fn blake3(bytes: &[u8]) -> Self {
116 Self {
117 digest: Digest::blake3(bytes),
118 byte_len: bytes.len() as u64,
119 }
120 }
121
122 pub fn verifies(&self, bytes: &[u8]) -> bool {
123 self.byte_len == bytes.len() as u64 && self.digest == Digest::blake3(bytes)
124 }
125}
126
127#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
128pub enum ProofTypeError {
129 #[error("expected a 32-byte digest, found {0} bytes")]
130 WrongDigestLength(usize),
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn content_reference_and_application_commitment_keep_distinct_meanings() {
139 let bytes = b"checkpoint";
140 let blob = BlobRef::blake3(bytes);
141 let commitment = Commitment::direct(CommitmentDomain::StorageCheckpoints, bytes);
142 assert!(blob.verifies(bytes));
143 assert!(commitment.verifies_direct(CommitmentDomain::StorageCheckpoints, bytes));
144 assert_eq!(blob.digest, commitment.digest);
145 assert_ne!(blob.digest.alg, DigestAlg::P2pandaOperation);
146 }
147
148 #[test]
149 fn p2panda_identity_is_typed_separately() {
150 let operation = Digest::p2panda_operation([7; 32]);
151 assert_eq!(operation.alg, DigestAlg::P2pandaOperation);
152 assert_eq!(operation.as_32().unwrap(), [7; 32]);
153 }
154}