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