Skip to main content

prolly/prolly/
proof.rs

1//! Verifiable key path proofs for prolly trees.
2//!
3//! A key proof contains the root-to-leaf node path needed to verify either the
4//! value at a key or the absence of that key under a root CID. Verification is
5//! store-independent: it recomputes node CIDs from the supplied path, checks
6//! child links, follows internal separator keys, and derives the value from the
7//! terminal leaf.
8
9use super::cid::Cid;
10use super::diff::DiffPage;
11use super::error::{Diff, Error};
12use super::key;
13use super::node::Node;
14use super::range::{RangeCursor, RangePage};
15#[cfg(feature = "async-store")]
16use super::store::AsyncStore;
17use super::store::Store;
18use super::tree::Tree;
19#[cfg(feature = "async-store")]
20use super::AsyncProlly;
21use super::Prolly;
22use serde::{Deserialize, Serialize};
23use sha2::{Digest, Sha256};
24use std::collections::{HashMap, HashSet};
25
26const PROOF_BUNDLE_VERSION: u64 = 1;
27const PROOF_BUNDLE_KIND_KEY: u8 = 1;
28const PROOF_BUNDLE_KIND_MULTI_KEY: u8 = 2;
29const PROOF_BUNDLE_KIND_RANGE: u8 = 3;
30const PROOF_BUNDLE_KIND_RANGE_PAGE: u8 = 4;
31const PROOF_BUNDLE_KIND_DIFF_PAGE: u8 = 5;
32const AUTHENTICATED_PROOF_ENVELOPE_VERSION: u64 = 1;
33const AUTHENTICATED_PROOF_ENVELOPE_ALGORITHM_HMAC_SHA256: &str = "hmac-sha256";
34const AUTHENTICATED_PROOF_ENVELOPE_DOMAIN: &[u8] = b"trail.prolly.authenticated-proof-envelope.v1";
35
36#[derive(Serialize, Deserialize)]
37struct ProofBundleWire {
38    version: u64,
39    kind: u8,
40    root: Option<Vec<u8>>,
41    keys: Vec<Vec<u8>>,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    start: Option<Vec<u8>>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    end: Option<Vec<u8>>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    after: Option<Vec<u8>>,
48    path_node_bytes: Vec<Vec<u8>>,
49}
50
51#[derive(Serialize, Deserialize)]
52struct AuthenticatedProofEnvelopeSigningWire {
53    version: u64,
54    algorithm: String,
55    key_id: Vec<u8>,
56    proof_bundle: Vec<u8>,
57    context: Vec<u8>,
58    issued_at_millis: Option<u64>,
59    expires_at_millis: Option<u64>,
60    nonce: Vec<u8>,
61}
62
63#[derive(Serialize, Deserialize)]
64struct AuthenticatedProofEnvelopeWire {
65    version: u64,
66    algorithm: String,
67    key_id: Vec<u8>,
68    proof_bundle: Vec<u8>,
69    context: Vec<u8>,
70    issued_at_millis: Option<u64>,
71    expires_at_millis: Option<u64>,
72    nonce: Vec<u8>,
73    signature: Vec<u8>,
74}
75
76#[derive(Serialize, Deserialize)]
77struct DiffPageProofBundleWire {
78    version: u64,
79    kind: u8,
80    requested_end: Option<Vec<u8>>,
81    limit: u64,
82    base_range_page_proof: Vec<u8>,
83    other_range_page_proof: Vec<u8>,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    lookahead_base_key_proof: Option<Vec<u8>>,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    lookahead_other_key_proof: Option<Vec<u8>>,
88}
89
90/// Root-to-leaf proof for one key.
91#[derive(Clone, Debug, PartialEq)]
92pub struct KeyProof {
93    /// Root CID the path claims to prove against, or `None` for an empty tree.
94    pub root: Option<Cid>,
95    /// Key being proven.
96    pub key: Vec<u8>,
97    /// Nodes from root to leaf. Empty only when `root` is `None`.
98    pub path: Vec<Node>,
99}
100
101/// Store-independent verification result for a [`KeyProof`].
102#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct KeyProofVerification {
104    /// Whether the proof path is internally consistent for `root` and `key`.
105    pub valid: bool,
106    /// Root CID from the proof.
107    pub root: Option<Cid>,
108    /// Key from the proof.
109    pub key: Vec<u8>,
110    /// Verified value when the key is present. `None` means verified absence
111    /// when `valid` is true.
112    pub value: Option<Vec<u8>>,
113}
114
115/// Shared root proof for multiple keys.
116///
117/// The `path` vector stores each proof node at most once. Verification follows
118/// the root-to-leaf route for every key through this node set and preserves the
119/// original key order in [`MultiKeyProofVerification::results`].
120#[derive(Clone, Debug, PartialEq)]
121pub struct MultiKeyProof {
122    /// Root CID the node set claims to prove against, or `None` for an empty
123    /// tree.
124    pub root: Option<Cid>,
125    /// Keys being proven, in caller-requested order.
126    pub keys: Vec<Vec<u8>>,
127    /// De-duplicated nodes needed to prove all keys. Empty when `root` is
128    /// `None` or `keys` is empty.
129    pub path: Vec<Node>,
130}
131
132/// Store-independent verification result for a [`MultiKeyProof`].
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct MultiKeyProofVerification {
135    /// Whether every requested key proof is valid.
136    pub valid: bool,
137    /// Root CID from the proof.
138    pub root: Option<Cid>,
139    /// Per-key verification results, in the same order as the proof keys.
140    pub results: Vec<KeyProofVerification>,
141}
142
143/// Complete proof for all entries in a key range.
144///
145/// The `path` vector stores every node needed to verify that all keys in
146/// `[start, end)` have been included and that no other matching keys are
147/// omitted.
148#[derive(Clone, Debug, PartialEq)]
149pub struct RangeProof {
150    /// Root CID the node set claims to prove against, or `None` for an empty
151    /// tree.
152    pub root: Option<Cid>,
153    /// Inclusive range start.
154    pub start: Vec<u8>,
155    /// Exclusive range end. `None` means unbounded.
156    pub end: Option<Vec<u8>>,
157    /// De-duplicated nodes needed to prove the full range.
158    pub path: Vec<Node>,
159}
160
161/// Proof for a resumable range page.
162///
163/// Unlike [`RangeProof`], the lower bound is an optional exclusive cursor
164/// boundary. That lets the proof model exactly match [`RangeCursor`] semantics:
165/// a page proves every entry with `key > after` and `key < end`.
166#[derive(Clone, Debug, PartialEq)]
167pub struct RangePageProof {
168    /// Root CID the node set claims to prove against, or `None` for an empty
169    /// tree.
170    pub root: Option<Cid>,
171    /// Exclusive cursor lower bound. `None` means start at the beginning of
172    /// the keyspace.
173    pub after: Option<Vec<u8>>,
174    /// Exclusive page upper bound. `None` means unbounded.
175    pub end: Option<Vec<u8>>,
176    /// De-duplicated nodes needed to prove the page window.
177    pub path: Vec<Node>,
178}
179
180/// A bounded range page paired with a store-independent proof for that page.
181#[derive(Clone, Debug, PartialEq)]
182pub struct ProvedRangePage {
183    /// Page entries plus the cursor that should be used to request the next
184    /// page, if more entries were observed while constructing this proof.
185    pub page: RangePage,
186    /// Proof for the page window. Verifying this proof yields exactly
187    /// `page.entries`.
188    pub proof: RangePageProof,
189}
190
191/// Proof for a resumable diff page.
192///
193/// The proof verifies the base and other entries for the page key window, then
194/// recomputes the diff offline. When another diff exists after the page, the
195/// optional lookahead key proofs prove the first omitted diff key so the
196/// verifier can derive the same continuation cursor as [`Prolly::diff_page`].
197#[derive(Clone, Debug, PartialEq)]
198pub struct DiffPageProof {
199    /// Range-page proof over the base tree for the verified key window.
200    pub base: RangePageProof,
201    /// Range-page proof over the other tree for the verified key window.
202    pub other: RangePageProof,
203    /// Base-tree key proof for the first omitted diff key when there is another
204    /// page.
205    pub lookahead_base: Option<KeyProof>,
206    /// Other-tree key proof for the first omitted diff key when there is another
207    /// page.
208    pub lookahead_other: Option<KeyProof>,
209    /// Original exclusive upper bound requested by the caller.
210    pub requested_end: Option<Vec<u8>>,
211    /// Original page limit requested by the caller.
212    pub limit: usize,
213}
214
215/// A bounded diff page paired with a store-independent proof for that page.
216#[derive(Clone, Debug, PartialEq)]
217pub struct ProvedDiffPage {
218    /// Diff entries plus the cursor that should be used to request the next
219    /// page, if more diffs were observed while constructing this proof.
220    pub page: DiffPage,
221    /// Proof whose verification recomputes exactly `page.diffs` and
222    /// `page.next_cursor`.
223    pub proof: DiffPageProof,
224}
225
226/// HMAC-authenticated envelope for any proof bundle.
227///
228/// The envelope does not change proof semantics: `proof_bundle` is still decoded
229/// by [`KeyProof::from_bundle_bytes`], [`MultiKeyProof::from_bundle_bytes`],
230/// [`RangeProof::from_bundle_bytes`], or [`RangePageProof::from_bundle_bytes`].
231/// The envelope adds provenance fields and an HMAC-SHA256 signature so a peer
232/// can reject tampered bundles before decoding them.
233#[derive(Clone, Debug, PartialEq, Eq)]
234pub struct AuthenticatedProofEnvelope {
235    /// Signature algorithm. Version 1 supports `hmac-sha256`.
236    pub algorithm: String,
237    /// Caller-defined identifier used to select the shared secret.
238    pub key_id: Vec<u8>,
239    /// Canonical proof bundle bytes from one of the proof `to_bundle_bytes`
240    /// methods.
241    pub proof_bundle: Vec<u8>,
242    /// Application-defined domain bytes, such as tenant, snapshot, endpoint, or
243    /// authorization scope.
244    pub context: Vec<u8>,
245    /// Optional issue time in Unix milliseconds.
246    pub issued_at_millis: Option<u64>,
247    /// Optional expiration time in Unix milliseconds.
248    pub expires_at_millis: Option<u64>,
249    /// Caller-provided nonce to make envelopes unique across repeated proofs.
250    pub nonce: Vec<u8>,
251    /// HMAC-SHA256 over the canonical signing payload.
252    pub signature: Vec<u8>,
253}
254
255/// Verification result for an [`AuthenticatedProofEnvelope`].
256#[derive(Clone, Debug, PartialEq, Eq)]
257pub struct AuthenticatedProofEnvelopeVerification {
258    /// True when the algorithm is supported, the signature matches, and optional
259    /// time bounds are valid for the supplied verification time.
260    pub valid: bool,
261    /// True when the algorithm is supported and the HMAC matches the envelope
262    /// signing payload.
263    pub signature_valid: bool,
264    /// True when the optional issue/expiration bounds are valid or time checking
265    /// was skipped.
266    pub time_valid: bool,
267    /// True when `issued_at_millis` is later than the supplied verification time.
268    pub not_yet_valid: bool,
269    /// True when `expires_at_millis` is less than or equal to the supplied
270    /// verification time.
271    pub expired: bool,
272    /// Signature algorithm from the envelope.
273    pub algorithm: String,
274    /// Caller-defined key identifier from the envelope.
275    pub key_id: Vec<u8>,
276    /// Authenticated proof bundle bytes.
277    pub proof_bundle: Vec<u8>,
278    /// Authenticated application context bytes.
279    pub context: Vec<u8>,
280    /// Issue time from the envelope.
281    pub issued_at_millis: Option<u64>,
282    /// Expiration time from the envelope.
283    pub expires_at_millis: Option<u64>,
284    /// Nonce from the envelope.
285    pub nonce: Vec<u8>,
286}
287
288/// Canonical proof bundle family.
289#[derive(Clone, Copy, Debug, PartialEq, Eq)]
290pub enum ProofBundleKind {
291    /// Bundle created by [`KeyProof::to_bundle_bytes`].
292    Key,
293    /// Bundle created by [`MultiKeyProof::to_bundle_bytes`].
294    MultiKey,
295    /// Bundle created by [`RangeProof::to_bundle_bytes`].
296    Range,
297    /// Bundle created by [`RangePageProof::to_bundle_bytes`].
298    RangePage,
299    /// Bundle created by [`DiffPageProof::to_bundle_bytes`].
300    DiffPage,
301}
302
303impl ProofBundleKind {
304    /// Stable lowercase identifier for language bindings and logs.
305    pub fn as_str(self) -> &'static str {
306        match self {
307            ProofBundleKind::Key => "key",
308            ProofBundleKind::MultiKey => "multi_key",
309            ProofBundleKind::Range => "range",
310            ProofBundleKind::RangePage => "range_page",
311            ProofBundleKind::DiffPage => "diff_page",
312        }
313    }
314}
315
316/// Lightweight metadata decoded from canonical proof bundle bytes.
317///
318/// This summary is intended for routing opaque proof bundles before a caller
319/// chooses the typed decoder. It validates bundle framing and root CID lengths,
320/// but it does not replace proof verification.
321#[derive(Clone, Debug, PartialEq, Eq)]
322pub struct ProofBundleSummary {
323    /// Bundle format version.
324    pub version: u64,
325    /// Proof bundle family.
326    pub kind: ProofBundleKind,
327    /// Root CID for single-root proofs, or the base root for diff-page proofs.
328    pub root: Option<Cid>,
329    /// Target/root CID for diff-page proofs. `None` for single-root proofs.
330    pub other_root: Option<Cid>,
331    /// Number of requested keys encoded directly in the top-level proof.
332    pub key_count: usize,
333    /// Number of encoded proof nodes carried by the bundle, including nested
334    /// range/lookahead proof nodes for diff-page proofs.
335    pub path_node_count: usize,
336    /// Inclusive start bound for range proofs.
337    pub start: Option<Vec<u8>>,
338    /// Exclusive upper bound for range and range-page proofs.
339    pub end: Option<Vec<u8>>,
340    /// Exclusive lower cursor bound for range-page and diff-page proofs.
341    pub after: Option<Vec<u8>>,
342    /// Original requested upper bound for diff-page proofs.
343    pub requested_end: Option<Vec<u8>>,
344    /// Original page limit for diff-page proofs.
345    pub limit: Option<usize>,
346    /// Whether a diff-page proof carries continuation lookahead key proofs.
347    pub has_lookahead: bool,
348}
349
350/// Store-independent verification result for opaque canonical proof bundle bytes.
351///
352/// This result is intentionally aggregate-level: it proves whether the decoded
353/// bundle verifies and reports counts that are useful for routing, logging, and
354/// sync protocols. Call the typed verifier after routing when callers need full
355/// values, range entries, or diff payloads.
356#[derive(Clone, Debug, PartialEq, Eq)]
357pub struct ProofBundleVerification {
358    /// Lightweight decoded bundle metadata.
359    pub summary: ProofBundleSummary,
360    /// Whether the decoded typed proof verifies.
361    pub valid: bool,
362    /// Number of verified existing keys for key and multi-key proofs.
363    pub exists_count: usize,
364    /// Number of verified absent keys for key and multi-key proofs.
365    pub absence_count: usize,
366    /// Number of verified entries for range and range-page proofs.
367    pub entry_count: usize,
368    /// Number of verified diffs for diff-page proofs.
369    pub diff_count: usize,
370    /// Continuation cursor proved by a diff-page proof, when present.
371    pub next_cursor: Option<RangeCursor>,
372}
373
374/// Verification result for serialized authenticated proof envelope bytes.
375///
376/// `valid` is true only when the envelope signature/time checks pass and the
377/// authenticated proof bundle verifies. When the envelope verifies but the proof
378/// bundle cannot be decoded, `proof` is `None` and `proof_error` describes the
379/// authenticated proof failure.
380#[derive(Clone, Debug, PartialEq, Eq)]
381pub struct AuthenticatedProofBundleVerification {
382    /// True when both the envelope and contained proof bundle verify.
383    pub valid: bool,
384    /// HMAC/time verification for the authenticated envelope.
385    pub envelope: AuthenticatedProofEnvelopeVerification,
386    /// Store-independent verification of the authenticated proof bundle.
387    pub proof: Option<ProofBundleVerification>,
388    /// Proof decode/verification error for an otherwise valid envelope.
389    pub proof_error: Option<String>,
390}
391
392/// Store-independent verification result for a [`RangeProof`].
393#[derive(Clone, Debug, PartialEq, Eq)]
394pub struct RangeProofVerification {
395    /// Whether the proof is internally consistent and complete for the range.
396    pub valid: bool,
397    /// Root CID from the proof.
398    pub root: Option<Cid>,
399    /// Inclusive range start.
400    pub start: Vec<u8>,
401    /// Exclusive range end. `None` means unbounded.
402    pub end: Option<Vec<u8>>,
403    /// Verified entries in lexicographic key order.
404    pub entries: Vec<(Vec<u8>, Vec<u8>)>,
405}
406
407/// Store-independent verification result for a [`RangePageProof`].
408#[derive(Clone, Debug, PartialEq, Eq)]
409pub struct RangePageProofVerification {
410    /// Whether the proof is internally consistent and complete for the page
411    /// window.
412    pub valid: bool,
413    /// Root CID from the proof.
414    pub root: Option<Cid>,
415    /// Exclusive cursor lower bound.
416    pub after: Option<Vec<u8>>,
417    /// Exclusive page upper bound. `None` means unbounded.
418    pub end: Option<Vec<u8>>,
419    /// Verified entries in lexicographic key order.
420    pub entries: Vec<(Vec<u8>, Vec<u8>)>,
421}
422
423/// Store-independent verification result for a [`DiffPageProof`].
424#[derive(Clone, Debug, PartialEq, Eq)]
425pub struct DiffPageProofVerification {
426    /// True when both range proofs are valid, bounds match, optional lookahead
427    /// proofs are valid, and the recomputed diff count matches the page limit.
428    pub valid: bool,
429    /// Whether the base range-page proof verified.
430    pub base_valid: bool,
431    /// Whether the other range-page proof verified.
432    pub other_valid: bool,
433    /// Whether the lookahead proofs were absent for a final page or valid for a
434    /// continued page.
435    pub lookahead_valid: bool,
436    /// Base tree root from the proof.
437    pub base_root: Option<Cid>,
438    /// Other tree root from the proof.
439    pub other_root: Option<Cid>,
440    /// Exclusive cursor lower bound.
441    pub after: Option<Vec<u8>>,
442    /// Original exclusive upper bound requested by the caller.
443    pub requested_end: Option<Vec<u8>>,
444    /// Exclusive upper bound covered by the range proofs. For continued pages
445    /// this is the first omitted diff key; for final pages it equals
446    /// `requested_end`.
447    pub proof_end: Option<Vec<u8>>,
448    /// Original page limit requested by the caller.
449    pub limit: usize,
450    /// Diffs recomputed from verified base/other entries.
451    pub diffs: Vec<Diff>,
452    /// Continuation cursor derived from the recomputed page and lookahead.
453    pub next_cursor: Option<RangeCursor>,
454}
455
456impl KeyProofVerification {
457    /// Whether the proof is valid and proves that the key exists.
458    pub fn exists(&self) -> bool {
459        self.valid && self.value.is_some()
460    }
461
462    /// Whether the proof is valid and proves that the key is absent.
463    pub fn is_absence(&self) -> bool {
464        self.valid && self.value.is_none()
465    }
466}
467
468impl MultiKeyProofVerification {
469    /// Whether all requested keys were verified.
470    pub fn all_valid(&self) -> bool {
471        self.valid && self.results.iter().all(|result| result.valid)
472    }
473}
474
475impl RangeProofVerification {
476    /// Whether the verified range contains no entries.
477    pub fn is_empty(&self) -> bool {
478        self.valid && self.entries.is_empty()
479    }
480}
481
482impl RangePageProofVerification {
483    /// Whether the verified page window contains no entries.
484    pub fn is_empty(&self) -> bool {
485        self.valid && self.entries.is_empty()
486    }
487}
488
489impl DiffPageProofVerification {
490    /// Whether the verified diff page contains no diffs.
491    pub fn is_empty(&self) -> bool {
492        self.valid && self.diffs.is_empty()
493    }
494}
495
496impl AuthenticatedProofEnvelope {
497    /// Serialize this envelope as deterministic, versioned binary bytes.
498    pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
499        authenticated_proof_envelope_to_bytes(self)
500    }
501
502    /// Decode an envelope from bytes produced by
503    /// [`AuthenticatedProofEnvelope::to_bytes`].
504    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
505        authenticated_proof_envelope_from_bytes(bytes)
506    }
507
508    /// Verify this envelope with the shared secret. Passing `None` for
509    /// `now_millis` skips issue/expiration checks.
510    pub fn verify(
511        &self,
512        secret: &[u8],
513        now_millis: Option<u64>,
514    ) -> AuthenticatedProofEnvelopeVerification {
515        verify_authenticated_proof_envelope(self, secret, now_millis)
516    }
517}
518
519impl ProofBundleSummary {
520    /// Stable lowercase identifier for language bindings and logs.
521    pub fn kind_name(&self) -> &'static str {
522        self.kind.as_str()
523    }
524}
525
526impl ProofBundleVerification {
527    /// Stable lowercase identifier for language bindings and logs.
528    pub fn kind_name(&self) -> &'static str {
529        self.summary.kind_name()
530    }
531}
532
533impl KeyProof {
534    /// Verify this proof without consulting a store.
535    pub fn verify(&self) -> KeyProofVerification {
536        verify_key_proof(self)
537    }
538
539    /// Return the nodes in this proof as deterministic encoded bytes.
540    pub fn path_node_bytes(&self) -> Vec<Vec<u8>> {
541        self.path.iter().map(Node::to_bytes).collect()
542    }
543
544    /// Rebuild a typed proof from encoded path nodes.
545    pub fn from_node_bytes(
546        root: Option<Cid>,
547        key: impl Into<Vec<u8>>,
548        path_node_bytes: Vec<Vec<u8>>,
549    ) -> Result<Self, Error> {
550        let path = path_node_bytes
551            .iter()
552            .map(|bytes| Node::from_bytes(bytes))
553            .collect::<Result<Vec<_>, _>>()?;
554        Ok(Self {
555            root,
556            key: key.into(),
557            path,
558        })
559    }
560
561    /// Serialize this proof as a versioned, deterministic binary bundle.
562    pub fn to_bundle_bytes(&self) -> Result<Vec<u8>, Error> {
563        proof_bundle_to_bytes(ProofBundleWire {
564            version: PROOF_BUNDLE_VERSION,
565            kind: PROOF_BUNDLE_KIND_KEY,
566            root: self.root.as_ref().map(|cid| cid.as_bytes().to_vec()),
567            keys: vec![self.key.clone()],
568            start: None,
569            end: None,
570            after: None,
571            path_node_bytes: self.path_node_bytes(),
572        })
573    }
574
575    /// Decode a proof from bytes produced by [`KeyProof::to_bundle_bytes`].
576    pub fn from_bundle_bytes(bytes: &[u8]) -> Result<Self, Error> {
577        let wire = proof_bundle_from_bytes(bytes)?;
578        if wire.kind != PROOF_BUNDLE_KIND_KEY {
579            return Err(proof_bundle_deserialize("proof bundle is not a key proof"));
580        }
581        if wire.keys.len() != 1 {
582            return Err(proof_bundle_deserialize(
583                "key proof bundle must contain exactly one key",
584            ));
585        }
586        Self::from_node_bytes(
587            cid_from_bundle_root(wire.root)?,
588            wire.keys.into_iter().next().unwrap(),
589            wire.path_node_bytes,
590        )
591    }
592}
593
594impl MultiKeyProof {
595    /// Verify this proof without consulting a store.
596    pub fn verify(&self) -> MultiKeyProofVerification {
597        verify_multi_key_proof(self)
598    }
599
600    /// Return the de-duplicated proof nodes as deterministic encoded bytes.
601    pub fn path_node_bytes(&self) -> Vec<Vec<u8>> {
602        self.path.iter().map(Node::to_bytes).collect()
603    }
604
605    /// Rebuild a typed proof from encoded path nodes.
606    pub fn from_node_bytes(
607        root: Option<Cid>,
608        keys: Vec<Vec<u8>>,
609        path_node_bytes: Vec<Vec<u8>>,
610    ) -> Result<Self, Error> {
611        let path = path_node_bytes
612            .iter()
613            .map(|bytes| Node::from_bytes(bytes))
614            .collect::<Result<Vec<_>, _>>()?;
615        Ok(Self { root, keys, path })
616    }
617
618    /// Serialize this proof as a versioned, deterministic binary bundle.
619    pub fn to_bundle_bytes(&self) -> Result<Vec<u8>, Error> {
620        proof_bundle_to_bytes(ProofBundleWire {
621            version: PROOF_BUNDLE_VERSION,
622            kind: PROOF_BUNDLE_KIND_MULTI_KEY,
623            root: self.root.as_ref().map(|cid| cid.as_bytes().to_vec()),
624            keys: self.keys.clone(),
625            start: None,
626            end: None,
627            after: None,
628            path_node_bytes: self.path_node_bytes(),
629        })
630    }
631
632    /// Decode a proof from bytes produced by [`MultiKeyProof::to_bundle_bytes`].
633    pub fn from_bundle_bytes(bytes: &[u8]) -> Result<Self, Error> {
634        let wire = proof_bundle_from_bytes(bytes)?;
635        if wire.kind != PROOF_BUNDLE_KIND_MULTI_KEY {
636            return Err(proof_bundle_deserialize(
637                "proof bundle is not a multi-key proof",
638            ));
639        }
640        Self::from_node_bytes(
641            cid_from_bundle_root(wire.root)?,
642            wire.keys,
643            wire.path_node_bytes,
644        )
645    }
646}
647
648impl RangeProof {
649    /// Verify this proof without consulting a store.
650    pub fn verify(&self) -> RangeProofVerification {
651        verify_range_proof(self)
652    }
653
654    /// Return the de-duplicated proof nodes as deterministic encoded bytes.
655    pub fn path_node_bytes(&self) -> Vec<Vec<u8>> {
656        self.path.iter().map(Node::to_bytes).collect()
657    }
658
659    /// Rebuild a typed proof from encoded path nodes.
660    pub fn from_node_bytes(
661        root: Option<Cid>,
662        start: impl Into<Vec<u8>>,
663        end: Option<Vec<u8>>,
664        path_node_bytes: Vec<Vec<u8>>,
665    ) -> Result<Self, Error> {
666        let path = path_node_bytes
667            .iter()
668            .map(|bytes| Node::from_bytes(bytes))
669            .collect::<Result<Vec<_>, _>>()?;
670        Ok(Self {
671            root,
672            start: start.into(),
673            end,
674            path,
675        })
676    }
677
678    /// Serialize this proof as a versioned, deterministic binary bundle.
679    pub fn to_bundle_bytes(&self) -> Result<Vec<u8>, Error> {
680        proof_bundle_to_bytes(ProofBundleWire {
681            version: PROOF_BUNDLE_VERSION,
682            kind: PROOF_BUNDLE_KIND_RANGE,
683            root: self.root.as_ref().map(|cid| cid.as_bytes().to_vec()),
684            keys: Vec::new(),
685            start: Some(self.start.clone()),
686            end: self.end.clone(),
687            after: None,
688            path_node_bytes: self.path_node_bytes(),
689        })
690    }
691
692    /// Decode a proof from bytes produced by [`RangeProof::to_bundle_bytes`].
693    pub fn from_bundle_bytes(bytes: &[u8]) -> Result<Self, Error> {
694        let wire = proof_bundle_from_bytes(bytes)?;
695        if wire.kind != PROOF_BUNDLE_KIND_RANGE {
696            return Err(proof_bundle_deserialize(
697                "proof bundle is not a range proof",
698            ));
699        }
700        let Some(start) = wire.start else {
701            return Err(proof_bundle_deserialize(
702                "range proof bundle must contain a start key",
703            ));
704        };
705        Self::from_node_bytes(
706            cid_from_bundle_root(wire.root)?,
707            start,
708            wire.end,
709            wire.path_node_bytes,
710        )
711    }
712}
713
714impl RangePageProof {
715    /// Verify this page proof without consulting a store.
716    pub fn verify(&self) -> RangePageProofVerification {
717        verify_range_page_proof(self)
718    }
719
720    /// Return the de-duplicated proof nodes as deterministic encoded bytes.
721    pub fn path_node_bytes(&self) -> Vec<Vec<u8>> {
722        self.path.iter().map(Node::to_bytes).collect()
723    }
724
725    /// Rebuild a typed proof from encoded path nodes.
726    pub fn from_node_bytes(
727        root: Option<Cid>,
728        after: Option<Vec<u8>>,
729        end: Option<Vec<u8>>,
730        path_node_bytes: Vec<Vec<u8>>,
731    ) -> Result<Self, Error> {
732        let path = path_node_bytes
733            .iter()
734            .map(|bytes| Node::from_bytes(bytes))
735            .collect::<Result<Vec<_>, _>>()?;
736        Ok(Self {
737            root,
738            after,
739            end,
740            path,
741        })
742    }
743
744    /// Serialize this page proof as a versioned, deterministic binary bundle.
745    pub fn to_bundle_bytes(&self) -> Result<Vec<u8>, Error> {
746        proof_bundle_to_bytes(ProofBundleWire {
747            version: PROOF_BUNDLE_VERSION,
748            kind: PROOF_BUNDLE_KIND_RANGE_PAGE,
749            root: self.root.as_ref().map(|cid| cid.as_bytes().to_vec()),
750            keys: Vec::new(),
751            start: None,
752            end: self.end.clone(),
753            after: self.after.clone(),
754            path_node_bytes: self.path_node_bytes(),
755        })
756    }
757
758    /// Decode a proof from bytes produced by [`RangePageProof::to_bundle_bytes`].
759    pub fn from_bundle_bytes(bytes: &[u8]) -> Result<Self, Error> {
760        let wire = proof_bundle_from_bytes(bytes)?;
761        if wire.kind != PROOF_BUNDLE_KIND_RANGE_PAGE {
762            return Err(proof_bundle_deserialize(
763                "proof bundle is not a range page proof",
764            ));
765        }
766        Self::from_node_bytes(
767            cid_from_bundle_root(wire.root)?,
768            wire.after,
769            wire.end,
770            wire.path_node_bytes,
771        )
772    }
773}
774
775impl DiffPageProof {
776    /// Verify this diff-page proof without consulting a store.
777    pub fn verify(&self) -> DiffPageProofVerification {
778        verify_diff_page_proof(self)
779    }
780
781    /// Serialize this diff-page proof as a versioned, deterministic binary
782    /// bundle.
783    pub fn to_bundle_bytes(&self) -> Result<Vec<u8>, Error> {
784        let limit = u64::try_from(self.limit)
785            .map_err(|_| Error::Serialize("diff page proof limit is too large".to_string()))?;
786        let base_range_page_proof = self.base.to_bundle_bytes()?;
787        let other_range_page_proof = self.other.to_bundle_bytes()?;
788        let lookahead_base_key_proof = self
789            .lookahead_base
790            .as_ref()
791            .map(KeyProof::to_bundle_bytes)
792            .transpose()?;
793        let lookahead_other_key_proof = self
794            .lookahead_other
795            .as_ref()
796            .map(KeyProof::to_bundle_bytes)
797            .transpose()?;
798
799        serde_cbor::ser::to_vec_packed(&DiffPageProofBundleWire {
800            version: PROOF_BUNDLE_VERSION,
801            kind: PROOF_BUNDLE_KIND_DIFF_PAGE,
802            requested_end: self.requested_end.clone(),
803            limit,
804            base_range_page_proof,
805            other_range_page_proof,
806            lookahead_base_key_proof,
807            lookahead_other_key_proof,
808        })
809        .map_err(|err| Error::Serialize(err.to_string()))
810    }
811
812    /// Decode a proof from bytes produced by [`DiffPageProof::to_bundle_bytes`].
813    pub fn from_bundle_bytes(bytes: &[u8]) -> Result<Self, Error> {
814        let wire = diff_page_proof_bundle_from_bytes(bytes)?;
815        let limit = usize::try_from(wire.limit)
816            .map_err(|_| proof_bundle_deserialize("diff page proof bundle limit is too large"))?;
817
818        Ok(Self {
819            base: RangePageProof::from_bundle_bytes(&wire.base_range_page_proof)?,
820            other: RangePageProof::from_bundle_bytes(&wire.other_range_page_proof)?,
821            lookahead_base: wire
822                .lookahead_base_key_proof
823                .map(|proof| KeyProof::from_bundle_bytes(&proof))
824                .transpose()?,
825            lookahead_other: wire
826                .lookahead_other_key_proof
827                .map(|proof| KeyProof::from_bundle_bytes(&proof))
828                .transpose()?,
829            requested_end: wire.requested_end,
830            limit,
831        })
832    }
833}
834
835impl<S: Store> Prolly<S> {
836    /// Build a root-to-leaf proof for `key`.
837    ///
838    /// The returned proof is self-contained and can be verified without access
839    /// to this store. A valid proof may prove either key presence or absence.
840    pub fn prove_key(&self, tree: &Tree, key: &[u8]) -> Result<KeyProof, Error> {
841        let mut path = Vec::new();
842
843        let Some(root_cid) = &tree.root else {
844            return Ok(KeyProof {
845                root: None,
846                key: key.to_vec(),
847                path,
848            });
849        };
850
851        let mut cid = root_cid.clone();
852        loop {
853            let node = self.load(&cid)?;
854            let is_leaf = node.leaf;
855            let child_index = path_child_index(&node, key);
856            path.push(node.clone());
857
858            if is_leaf {
859                break;
860            }
861
862            let Some(child_bytes) = node.vals.get(child_index) else {
863                return Err(Error::InvalidNode);
864            };
865            cid = cid_from_child_bytes(child_bytes).ok_or(Error::InvalidNode)?;
866        }
867
868        Ok(KeyProof {
869            root: Some(root_cid.clone()),
870            key: key.to_vec(),
871            path,
872        })
873    }
874
875    /// Build one shared proof for multiple keys.
876    ///
877    /// The returned proof de-duplicates shared path nodes while preserving the
878    /// input key order. A valid proof may prove a mix of key presence and
879    /// absence.
880    pub fn prove_keys<K: AsRef<[u8]>>(
881        &self,
882        tree: &Tree,
883        keys: &[K],
884    ) -> Result<MultiKeyProof, Error> {
885        let keys = keys
886            .iter()
887            .map(|key| key.as_ref().to_vec())
888            .collect::<Vec<_>>();
889        let mut path = Vec::new();
890
891        let Some(root_cid) = &tree.root else {
892            return Ok(MultiKeyProof {
893                root: None,
894                keys,
895                path,
896            });
897        };
898
899        if keys.is_empty() {
900            return Ok(MultiKeyProof {
901                root: Some(root_cid.clone()),
902                keys,
903                path,
904            });
905        }
906
907        let mut seen = HashSet::new();
908        for key in &keys {
909            let key_proof = self.prove_key(tree, key)?;
910            for node in key_proof.path {
911                let cid = node.cid();
912                if seen.insert(cid) {
913                    path.push(node);
914                }
915            }
916        }
917
918        Ok(MultiKeyProof {
919            root: Some(root_cid.clone()),
920            keys,
921            path,
922        })
923    }
924
925    /// Build a complete proof for every entry in `[start, end)`.
926    ///
927    /// The returned proof contains all overlapping child subtrees needed to
928    /// verify range completeness without access to this store.
929    pub fn prove_range(
930        &self,
931        tree: &Tree,
932        start: &[u8],
933        end: Option<&[u8]>,
934    ) -> Result<RangeProof, Error> {
935        let mut path = Vec::new();
936
937        let Some(root_cid) = &tree.root else {
938            return Ok(RangeProof {
939                root: None,
940                start: start.to_vec(),
941                end: end.map(<[u8]>::to_vec),
942                path,
943            });
944        };
945
946        if range_is_empty_by_bounds(start, end) {
947            return Ok(RangeProof {
948                root: Some(root_cid.clone()),
949                start: start.to_vec(),
950                end: end.map(<[u8]>::to_vec),
951                path,
952            });
953        }
954
955        let mut seen = HashSet::new();
956        self.collect_range_proof_nodes(root_cid, start, end, &mut seen, &mut path)?;
957
958        Ok(RangeProof {
959            root: Some(root_cid.clone()),
960            start: start.to_vec(),
961            end: end.map(<[u8]>::to_vec),
962            path,
963        })
964    }
965
966    /// Build a complete proof for every entry whose key starts with `prefix`.
967    ///
968    /// This is equivalent to calling [`Prolly::prove_range`] with bounds from
969    /// [`crate::prefix_range`], but makes prefix/namespace proofs explicit at
970    /// API boundaries.
971    pub fn prove_prefix(&self, tree: &Tree, prefix: &[u8]) -> Result<RangeProof, Error> {
972        let (start, end) = key::prefix_range(prefix);
973        self.prove_range(tree, &start, end.as_deref())
974    }
975
976    /// Read a bounded range page and build a proof for exactly that page window.
977    ///
978    /// The proof uses the cursor's exclusive `after` bound instead of converting
979    /// it into an inclusive key. This preserves raw byte-key semantics even when
980    /// the cursor key is a prefix of later keys.
981    pub fn prove_range_page(
982        &self,
983        tree: &Tree,
984        cursor: &RangeCursor,
985        end: Option<&[u8]>,
986        limit: usize,
987    ) -> Result<ProvedRangePage, Error> {
988        let after = cursor.after().map(<[u8]>::to_vec);
989
990        if limit == 0 {
991            let proof_end = after.clone().or_else(|| Some(Vec::new()));
992            return Ok(ProvedRangePage {
993                page: RangePage {
994                    entries: Vec::new(),
995                    next_cursor: Some(cursor.clone()),
996                },
997                proof: RangePageProof {
998                    root: tree.root.clone(),
999                    after,
1000                    end: proof_end,
1001                    path: Vec::new(),
1002                },
1003            });
1004        }
1005
1006        let mut iter = self.range_from_cursor(tree, cursor, end)?;
1007        let mut entries = Vec::with_capacity(limit);
1008
1009        for _ in 0..limit {
1010            let Some(item) = iter.next() else {
1011                let proof = self.prove_range_page_window(tree, after.as_deref(), end)?;
1012                return Ok(ProvedRangePage {
1013                    page: RangePage {
1014                        entries,
1015                        next_cursor: None,
1016                    },
1017                    proof,
1018                });
1019            };
1020            entries.push(item?);
1021        }
1022
1023        let lookahead = iter.next().transpose()?;
1024        let proof_end = lookahead
1025            .as_ref()
1026            .map(|(key, _)| key.clone())
1027            .or_else(|| end.map(<[u8]>::to_vec));
1028        let proof = self.prove_range_page_window(tree, after.as_deref(), proof_end.as_deref())?;
1029        let next_cursor = lookahead.as_ref().and_then(|_| {
1030            entries
1031                .last()
1032                .map(|(key, _)| RangeCursor::after_key(key.clone()))
1033        });
1034
1035        Ok(ProvedRangePage {
1036            page: RangePage {
1037                entries,
1038                next_cursor,
1039            },
1040            proof,
1041        })
1042    }
1043
1044    /// Read a bounded diff page and build a proof for exactly that page.
1045    ///
1046    /// Verification recomputes the page from two range-page proofs and, when
1047    /// the result has a continuation cursor, two key proofs for the first
1048    /// omitted diff key.
1049    pub fn prove_diff_page(
1050        &self,
1051        base: &Tree,
1052        other: &Tree,
1053        cursor: &RangeCursor,
1054        end: Option<&[u8]>,
1055        limit: usize,
1056    ) -> Result<ProvedDiffPage, Error> {
1057        let after = cursor.after().map(<[u8]>::to_vec);
1058
1059        if limit == 0 {
1060            let proof_end = after.clone().or_else(|| Some(Vec::new()));
1061            return Ok(ProvedDiffPage {
1062                page: DiffPage {
1063                    diffs: Vec::new(),
1064                    next_cursor: Some(cursor.clone()),
1065                },
1066                proof: DiffPageProof {
1067                    base: RangePageProof {
1068                        root: base.root.clone(),
1069                        after: after.clone(),
1070                        end: proof_end.clone(),
1071                        path: Vec::new(),
1072                    },
1073                    other: RangePageProof {
1074                        root: other.root.clone(),
1075                        after,
1076                        end: proof_end,
1077                        path: Vec::new(),
1078                    },
1079                    lookahead_base: None,
1080                    lookahead_other: None,
1081                    requested_end: end.map(<[u8]>::to_vec),
1082                    limit,
1083                },
1084            });
1085        }
1086
1087        let mut all_diffs = self.diff_from_cursor(base, other, cursor, end)?;
1088        let has_more = all_diffs.len() > limit;
1089        let lookahead_key = has_more.then(|| all_diffs[limit].key().to_vec());
1090        if has_more {
1091            all_diffs.truncate(limit);
1092        }
1093
1094        let next_cursor = if has_more {
1095            all_diffs
1096                .last()
1097                .map(|diff| RangeCursor::after_key(diff.key().to_vec()))
1098        } else {
1099            None
1100        };
1101        let proof_end = lookahead_key.clone().or_else(|| end.map(<[u8]>::to_vec));
1102        let lookahead_base = lookahead_key
1103            .as_ref()
1104            .map(|key| self.prove_key(base, key))
1105            .transpose()?;
1106        let lookahead_other = lookahead_key
1107            .as_ref()
1108            .map(|key| self.prove_key(other, key))
1109            .transpose()?;
1110
1111        Ok(ProvedDiffPage {
1112            page: DiffPage {
1113                diffs: all_diffs,
1114                next_cursor,
1115            },
1116            proof: DiffPageProof {
1117                base: self.prove_range_page_window(base, after.as_deref(), proof_end.as_deref())?,
1118                other: self.prove_range_page_window(
1119                    other,
1120                    after.as_deref(),
1121                    proof_end.as_deref(),
1122                )?,
1123                lookahead_base,
1124                lookahead_other,
1125                requested_end: end.map(<[u8]>::to_vec),
1126                limit,
1127            },
1128        })
1129    }
1130
1131    fn prove_range_page_window(
1132        &self,
1133        tree: &Tree,
1134        after: Option<&[u8]>,
1135        end: Option<&[u8]>,
1136    ) -> Result<RangePageProof, Error> {
1137        let Some(root_cid) = &tree.root else {
1138            return Ok(RangePageProof {
1139                root: None,
1140                after: after.map(<[u8]>::to_vec),
1141                end: end.map(<[u8]>::to_vec),
1142                path: Vec::new(),
1143            });
1144        };
1145
1146        if page_range_is_empty_by_bounds(after, end) {
1147            return Ok(RangePageProof {
1148                root: Some(root_cid.clone()),
1149                after: after.map(<[u8]>::to_vec),
1150                end: end.map(<[u8]>::to_vec),
1151                path: Vec::new(),
1152            });
1153        }
1154
1155        let mut seen = HashSet::new();
1156        let mut path = Vec::new();
1157        self.collect_range_page_proof_nodes(root_cid, after, end, &mut seen, &mut path)?;
1158
1159        Ok(RangePageProof {
1160            root: Some(root_cid.clone()),
1161            after: after.map(<[u8]>::to_vec),
1162            end: end.map(<[u8]>::to_vec),
1163            path,
1164        })
1165    }
1166
1167    fn collect_range_proof_nodes(
1168        &self,
1169        cid: &Cid,
1170        start: &[u8],
1171        end: Option<&[u8]>,
1172        seen: &mut HashSet<Cid>,
1173        path: &mut Vec<Node>,
1174    ) -> Result<(), Error> {
1175        let node = self.load(cid)?;
1176        if !seen.insert(cid.clone()) {
1177            return Ok(());
1178        }
1179        path.push(node.clone());
1180
1181        if node.leaf {
1182            return Ok(());
1183        }
1184
1185        for idx in overlapping_child_index_range(&node, start, end) {
1186            let child_start = node.keys[idx].as_slice();
1187            let child_end = child_span_end(&node, idx, None);
1188            if !span_overlaps_range(child_start, child_end, start, end) {
1189                if range_ends_before_or_at(end, child_start) {
1190                    break;
1191                }
1192                continue;
1193            }
1194            let child_cid = cid_from_child_bytes(node.vals.get(idx).ok_or(Error::InvalidNode)?)
1195                .ok_or(Error::InvalidNode)?;
1196            self.collect_range_proof_nodes(&child_cid, start, end, seen, path)?;
1197        }
1198
1199        Ok(())
1200    }
1201
1202    fn collect_range_page_proof_nodes(
1203        &self,
1204        cid: &Cid,
1205        after: Option<&[u8]>,
1206        end: Option<&[u8]>,
1207        seen: &mut HashSet<Cid>,
1208        path: &mut Vec<Node>,
1209    ) -> Result<(), Error> {
1210        let node = self.load(cid)?;
1211        if !seen.insert(cid.clone()) {
1212            return Ok(());
1213        }
1214        path.push(node.clone());
1215
1216        if node.leaf {
1217            return Ok(());
1218        }
1219
1220        let traversal_start = after.unwrap_or(&[]);
1221        for idx in overlapping_child_index_range(&node, traversal_start, end) {
1222            let child_start = node.keys[idx].as_slice();
1223            let child_end = child_span_end(&node, idx, None);
1224            if !span_overlaps_page_range(child_start, child_end, after, end) {
1225                if range_ends_before_or_at(end, child_start) {
1226                    break;
1227                }
1228                continue;
1229            }
1230            let child_cid = cid_from_child_bytes(node.vals.get(idx).ok_or(Error::InvalidNode)?)
1231                .ok_or(Error::InvalidNode)?;
1232            self.collect_range_page_proof_nodes(&child_cid, after, end, seen, path)?;
1233        }
1234
1235        Ok(())
1236    }
1237}
1238
1239#[cfg(feature = "async-store")]
1240impl<S> AsyncProlly<S>
1241where
1242    S: AsyncStore,
1243    S::Error: Send + Sync,
1244{
1245    /// Build a root-to-leaf proof for `key`.
1246    ///
1247    /// The returned proof is self-contained and can be verified without access
1248    /// to this store. A valid proof may prove either key presence or absence.
1249    pub async fn prove_key(&self, tree: &Tree, key: &[u8]) -> Result<KeyProof, Error> {
1250        let mut path = Vec::new();
1251
1252        let Some(root_cid) = &tree.root else {
1253            return Ok(KeyProof {
1254                root: None,
1255                key: key.to_vec(),
1256                path,
1257            });
1258        };
1259
1260        let mut cid = root_cid.clone();
1261        loop {
1262            let node = self.load_arc(&cid).await?;
1263            let is_leaf = node.leaf;
1264            let child_index = path_child_index(&node, key);
1265            path.push((*node).clone());
1266
1267            if is_leaf {
1268                break;
1269            }
1270
1271            let Some(child_bytes) = node.vals.get(child_index) else {
1272                return Err(Error::InvalidNode);
1273            };
1274            cid = cid_from_child_bytes(child_bytes).ok_or(Error::InvalidNode)?;
1275        }
1276
1277        Ok(KeyProof {
1278            root: Some(root_cid.clone()),
1279            key: key.to_vec(),
1280            path,
1281        })
1282    }
1283
1284    /// Build one shared proof for multiple keys.
1285    ///
1286    /// The returned proof de-duplicates shared path nodes while preserving the
1287    /// input key order. A valid proof may prove a mix of key presence and
1288    /// absence.
1289    pub async fn prove_keys<K: AsRef<[u8]>>(
1290        &self,
1291        tree: &Tree,
1292        keys: &[K],
1293    ) -> Result<MultiKeyProof, Error> {
1294        let keys = keys
1295            .iter()
1296            .map(|key| key.as_ref().to_vec())
1297            .collect::<Vec<_>>();
1298        let mut path = Vec::new();
1299
1300        let Some(root_cid) = &tree.root else {
1301            return Ok(MultiKeyProof {
1302                root: None,
1303                keys,
1304                path,
1305            });
1306        };
1307
1308        if keys.is_empty() {
1309            return Ok(MultiKeyProof {
1310                root: Some(root_cid.clone()),
1311                keys,
1312                path,
1313            });
1314        }
1315
1316        let mut seen = HashSet::new();
1317        for key in &keys {
1318            let key_proof = self.prove_key(tree, key).await?;
1319            for node in key_proof.path {
1320                let cid = node.cid();
1321                if seen.insert(cid) {
1322                    path.push(node);
1323                }
1324            }
1325        }
1326
1327        Ok(MultiKeyProof {
1328            root: Some(root_cid.clone()),
1329            keys,
1330            path,
1331        })
1332    }
1333
1334    /// Build a complete proof for every entry in `[start, end)`.
1335    ///
1336    /// The returned proof contains all overlapping child subtrees needed to
1337    /// verify range completeness without access to this store.
1338    pub async fn prove_range(
1339        &self,
1340        tree: &Tree,
1341        start: &[u8],
1342        end: Option<&[u8]>,
1343    ) -> Result<RangeProof, Error> {
1344        let mut path = Vec::new();
1345
1346        let Some(root_cid) = &tree.root else {
1347            return Ok(RangeProof {
1348                root: None,
1349                start: start.to_vec(),
1350                end: end.map(<[u8]>::to_vec),
1351                path,
1352            });
1353        };
1354
1355        if range_is_empty_by_bounds(start, end) {
1356            return Ok(RangeProof {
1357                root: Some(root_cid.clone()),
1358                start: start.to_vec(),
1359                end: end.map(<[u8]>::to_vec),
1360                path,
1361            });
1362        }
1363
1364        let mut seen = HashSet::new();
1365        self.collect_range_proof_nodes(root_cid, start, end, &mut seen, &mut path)
1366            .await?;
1367
1368        Ok(RangeProof {
1369            root: Some(root_cid.clone()),
1370            start: start.to_vec(),
1371            end: end.map(<[u8]>::to_vec),
1372            path,
1373        })
1374    }
1375
1376    /// Build a complete proof for every entry whose key starts with `prefix`.
1377    pub async fn prove_prefix(&self, tree: &Tree, prefix: &[u8]) -> Result<RangeProof, Error> {
1378        let (start, end) = key::prefix_range(prefix);
1379        self.prove_range(tree, &start, end.as_deref()).await
1380    }
1381
1382    /// Read a bounded range page and build a proof for exactly that page window.
1383    pub async fn prove_range_page(
1384        &self,
1385        tree: &Tree,
1386        cursor: &RangeCursor,
1387        end: Option<&[u8]>,
1388        limit: usize,
1389    ) -> Result<ProvedRangePage, Error> {
1390        let after = cursor.after().map(<[u8]>::to_vec);
1391
1392        if limit == 0 {
1393            let proof_end = after.clone().or_else(|| Some(Vec::new()));
1394            return Ok(ProvedRangePage {
1395                page: RangePage {
1396                    entries: Vec::new(),
1397                    next_cursor: Some(cursor.clone()),
1398                },
1399                proof: RangePageProof {
1400                    root: tree.root.clone(),
1401                    after,
1402                    end: proof_end,
1403                    path: Vec::new(),
1404                },
1405            });
1406        }
1407
1408        let mut iter = self.range_from_cursor(tree, cursor, end).await?;
1409        let mut entries = Vec::with_capacity(limit);
1410
1411        for _ in 0..limit {
1412            let Some(item) = iter.next().await else {
1413                let proof = self
1414                    .prove_range_page_window(tree, after.as_deref(), end)
1415                    .await?;
1416                return Ok(ProvedRangePage {
1417                    page: RangePage {
1418                        entries,
1419                        next_cursor: None,
1420                    },
1421                    proof,
1422                });
1423            };
1424            entries.push(item?);
1425        }
1426
1427        let lookahead = iter.next().await.transpose()?;
1428        let proof_end = lookahead
1429            .as_ref()
1430            .map(|(key, _)| key.clone())
1431            .or_else(|| end.map(<[u8]>::to_vec));
1432        let proof = self
1433            .prove_range_page_window(tree, after.as_deref(), proof_end.as_deref())
1434            .await?;
1435        let next_cursor = lookahead.as_ref().and_then(|_| {
1436            entries
1437                .last()
1438                .map(|(key, _)| RangeCursor::after_key(key.clone()))
1439        });
1440
1441        Ok(ProvedRangePage {
1442            page: RangePage {
1443                entries,
1444                next_cursor,
1445            },
1446            proof,
1447        })
1448    }
1449
1450    /// Read a bounded diff page through the async store and build a proof for
1451    /// exactly that page.
1452    pub async fn prove_diff_page(
1453        &self,
1454        base: &Tree,
1455        other: &Tree,
1456        cursor: &RangeCursor,
1457        end: Option<&[u8]>,
1458        limit: usize,
1459    ) -> Result<ProvedDiffPage, Error> {
1460        let after = cursor.after().map(<[u8]>::to_vec);
1461
1462        if limit == 0 {
1463            let proof_end = after.clone().or_else(|| Some(Vec::new()));
1464            return Ok(ProvedDiffPage {
1465                page: DiffPage {
1466                    diffs: Vec::new(),
1467                    next_cursor: Some(cursor.clone()),
1468                },
1469                proof: DiffPageProof {
1470                    base: RangePageProof {
1471                        root: base.root.clone(),
1472                        after: after.clone(),
1473                        end: proof_end.clone(),
1474                        path: Vec::new(),
1475                    },
1476                    other: RangePageProof {
1477                        root: other.root.clone(),
1478                        after,
1479                        end: proof_end,
1480                        path: Vec::new(),
1481                    },
1482                    lookahead_base: None,
1483                    lookahead_other: None,
1484                    requested_end: end.map(<[u8]>::to_vec),
1485                    limit,
1486                },
1487            });
1488        }
1489
1490        let mut all_diffs = self.diff_from_cursor(base, other, cursor, end).await?;
1491        let has_more = all_diffs.len() > limit;
1492        let lookahead_key = has_more.then(|| all_diffs[limit].key().to_vec());
1493        if has_more {
1494            all_diffs.truncate(limit);
1495        }
1496
1497        let next_cursor = if has_more {
1498            all_diffs
1499                .last()
1500                .map(|diff| RangeCursor::after_key(diff.key().to_vec()))
1501        } else {
1502            None
1503        };
1504        let proof_end = lookahead_key.clone().or_else(|| end.map(<[u8]>::to_vec));
1505        let lookahead_base = match &lookahead_key {
1506            Some(key) => Some(self.prove_key(base, key).await?),
1507            None => None,
1508        };
1509        let lookahead_other = match &lookahead_key {
1510            Some(key) => Some(self.prove_key(other, key).await?),
1511            None => None,
1512        };
1513
1514        Ok(ProvedDiffPage {
1515            page: DiffPage {
1516                diffs: all_diffs,
1517                next_cursor,
1518            },
1519            proof: DiffPageProof {
1520                base: self
1521                    .prove_range_page_window(base, after.as_deref(), proof_end.as_deref())
1522                    .await?,
1523                other: self
1524                    .prove_range_page_window(other, after.as_deref(), proof_end.as_deref())
1525                    .await?,
1526                lookahead_base,
1527                lookahead_other,
1528                requested_end: end.map(<[u8]>::to_vec),
1529                limit,
1530            },
1531        })
1532    }
1533
1534    async fn prove_range_page_window(
1535        &self,
1536        tree: &Tree,
1537        after: Option<&[u8]>,
1538        end: Option<&[u8]>,
1539    ) -> Result<RangePageProof, Error> {
1540        let Some(root_cid) = &tree.root else {
1541            return Ok(RangePageProof {
1542                root: None,
1543                after: after.map(<[u8]>::to_vec),
1544                end: end.map(<[u8]>::to_vec),
1545                path: Vec::new(),
1546            });
1547        };
1548
1549        if page_range_is_empty_by_bounds(after, end) {
1550            return Ok(RangePageProof {
1551                root: Some(root_cid.clone()),
1552                after: after.map(<[u8]>::to_vec),
1553                end: end.map(<[u8]>::to_vec),
1554                path: Vec::new(),
1555            });
1556        }
1557
1558        let mut seen = HashSet::new();
1559        let mut path = Vec::new();
1560        self.collect_range_page_proof_nodes(root_cid, after, end, &mut seen, &mut path)
1561            .await?;
1562
1563        Ok(RangePageProof {
1564            root: Some(root_cid.clone()),
1565            after: after.map(<[u8]>::to_vec),
1566            end: end.map(<[u8]>::to_vec),
1567            path,
1568        })
1569    }
1570
1571    async fn collect_range_proof_nodes(
1572        &self,
1573        cid: &Cid,
1574        start: &[u8],
1575        end: Option<&[u8]>,
1576        seen: &mut HashSet<Cid>,
1577        path: &mut Vec<Node>,
1578    ) -> Result<(), Error> {
1579        let mut stack = vec![cid.clone()];
1580        while let Some(cid) = stack.pop() {
1581            if !seen.insert(cid.clone()) {
1582                continue;
1583            }
1584            let node = self.load_arc(&cid).await?;
1585            path.push((*node).clone());
1586
1587            if node.leaf {
1588                continue;
1589            }
1590
1591            let mut child_cids = Vec::new();
1592            for idx in overlapping_child_index_range(&node, start, end) {
1593                let child_start = node.keys[idx].as_slice();
1594                let child_end = child_span_end(&node, idx, None);
1595                if !span_overlaps_range(child_start, child_end, start, end) {
1596                    if range_ends_before_or_at(end, child_start) {
1597                        break;
1598                    }
1599                    continue;
1600                }
1601                child_cids.push(
1602                    cid_from_child_bytes(node.vals.get(idx).ok_or(Error::InvalidNode)?)
1603                        .ok_or(Error::InvalidNode)?,
1604                );
1605            }
1606            child_cids.reverse();
1607            stack.extend(child_cids);
1608        }
1609
1610        Ok(())
1611    }
1612
1613    async fn collect_range_page_proof_nodes(
1614        &self,
1615        cid: &Cid,
1616        after: Option<&[u8]>,
1617        end: Option<&[u8]>,
1618        seen: &mut HashSet<Cid>,
1619        path: &mut Vec<Node>,
1620    ) -> Result<(), Error> {
1621        let mut stack = vec![cid.clone()];
1622        while let Some(cid) = stack.pop() {
1623            if !seen.insert(cid.clone()) {
1624                continue;
1625            }
1626            let node = self.load_arc(&cid).await?;
1627            path.push((*node).clone());
1628
1629            if node.leaf {
1630                continue;
1631            }
1632
1633            let traversal_start = after.unwrap_or(&[]);
1634            let mut child_cids = Vec::new();
1635            for idx in overlapping_child_index_range(&node, traversal_start, end) {
1636                let child_start = node.keys[idx].as_slice();
1637                let child_end = child_span_end(&node, idx, None);
1638                if !span_overlaps_page_range(child_start, child_end, after, end) {
1639                    if range_ends_before_or_at(end, child_start) {
1640                        break;
1641                    }
1642                    continue;
1643                }
1644                child_cids.push(
1645                    cid_from_child_bytes(node.vals.get(idx).ok_or(Error::InvalidNode)?)
1646                        .ok_or(Error::InvalidNode)?,
1647                );
1648            }
1649            child_cids.reverse();
1650            stack.extend(child_cids);
1651        }
1652
1653        Ok(())
1654    }
1655}
1656
1657/// Verify a key proof without consulting a store.
1658pub fn verify_key_proof(proof: &KeyProof) -> KeyProofVerification {
1659    let valid = proof_is_consistent(proof);
1660    let value = if valid {
1661        verified_leaf_value(proof.path.last(), &proof.key)
1662    } else {
1663        None
1664    };
1665
1666    KeyProofVerification {
1667        valid,
1668        root: proof.root.clone(),
1669        key: proof.key.clone(),
1670        value,
1671    }
1672}
1673
1674/// Verify a shared multi-key proof without consulting a store.
1675pub fn verify_multi_key_proof(proof: &MultiKeyProof) -> MultiKeyProofVerification {
1676    let mut results = proof
1677        .keys
1678        .iter()
1679        .map(|key| KeyProofVerification {
1680            valid: false,
1681            root: proof.root.clone(),
1682            key: key.clone(),
1683            value: None,
1684        })
1685        .collect::<Vec<_>>();
1686
1687    let node_map = match proof_node_map(proof) {
1688        Some(node_map) => node_map,
1689        None => {
1690            return MultiKeyProofVerification {
1691                valid: false,
1692                root: proof.root.clone(),
1693                results,
1694            };
1695        }
1696    };
1697
1698    match &proof.root {
1699        None => {
1700            let valid = proof.path.is_empty();
1701            for result in &mut results {
1702                result.valid = valid;
1703            }
1704            MultiKeyProofVerification {
1705                valid,
1706                root: proof.root.clone(),
1707                results,
1708            }
1709        }
1710        Some(root) if proof.keys.is_empty() => {
1711            let valid = proof.path.is_empty() || node_map.contains_key(root);
1712            MultiKeyProofVerification {
1713                valid,
1714                root: proof.root.clone(),
1715                results,
1716            }
1717        }
1718        Some(root) => {
1719            let mut all_valid = true;
1720            for result in &mut results {
1721                match verified_value_from_node_set(root, &result.key, &node_map) {
1722                    Some(value) => {
1723                        result.valid = true;
1724                        result.value = value;
1725                    }
1726                    None => {
1727                        all_valid = false;
1728                    }
1729                }
1730            }
1731            MultiKeyProofVerification {
1732                valid: all_valid,
1733                root: proof.root.clone(),
1734                results,
1735            }
1736        }
1737    }
1738}
1739
1740/// Verify a range proof without consulting a store.
1741pub fn verify_range_proof(proof: &RangeProof) -> RangeProofVerification {
1742    let mut entries = Vec::new();
1743
1744    if range_is_empty_by_bounds(&proof.start, proof.end.as_deref()) {
1745        return RangeProofVerification {
1746            valid: proof.path.is_empty(),
1747            root: proof.root.clone(),
1748            start: proof.start.clone(),
1749            end: proof.end.clone(),
1750            entries,
1751        };
1752    }
1753
1754    let node_map = match range_proof_node_map(proof) {
1755        Some(node_map) => node_map,
1756        None => {
1757            return RangeProofVerification {
1758                valid: false,
1759                root: proof.root.clone(),
1760                start: proof.start.clone(),
1761                end: proof.end.clone(),
1762                entries,
1763            };
1764        }
1765    };
1766
1767    let valid = match &proof.root {
1768        None => proof.path.is_empty(),
1769        Some(root) => {
1770            let mut stack = HashSet::new();
1771            verify_range_node(
1772                root,
1773                &proof.start,
1774                proof.end.as_deref(),
1775                &node_map,
1776                &mut stack,
1777                &mut entries,
1778            )
1779        }
1780    };
1781
1782    if !valid {
1783        entries.clear();
1784    }
1785
1786    RangeProofVerification {
1787        valid,
1788        root: proof.root.clone(),
1789        start: proof.start.clone(),
1790        end: proof.end.clone(),
1791        entries,
1792    }
1793}
1794
1795/// Verify a resumable range-page proof without consulting a store.
1796pub fn verify_range_page_proof(proof: &RangePageProof) -> RangePageProofVerification {
1797    let mut entries = Vec::new();
1798
1799    if page_range_is_empty_by_bounds(proof.after.as_deref(), proof.end.as_deref()) {
1800        return RangePageProofVerification {
1801            valid: proof.path.is_empty(),
1802            root: proof.root.clone(),
1803            after: proof.after.clone(),
1804            end: proof.end.clone(),
1805            entries,
1806        };
1807    }
1808
1809    let node_map = match range_page_proof_node_map(proof) {
1810        Some(node_map) => node_map,
1811        None => {
1812            return RangePageProofVerification {
1813                valid: false,
1814                root: proof.root.clone(),
1815                after: proof.after.clone(),
1816                end: proof.end.clone(),
1817                entries,
1818            };
1819        }
1820    };
1821
1822    let valid = match &proof.root {
1823        None => proof.path.is_empty(),
1824        Some(root) => {
1825            let mut stack = HashSet::new();
1826            verify_range_page_node(
1827                root,
1828                proof.after.as_deref(),
1829                proof.end.as_deref(),
1830                &node_map,
1831                &mut stack,
1832                &mut entries,
1833            )
1834        }
1835    };
1836
1837    if !valid {
1838        entries.clear();
1839    }
1840
1841    RangePageProofVerification {
1842        valid,
1843        root: proof.root.clone(),
1844        after: proof.after.clone(),
1845        end: proof.end.clone(),
1846        entries,
1847    }
1848}
1849
1850/// Verify a diff-page proof without consulting a store.
1851pub fn verify_diff_page_proof(proof: &DiffPageProof) -> DiffPageProofVerification {
1852    let base = verify_range_page_proof(&proof.base);
1853    let other = verify_range_page_proof(&proof.other);
1854    let same_bounds = proof.base.after == proof.other.after && proof.base.end == proof.other.end;
1855    let after = proof.base.after.clone();
1856    let proof_end = proof.base.end.clone();
1857    let mut diffs = Vec::new();
1858
1859    let mut lookahead_valid = false;
1860    let mut next_cursor = None;
1861    let mut valid = base.valid && other.valid && same_bounds;
1862
1863    if valid {
1864        diffs = diff_verified_entries(&base.entries, &other.entries);
1865        match (&proof.lookahead_base, &proof.lookahead_other) {
1866            (Some(base_lookahead), Some(other_lookahead)) => {
1867                let base_lookahead = verify_key_proof(base_lookahead);
1868                let other_lookahead = verify_key_proof(other_lookahead);
1869                lookahead_valid = verify_diff_page_lookahead(
1870                    &base_lookahead,
1871                    &other_lookahead,
1872                    after.as_deref(),
1873                    proof.requested_end.as_deref(),
1874                    proof_end.as_deref(),
1875                );
1876                valid = valid && lookahead_valid && proof.limit > 0 && diffs.len() == proof.limit;
1877                if valid {
1878                    next_cursor = diffs
1879                        .last()
1880                        .map(|diff| RangeCursor::after_key(diff.key().to_vec()));
1881                }
1882            }
1883            (None, None) => {
1884                lookahead_valid = true;
1885                if proof.limit == 0 {
1886                    valid = valid && diffs.is_empty();
1887                    if valid {
1888                        next_cursor = Some(
1889                            after
1890                                .clone()
1891                                .map(RangeCursor::after_key)
1892                                .unwrap_or_else(RangeCursor::start),
1893                        );
1894                    }
1895                } else {
1896                    valid = valid && proof_end == proof.requested_end && diffs.len() <= proof.limit;
1897                }
1898            }
1899            _ => {
1900                valid = false;
1901            }
1902        }
1903    }
1904
1905    if !valid {
1906        diffs.clear();
1907        next_cursor = None;
1908    }
1909
1910    DiffPageProofVerification {
1911        valid,
1912        base_valid: base.valid,
1913        other_valid: other.valid,
1914        lookahead_valid,
1915        base_root: base.root,
1916        other_root: other.root,
1917        after,
1918        requested_end: proof.requested_end.clone(),
1919        proof_end,
1920        limit: proof.limit,
1921        diffs,
1922        next_cursor,
1923    }
1924}
1925
1926/// Decode lightweight metadata from canonical proof bundle bytes.
1927///
1928/// This function lets callers route an opaque bundle to the right typed decoder
1929/// without consulting a store. It validates version/kind framing and root CID
1930/// lengths, but it does not prove membership, absence, range completeness, or
1931/// diff-page continuation correctness.
1932pub fn inspect_proof_bundle(bytes: &[u8]) -> Result<ProofBundleSummary, Error> {
1933    match proof_bundle_from_bytes(bytes) {
1934        Ok(wire) => proof_bundle_summary_from_wire(wire),
1935        Err(primary_error) => match diff_page_proof_bundle_from_bytes(bytes) {
1936            Ok(wire) => diff_page_proof_bundle_summary_from_wire(wire),
1937            Err(_) => Err(primary_error),
1938        },
1939    }
1940}
1941
1942/// Decode and verify opaque canonical proof bundle bytes.
1943///
1944/// This is the verifying counterpart to [`inspect_proof_bundle`]. It chooses
1945/// the typed proof decoder from the bundle kind, runs the matching verifier,
1946/// and returns an aggregate record that is convenient across FFI boundaries.
1947pub fn verify_proof_bundle(bytes: &[u8]) -> Result<ProofBundleVerification, Error> {
1948    let summary = inspect_proof_bundle(bytes)?;
1949    match summary.kind {
1950        ProofBundleKind::Key => {
1951            let verified = KeyProof::from_bundle_bytes(bytes)?.verify();
1952            Ok(ProofBundleVerification {
1953                summary,
1954                valid: verified.valid,
1955                exists_count: usize::from(verified.exists()),
1956                absence_count: usize::from(verified.is_absence()),
1957                entry_count: 0,
1958                diff_count: 0,
1959                next_cursor: None,
1960            })
1961        }
1962        ProofBundleKind::MultiKey => {
1963            let verified = MultiKeyProof::from_bundle_bytes(bytes)?.verify();
1964            let (exists_count, absence_count) = if verified.valid {
1965                (
1966                    verified
1967                        .results
1968                        .iter()
1969                        .filter(|result| result.exists())
1970                        .count(),
1971                    verified
1972                        .results
1973                        .iter()
1974                        .filter(|result| result.is_absence())
1975                        .count(),
1976                )
1977            } else {
1978                (0, 0)
1979            };
1980            Ok(ProofBundleVerification {
1981                summary,
1982                valid: verified.valid,
1983                exists_count,
1984                absence_count,
1985                entry_count: 0,
1986                diff_count: 0,
1987                next_cursor: None,
1988            })
1989        }
1990        ProofBundleKind::Range => {
1991            let verified = RangeProof::from_bundle_bytes(bytes)?.verify();
1992            Ok(ProofBundleVerification {
1993                summary,
1994                valid: verified.valid,
1995                exists_count: 0,
1996                absence_count: 0,
1997                entry_count: if verified.valid {
1998                    verified.entries.len()
1999                } else {
2000                    0
2001                },
2002                diff_count: 0,
2003                next_cursor: None,
2004            })
2005        }
2006        ProofBundleKind::RangePage => {
2007            let verified = RangePageProof::from_bundle_bytes(bytes)?.verify();
2008            Ok(ProofBundleVerification {
2009                summary,
2010                valid: verified.valid,
2011                exists_count: 0,
2012                absence_count: 0,
2013                entry_count: if verified.valid {
2014                    verified.entries.len()
2015                } else {
2016                    0
2017                },
2018                diff_count: 0,
2019                next_cursor: None,
2020            })
2021        }
2022        ProofBundleKind::DiffPage => {
2023            let verified = DiffPageProof::from_bundle_bytes(bytes)?.verify();
2024            Ok(ProofBundleVerification {
2025                summary,
2026                valid: verified.valid,
2027                exists_count: 0,
2028                absence_count: 0,
2029                entry_count: 0,
2030                diff_count: if verified.valid {
2031                    verified.diffs.len()
2032                } else {
2033                    0
2034                },
2035                next_cursor: verified.next_cursor,
2036            })
2037        }
2038    }
2039}
2040
2041/// Build an HMAC-SHA256 authenticated envelope for canonical proof bundle bytes.
2042pub fn sign_proof_bundle_hmac_sha256(
2043    proof_bundle: impl Into<Vec<u8>>,
2044    key_id: impl Into<Vec<u8>>,
2045    secret: &[u8],
2046    context: impl Into<Vec<u8>>,
2047    issued_at_millis: Option<u64>,
2048    expires_at_millis: Option<u64>,
2049    nonce: impl Into<Vec<u8>>,
2050) -> Result<AuthenticatedProofEnvelope, Error> {
2051    let envelope = AuthenticatedProofEnvelope {
2052        algorithm: AUTHENTICATED_PROOF_ENVELOPE_ALGORITHM_HMAC_SHA256.to_string(),
2053        key_id: key_id.into(),
2054        proof_bundle: proof_bundle.into(),
2055        context: context.into(),
2056        issued_at_millis,
2057        expires_at_millis,
2058        nonce: nonce.into(),
2059        signature: Vec::new(),
2060    };
2061    let signature = hmac_sha256(
2062        secret,
2063        &authenticated_proof_envelope_signing_bytes(&envelope)?,
2064    );
2065    Ok(AuthenticatedProofEnvelope {
2066        signature: signature.to_vec(),
2067        ..envelope
2068    })
2069}
2070
2071/// Verify an authenticated proof envelope with the shared secret.
2072///
2073/// Passing `None` for `now_millis` skips issue and expiration checks while still
2074/// authenticating the envelope bytes.
2075pub fn verify_authenticated_proof_envelope(
2076    envelope: &AuthenticatedProofEnvelope,
2077    secret: &[u8],
2078    now_millis: Option<u64>,
2079) -> AuthenticatedProofEnvelopeVerification {
2080    let algorithm_supported =
2081        envelope.algorithm == AUTHENTICATED_PROOF_ENVELOPE_ALGORITHM_HMAC_SHA256;
2082    let signature_valid = algorithm_supported
2083        && authenticated_proof_envelope_signing_bytes(envelope)
2084            .map(|bytes| {
2085                let expected = hmac_sha256(secret, &bytes);
2086                constant_time_eq(&expected, &envelope.signature)
2087            })
2088            .unwrap_or(false);
2089    let (not_yet_valid, expired) = match now_millis {
2090        Some(now) => (
2091            envelope
2092                .issued_at_millis
2093                .is_some_and(|issued_at| issued_at > now),
2094            envelope
2095                .expires_at_millis
2096                .is_some_and(|expires_at| expires_at <= now),
2097        ),
2098        None => (false, false),
2099    };
2100    let time_valid = !not_yet_valid && !expired;
2101
2102    AuthenticatedProofEnvelopeVerification {
2103        valid: signature_valid && time_valid,
2104        signature_valid,
2105        time_valid,
2106        not_yet_valid,
2107        expired,
2108        algorithm: envelope.algorithm.clone(),
2109        key_id: envelope.key_id.clone(),
2110        proof_bundle: envelope.proof_bundle.clone(),
2111        context: envelope.context.clone(),
2112        issued_at_millis: envelope.issued_at_millis,
2113        expires_at_millis: envelope.expires_at_millis,
2114        nonce: envelope.nonce.clone(),
2115    }
2116}
2117
2118/// Decode, authenticate, and verify serialized proof envelope bytes.
2119///
2120/// This is the one-shot verifier for transport payloads created by
2121/// [`AuthenticatedProofEnvelope::to_bytes`]. A malformed envelope is returned as
2122/// an error. An envelope with a bad signature or invalid time bounds returns a
2123/// verification record with `valid = false` and does not attempt proof
2124/// verification. An authenticated envelope carrying a malformed proof bundle
2125/// returns `valid = false` with `proof_error` populated.
2126pub fn verify_authenticated_proof_bundle(
2127    envelope_bytes: &[u8],
2128    secret: &[u8],
2129    now_millis: Option<u64>,
2130) -> Result<AuthenticatedProofBundleVerification, Error> {
2131    let envelope = AuthenticatedProofEnvelope::from_bytes(envelope_bytes)?;
2132    let envelope = verify_authenticated_proof_envelope(&envelope, secret, now_millis);
2133
2134    if !envelope.valid {
2135        return Ok(AuthenticatedProofBundleVerification {
2136            valid: false,
2137            envelope,
2138            proof: None,
2139            proof_error: None,
2140        });
2141    }
2142
2143    match verify_proof_bundle(&envelope.proof_bundle) {
2144        Ok(proof) => Ok(AuthenticatedProofBundleVerification {
2145            valid: proof.valid,
2146            envelope,
2147            proof: Some(proof),
2148            proof_error: None,
2149        }),
2150        Err(err) => Ok(AuthenticatedProofBundleVerification {
2151            valid: false,
2152            envelope,
2153            proof: None,
2154            proof_error: Some(err.to_string()),
2155        }),
2156    }
2157}
2158
2159fn proof_node_map(proof: &MultiKeyProof) -> Option<HashMap<Cid, &Node>> {
2160    let mut node_map = HashMap::with_capacity(proof.path.len());
2161    for node in &proof.path {
2162        if !node_shape_is_valid(node) {
2163            return None;
2164        }
2165        node_map.insert(node.cid(), node);
2166    }
2167    Some(node_map)
2168}
2169
2170fn range_proof_node_map(proof: &RangeProof) -> Option<HashMap<Cid, &Node>> {
2171    let mut node_map = HashMap::with_capacity(proof.path.len());
2172    for node in &proof.path {
2173        if !node_shape_is_valid(node) {
2174            return None;
2175        }
2176        node_map.insert(node.cid(), node);
2177    }
2178    Some(node_map)
2179}
2180
2181fn range_page_proof_node_map(proof: &RangePageProof) -> Option<HashMap<Cid, &Node>> {
2182    let mut node_map = HashMap::with_capacity(proof.path.len());
2183    for node in &proof.path {
2184        if !node_shape_is_valid(node) {
2185            return None;
2186        }
2187        node_map.insert(node.cid(), node);
2188    }
2189    Some(node_map)
2190}
2191
2192fn diff_verified_entries(base: &[(Vec<u8>, Vec<u8>)], other: &[(Vec<u8>, Vec<u8>)]) -> Vec<Diff> {
2193    let mut diffs = Vec::new();
2194    let mut base_idx = 0;
2195    let mut other_idx = 0;
2196
2197    while base_idx < base.len() && other_idx < other.len() {
2198        let (base_key, base_value) = &base[base_idx];
2199        let (other_key, other_value) = &other[other_idx];
2200        match base_key.cmp(other_key) {
2201            std::cmp::Ordering::Less => {
2202                diffs.push(Diff::Removed {
2203                    key: base_key.clone(),
2204                    val: base_value.clone(),
2205                });
2206                base_idx += 1;
2207            }
2208            std::cmp::Ordering::Greater => {
2209                diffs.push(Diff::Added {
2210                    key: other_key.clone(),
2211                    val: other_value.clone(),
2212                });
2213                other_idx += 1;
2214            }
2215            std::cmp::Ordering::Equal => {
2216                if base_value != other_value {
2217                    diffs.push(Diff::Changed {
2218                        key: base_key.clone(),
2219                        old: base_value.clone(),
2220                        new: other_value.clone(),
2221                    });
2222                }
2223                base_idx += 1;
2224                other_idx += 1;
2225            }
2226        }
2227    }
2228
2229    for (key, value) in &base[base_idx..] {
2230        diffs.push(Diff::Removed {
2231            key: key.clone(),
2232            val: value.clone(),
2233        });
2234    }
2235    for (key, value) in &other[other_idx..] {
2236        diffs.push(Diff::Added {
2237            key: key.clone(),
2238            val: value.clone(),
2239        });
2240    }
2241
2242    diffs
2243}
2244
2245fn verify_diff_page_lookahead(
2246    base: &KeyProofVerification,
2247    other: &KeyProofVerification,
2248    after: Option<&[u8]>,
2249    requested_end: Option<&[u8]>,
2250    proof_end: Option<&[u8]>,
2251) -> bool {
2252    if !base.valid || !other.valid || base.key != other.key {
2253        return false;
2254    }
2255    let key = base.key.as_slice();
2256    if proof_end != Some(key) || !key_in_page_range(key, after, requested_end) {
2257        return false;
2258    }
2259    match (&base.value, &other.value) {
2260        (None, None) => false,
2261        (Some(left), Some(right)) => left != right,
2262        _ => true,
2263    }
2264}
2265
2266fn verify_range_node(
2267    cid: &Cid,
2268    start: &[u8],
2269    end: Option<&[u8]>,
2270    node_map: &HashMap<Cid, &Node>,
2271    stack: &mut HashSet<Cid>,
2272    entries: &mut Vec<(Vec<u8>, Vec<u8>)>,
2273) -> bool {
2274    if !stack.insert(cid.clone()) {
2275        return false;
2276    }
2277
2278    let Some(node) = node_map.get(cid).copied() else {
2279        stack.remove(cid);
2280        return false;
2281    };
2282
2283    if node.leaf {
2284        for (key, value) in node.keys.iter().zip(&node.vals) {
2285            if key_in_range(key, start, end) {
2286                entries.push((key.clone(), value.clone()));
2287            }
2288        }
2289        stack.remove(cid);
2290        return true;
2291    }
2292
2293    for idx in overlapping_child_index_range(node, start, end) {
2294        let child_start = node.keys[idx].as_slice();
2295        let child_end = child_span_end(node, idx, None);
2296        if !span_overlaps_range(child_start, child_end, start, end) {
2297            if range_ends_before_or_at(end, child_start) {
2298                break;
2299            }
2300            continue;
2301        }
2302
2303        let Some(child_cid) = node
2304            .vals
2305            .get(idx)
2306            .and_then(|bytes| cid_from_child_bytes(bytes))
2307        else {
2308            stack.remove(cid);
2309            return false;
2310        };
2311        let Some(child) = node_map.get(&child_cid).copied() else {
2312            stack.remove(cid);
2313            return false;
2314        };
2315        if node.level != child.level.saturating_add(1) {
2316            stack.remove(cid);
2317            return false;
2318        }
2319        if !verify_range_node(&child_cid, start, end, node_map, stack, entries) {
2320            stack.remove(cid);
2321            return false;
2322        }
2323    }
2324
2325    stack.remove(cid);
2326    true
2327}
2328
2329fn verify_range_page_node(
2330    cid: &Cid,
2331    after: Option<&[u8]>,
2332    end: Option<&[u8]>,
2333    node_map: &HashMap<Cid, &Node>,
2334    stack: &mut HashSet<Cid>,
2335    entries: &mut Vec<(Vec<u8>, Vec<u8>)>,
2336) -> bool {
2337    if !stack.insert(cid.clone()) {
2338        return false;
2339    }
2340
2341    let Some(node) = node_map.get(cid).copied() else {
2342        stack.remove(cid);
2343        return false;
2344    };
2345
2346    if node.leaf {
2347        for (key, value) in node.keys.iter().zip(&node.vals) {
2348            if key_in_page_range(key, after, end) {
2349                entries.push((key.clone(), value.clone()));
2350            }
2351        }
2352        stack.remove(cid);
2353        return true;
2354    }
2355
2356    let traversal_start = after.unwrap_or(&[]);
2357    for idx in overlapping_child_index_range(node, traversal_start, end) {
2358        let child_start = node.keys[idx].as_slice();
2359        let child_end = child_span_end(node, idx, None);
2360        if !span_overlaps_page_range(child_start, child_end, after, end) {
2361            if range_ends_before_or_at(end, child_start) {
2362                break;
2363            }
2364            continue;
2365        }
2366
2367        let Some(child_cid) = node
2368            .vals
2369            .get(idx)
2370            .and_then(|bytes| cid_from_child_bytes(bytes))
2371        else {
2372            stack.remove(cid);
2373            return false;
2374        };
2375        let Some(child) = node_map.get(&child_cid).copied() else {
2376            stack.remove(cid);
2377            return false;
2378        };
2379        if node.level != child.level.saturating_add(1) {
2380            stack.remove(cid);
2381            return false;
2382        }
2383        if !verify_range_page_node(&child_cid, after, end, node_map, stack, entries) {
2384            stack.remove(cid);
2385            return false;
2386        }
2387    }
2388
2389    stack.remove(cid);
2390    true
2391}
2392
2393fn verified_value_from_node_set(
2394    root: &Cid,
2395    key: &[u8],
2396    node_map: &HashMap<Cid, &Node>,
2397) -> Option<Option<Vec<u8>>> {
2398    let mut cid = root.clone();
2399    let mut visited = HashSet::new();
2400
2401    for _ in 0..=node_map.len() {
2402        if !visited.insert(cid.clone()) {
2403            return None;
2404        }
2405
2406        let node = *node_map.get(&cid)?;
2407        if node.leaf {
2408            return Some(verified_leaf_value(Some(node), key));
2409        }
2410
2411        let child_index = path_child_index(node, key);
2412        let child_cid = cid_from_child_bytes(node.vals.get(child_index)?)?;
2413        let child = *node_map.get(&child_cid)?;
2414        if node.level != child.level.saturating_add(1) {
2415            return None;
2416        }
2417        cid = child_cid;
2418    }
2419
2420    None
2421}
2422
2423fn proof_is_consistent(proof: &KeyProof) -> bool {
2424    match (&proof.root, proof.path.as_slice()) {
2425        (None, []) => return true,
2426        (None, _) | (Some(_), []) => return false,
2427        (Some(root), [first, ..]) if &first.cid() != root => return false,
2428        _ => {}
2429    }
2430
2431    for (depth, node) in proof.path.iter().enumerate() {
2432        if !node_shape_is_valid(node) {
2433            return false;
2434        }
2435
2436        let is_last = depth + 1 == proof.path.len();
2437        if is_last {
2438            return node.leaf;
2439        }
2440
2441        if node.leaf {
2442            return false;
2443        }
2444
2445        let next = &proof.path[depth + 1];
2446        if node.level != next.level.saturating_add(1) {
2447            return false;
2448        }
2449
2450        let child_index = path_child_index(node, &proof.key);
2451        let Some(child_bytes) = node.vals.get(child_index) else {
2452            return false;
2453        };
2454        let Some(child_cid) = cid_from_child_bytes(child_bytes) else {
2455            return false;
2456        };
2457        if next.cid() != child_cid {
2458            return false;
2459        }
2460    }
2461
2462    false
2463}
2464
2465fn verified_leaf_value(leaf: Option<&Node>, key: &[u8]) -> Option<Vec<u8>> {
2466    let leaf = leaf?;
2467    if !leaf.leaf {
2468        return None;
2469    }
2470    match leaf.search(key) {
2471        Ok(index) => leaf.vals.get(index).cloned(),
2472        Err(_) => None,
2473    }
2474}
2475
2476fn node_shape_is_valid(node: &Node) -> bool {
2477    if node.keys.is_empty() || node.keys.len() != node.vals.len() {
2478        return false;
2479    }
2480
2481    if !node.keys.windows(2).all(|window| window[0] < window[1]) {
2482        return false;
2483    }
2484
2485    node.leaf || node.vals.iter().all(|value| value.len() == 32)
2486}
2487
2488fn path_child_index(node: &Node, key: &[u8]) -> usize {
2489    node.keys
2490        .partition_point(|candidate| candidate.as_slice() <= key)
2491        .saturating_sub(1)
2492}
2493
2494fn overlapping_child_index_range(
2495    node: &Node,
2496    range_start: &[u8],
2497    range_end: Option<&[u8]>,
2498) -> std::ops::Range<usize> {
2499    let start = node
2500        .keys
2501        .partition_point(|candidate| candidate.as_slice() < range_start)
2502        .saturating_sub(1);
2503    let end = range_end.map_or(node.len(), |end| {
2504        node.keys
2505            .partition_point(|candidate| candidate.as_slice() < end)
2506    });
2507    start..end.max(start).min(node.len())
2508}
2509
2510fn child_span_end<'a>(node: &'a Node, idx: usize, span_end: Option<&'a [u8]>) -> Option<&'a [u8]> {
2511    node.keys.get(idx + 1).map(Vec::as_slice).or(span_end)
2512}
2513
2514fn span_overlaps_range(
2515    span_start: &[u8],
2516    span_end: Option<&[u8]>,
2517    range_start: &[u8],
2518    range_end: Option<&[u8]>,
2519) -> bool {
2520    !span_ends_before_or_at(span_end, range_start)
2521        && !range_ends_before_or_at(range_end, span_start)
2522}
2523
2524fn span_ends_before_or_at(end: Option<&[u8]>, start: &[u8]) -> bool {
2525    end.is_some_and(|end| end <= start)
2526}
2527
2528fn range_ends_before_or_at(end: Option<&[u8]>, start: &[u8]) -> bool {
2529    end.is_some_and(|end| end <= start)
2530}
2531
2532fn range_is_empty_by_bounds(start: &[u8], end: Option<&[u8]>) -> bool {
2533    end.is_some_and(|end| end <= start)
2534}
2535
2536fn page_range_is_empty_by_bounds(after: Option<&[u8]>, end: Option<&[u8]>) -> bool {
2537    match (after, end) {
2538        (Some(after), Some(end)) => end <= after,
2539        (None, Some(end)) => end.is_empty(),
2540        _ => false,
2541    }
2542}
2543
2544fn key_in_range(key: &[u8], start: &[u8], end: Option<&[u8]>) -> bool {
2545    key >= start
2546        && match end {
2547            Some(end) => key < end,
2548            None => true,
2549        }
2550}
2551
2552fn key_in_page_range(key: &[u8], after: Option<&[u8]>, end: Option<&[u8]>) -> bool {
2553    after.map_or(true, |after| key > after)
2554        && match end {
2555            Some(end) => key < end,
2556            None => true,
2557        }
2558}
2559
2560fn span_overlaps_page_range(
2561    span_start: &[u8],
2562    span_end: Option<&[u8]>,
2563    after: Option<&[u8]>,
2564    end: Option<&[u8]>,
2565) -> bool {
2566    !after.is_some_and(|after| span_ends_before_or_at(span_end, after))
2567        && !range_ends_before_or_at(end, span_start)
2568}
2569
2570fn cid_from_child_bytes(bytes: &[u8]) -> Option<Cid> {
2571    bytes.try_into().ok().map(Cid)
2572}
2573
2574fn proof_bundle_to_bytes(wire: ProofBundleWire) -> Result<Vec<u8>, Error> {
2575    serde_cbor::ser::to_vec_packed(&wire).map_err(|err| Error::Serialize(err.to_string()))
2576}
2577
2578fn proof_bundle_from_bytes(bytes: &[u8]) -> Result<ProofBundleWire, Error> {
2579    let wire: ProofBundleWire =
2580        serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))?;
2581    if wire.version != PROOF_BUNDLE_VERSION {
2582        return Err(proof_bundle_deserialize(format!(
2583            "unsupported proof bundle version {}",
2584            wire.version
2585        )));
2586    }
2587    match wire.kind {
2588        PROOF_BUNDLE_KIND_KEY
2589        | PROOF_BUNDLE_KIND_MULTI_KEY
2590        | PROOF_BUNDLE_KIND_RANGE
2591        | PROOF_BUNDLE_KIND_RANGE_PAGE => Ok(wire),
2592        other => Err(proof_bundle_deserialize(format!(
2593            "unsupported proof bundle kind {other}"
2594        ))),
2595    }
2596}
2597
2598fn diff_page_proof_bundle_from_bytes(bytes: &[u8]) -> Result<DiffPageProofBundleWire, Error> {
2599    let wire: DiffPageProofBundleWire =
2600        serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))?;
2601    if wire.version != PROOF_BUNDLE_VERSION {
2602        return Err(proof_bundle_deserialize(format!(
2603            "unsupported diff page proof bundle version {}",
2604            wire.version
2605        )));
2606    }
2607    if wire.kind != PROOF_BUNDLE_KIND_DIFF_PAGE {
2608        return Err(proof_bundle_deserialize(
2609            "proof bundle is not a diff page proof",
2610        ));
2611    }
2612    Ok(wire)
2613}
2614
2615fn proof_bundle_summary_from_wire(wire: ProofBundleWire) -> Result<ProofBundleSummary, Error> {
2616    Ok(ProofBundleSummary {
2617        version: wire.version,
2618        kind: proof_bundle_kind_from_u8(wire.kind)?,
2619        root: cid_from_bundle_root(wire.root)?,
2620        other_root: None,
2621        key_count: wire.keys.len(),
2622        path_node_count: wire.path_node_bytes.len(),
2623        start: wire.start,
2624        end: wire.end,
2625        after: wire.after,
2626        requested_end: None,
2627        limit: None,
2628        has_lookahead: false,
2629    })
2630}
2631
2632fn diff_page_proof_bundle_summary_from_wire(
2633    wire: DiffPageProofBundleWire,
2634) -> Result<ProofBundleSummary, Error> {
2635    let limit = usize::try_from(wire.limit)
2636        .map_err(|_| proof_bundle_deserialize("diff page proof bundle limit is too large"))?;
2637    let base = proof_bundle_from_bytes(&wire.base_range_page_proof)?;
2638    if base.kind != PROOF_BUNDLE_KIND_RANGE_PAGE {
2639        return Err(proof_bundle_deserialize(
2640            "diff page proof base proof must be a range page proof",
2641        ));
2642    }
2643    let other = proof_bundle_from_bytes(&wire.other_range_page_proof)?;
2644    if other.kind != PROOF_BUNDLE_KIND_RANGE_PAGE {
2645        return Err(proof_bundle_deserialize(
2646            "diff page proof other proof must be a range page proof",
2647        ));
2648    }
2649
2650    let mut path_node_count = base.path_node_bytes.len() + other.path_node_bytes.len();
2651    let mut has_lookahead = false;
2652    if let Some(lookahead) = &wire.lookahead_base_key_proof {
2653        let lookahead = proof_bundle_from_bytes(lookahead)?;
2654        if lookahead.kind != PROOF_BUNDLE_KIND_KEY {
2655            return Err(proof_bundle_deserialize(
2656                "diff page proof base lookahead must be a key proof",
2657            ));
2658        }
2659        path_node_count += lookahead.path_node_bytes.len();
2660        has_lookahead = true;
2661    }
2662    if let Some(lookahead) = &wire.lookahead_other_key_proof {
2663        let lookahead = proof_bundle_from_bytes(lookahead)?;
2664        if lookahead.kind != PROOF_BUNDLE_KIND_KEY {
2665            return Err(proof_bundle_deserialize(
2666                "diff page proof other lookahead must be a key proof",
2667            ));
2668        }
2669        path_node_count += lookahead.path_node_bytes.len();
2670        has_lookahead = true;
2671    }
2672
2673    Ok(ProofBundleSummary {
2674        version: wire.version,
2675        kind: ProofBundleKind::DiffPage,
2676        root: cid_from_bundle_root(base.root)?,
2677        other_root: cid_from_bundle_root(other.root)?,
2678        key_count: 0,
2679        path_node_count,
2680        start: None,
2681        end: base.end,
2682        after: base.after,
2683        requested_end: wire.requested_end,
2684        limit: Some(limit),
2685        has_lookahead,
2686    })
2687}
2688
2689fn proof_bundle_kind_from_u8(kind: u8) -> Result<ProofBundleKind, Error> {
2690    match kind {
2691        PROOF_BUNDLE_KIND_KEY => Ok(ProofBundleKind::Key),
2692        PROOF_BUNDLE_KIND_MULTI_KEY => Ok(ProofBundleKind::MultiKey),
2693        PROOF_BUNDLE_KIND_RANGE => Ok(ProofBundleKind::Range),
2694        PROOF_BUNDLE_KIND_RANGE_PAGE => Ok(ProofBundleKind::RangePage),
2695        PROOF_BUNDLE_KIND_DIFF_PAGE => Ok(ProofBundleKind::DiffPage),
2696        other => Err(proof_bundle_deserialize(format!(
2697            "unsupported proof bundle kind {other}"
2698        ))),
2699    }
2700}
2701
2702fn cid_from_bundle_root(root: Option<Vec<u8>>) -> Result<Option<Cid>, Error> {
2703    root.map(|bytes| {
2704        bytes
2705            .try_into()
2706            .map(Cid)
2707            .map_err(|_| proof_bundle_deserialize("proof bundle root CID must be 32 bytes"))
2708    })
2709    .transpose()
2710}
2711
2712fn proof_bundle_deserialize(message: impl Into<String>) -> Error {
2713    Error::Deserialize(format!("invalid proof bundle: {}", message.into()))
2714}
2715
2716fn authenticated_proof_envelope_to_bytes(
2717    envelope: &AuthenticatedProofEnvelope,
2718) -> Result<Vec<u8>, Error> {
2719    serde_cbor::ser::to_vec_packed(&AuthenticatedProofEnvelopeWire {
2720        version: AUTHENTICATED_PROOF_ENVELOPE_VERSION,
2721        algorithm: envelope.algorithm.clone(),
2722        key_id: envelope.key_id.clone(),
2723        proof_bundle: envelope.proof_bundle.clone(),
2724        context: envelope.context.clone(),
2725        issued_at_millis: envelope.issued_at_millis,
2726        expires_at_millis: envelope.expires_at_millis,
2727        nonce: envelope.nonce.clone(),
2728        signature: envelope.signature.clone(),
2729    })
2730    .map_err(|err| Error::Serialize(err.to_string()))
2731}
2732
2733fn authenticated_proof_envelope_from_bytes(
2734    bytes: &[u8],
2735) -> Result<AuthenticatedProofEnvelope, Error> {
2736    let wire: AuthenticatedProofEnvelopeWire =
2737        serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))?;
2738    if wire.version != AUTHENTICATED_PROOF_ENVELOPE_VERSION {
2739        return Err(authenticated_proof_envelope_deserialize(format!(
2740            "unsupported envelope version {}",
2741            wire.version
2742        )));
2743    }
2744    if wire.algorithm != AUTHENTICATED_PROOF_ENVELOPE_ALGORITHM_HMAC_SHA256 {
2745        return Err(authenticated_proof_envelope_deserialize(format!(
2746            "unsupported envelope algorithm {}",
2747            wire.algorithm
2748        )));
2749    }
2750    if wire.signature.len() != 32 {
2751        return Err(authenticated_proof_envelope_deserialize(
2752            "HMAC-SHA256 signature must be 32 bytes",
2753        ));
2754    }
2755    Ok(AuthenticatedProofEnvelope {
2756        algorithm: wire.algorithm,
2757        key_id: wire.key_id,
2758        proof_bundle: wire.proof_bundle,
2759        context: wire.context,
2760        issued_at_millis: wire.issued_at_millis,
2761        expires_at_millis: wire.expires_at_millis,
2762        nonce: wire.nonce,
2763        signature: wire.signature,
2764    })
2765}
2766
2767fn authenticated_proof_envelope_signing_bytes(
2768    envelope: &AuthenticatedProofEnvelope,
2769) -> Result<Vec<u8>, Error> {
2770    let mut bytes = AUTHENTICATED_PROOF_ENVELOPE_DOMAIN.to_vec();
2771    bytes.push(0);
2772    let payload = serde_cbor::ser::to_vec_packed(&AuthenticatedProofEnvelopeSigningWire {
2773        version: AUTHENTICATED_PROOF_ENVELOPE_VERSION,
2774        algorithm: envelope.algorithm.clone(),
2775        key_id: envelope.key_id.clone(),
2776        proof_bundle: envelope.proof_bundle.clone(),
2777        context: envelope.context.clone(),
2778        issued_at_millis: envelope.issued_at_millis,
2779        expires_at_millis: envelope.expires_at_millis,
2780        nonce: envelope.nonce.clone(),
2781    })
2782    .map_err(|err| Error::Serialize(err.to_string()))?;
2783    bytes.extend(payload);
2784    Ok(bytes)
2785}
2786
2787fn authenticated_proof_envelope_deserialize(message: impl Into<String>) -> Error {
2788    Error::Deserialize(format!(
2789        "invalid authenticated proof envelope: {}",
2790        message.into()
2791    ))
2792}
2793
2794fn hmac_sha256(secret: &[u8], message: &[u8]) -> [u8; 32] {
2795    const BLOCK_SIZE: usize = 64;
2796
2797    let mut key_block = [0u8; BLOCK_SIZE];
2798    if secret.len() > BLOCK_SIZE {
2799        let digest = Sha256::digest(secret);
2800        key_block[..digest.len()].copy_from_slice(&digest);
2801    } else {
2802        key_block[..secret.len()].copy_from_slice(secret);
2803    }
2804
2805    let mut inner_pad = [0x36u8; BLOCK_SIZE];
2806    let mut outer_pad = [0x5cu8; BLOCK_SIZE];
2807    for idx in 0..BLOCK_SIZE {
2808        inner_pad[idx] ^= key_block[idx];
2809        outer_pad[idx] ^= key_block[idx];
2810    }
2811
2812    let mut inner = Sha256::new();
2813    inner.update(inner_pad);
2814    inner.update(message);
2815    let inner_digest = inner.finalize();
2816
2817    let mut outer = Sha256::new();
2818    outer.update(outer_pad);
2819    outer.update(inner_digest);
2820    outer.finalize().into()
2821}
2822
2823fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
2824    if left.len() != right.len() {
2825        return false;
2826    }
2827    let mut diff = 0u8;
2828    for (&left_byte, &right_byte) in left.iter().zip(right) {
2829        diff |= left_byte ^ right_byte;
2830    }
2831    diff == 0
2832}