1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! Issue and verify `lys/anchor-receipt/v1` `COSE_Sign1` inclusion receipts.
//!
//! [`sign_receipt`] takes the leaf the anchor notarized and the RFC 6962
//! inclusion proof for it, **recomputes the Merkle root itself**, and signs
//! that root as a detached COSE payload:
//!
//! ```text
//! Sig_structure = ["Signature1", protected, h'', root]
//! protected = {1: -8 (EdDSA), 3: <v1 receipt content type>,
//! 4: <anchor key>, 395: 1 (RFC9162_SHA256)}
//! root = SHA-256 Merkle Tree Hash reconstructed from leaf + path
//! ```
//!
//! The anchor never signs a caller-supplied root. It signs a value it derived,
//! which is the difference between vouching for a tree it computed and
//! rubber-stamping a number someone handed it — **a claim must not be settable
//! by the party being judged.**
//!
//! [`verify_receipt`] reverses it: recompute the leaf hash, walk the path to a
//! candidate root, and verify the anchor's signature against *that*. Any
//! mismatch collapses to [`TrustError::ReceiptVerification`].
//!
//! # Verifying requires naming the anchor you expect
//!
//! There is deliberately no way to "just verify" a receipt. A receipt carries
//! the anchor's key in its own protected header, so it verifies against
//! whatever key it carries — a receipt signed by a key nobody recognises is
//! cryptographically perfect and vouches for nothing. This is the same trap as
//! a self-signed certificate: the signature proves someone holding a key
//! *said* something, never that the statement is true.
//!
//! So [`verify_receipt`] requires the caller to pass the anchor key they
//! trust, and enforces the match. Callers who genuinely want to inspect an
//! unattributed receipt use [`AnchorReceipt::from_cose_bytes`], which is
//! honestly labelled as parsing rather than verification.
//!
//! # Domain separation from attestations
//!
//! A receipt's signing preimage begins `0x84 0x6A "Signature1"` — **identical**
//! to a `lys/attestation/v2` attestation's, because both are `COSE_Sign1`. The
//! byte-0 disjointness argument that separates attestations from signed notes
//! and X.509 TBS bytes therefore does *not* separate receipts from
//! attestations, and it would be a mistake to assume it does.
//!
//! What separates them is inside the signed bytes, in two independent places:
//!
//! - **The protected bucket differs.** An attestation's is a 3-entry map whose
//! content type is `application/vnd.lys.attestation.v2+cbor`; a receipt's is
//! a 4-entry map whose content type is
//! `application/vnd.lys.receipt.v1+cbor` and which carries `vds`. Different
//! bytes at the same offset means different signed messages, so no signature
//! is ever valid under both readings.
//! - **The payload differs in kind and length.** An attestation signs a
//! two-entry claims map (38–46 bytes); a receipt signs a bare 32-byte root.
//!
//! Both artifact verifiers additionally *pin* their own content type, so
//! neither will parse the other at all. Separation is by construction and by
//! enforcement, not by luck.
//!
//! # Known limitation: `tree_size` is authenticated only up to its walk class
//!
//! Because the proof rides in the unprotected header and is authenticated by
//! consequence, `tree_size` is pinned only as far as it changes the
//! reconstruction. It does not always change it. For `leaf_index = 0`, a tree
//! of size 3 and a tree of size 4 produce the *same* sequence of
//! left/right combinations, so one valid receipt can be re-presented with the
//! other size: same leaf, same index, same root, same signature, different
//! claimed size. `receipt::sign_tests` proves this rather than leaving it to
//! be discovered.
//!
//! This is not a forgery and not a false inclusion claim — the leaf, the
//! index and the root are all unaffected, and the information needed to
//! distinguish the two sizes is simply not present in an RFC 6962 inclusion
//! path. It is why RFC 9162 verifiers are told to check a proof against a
//! *known* root at a *known* size. A consumer who relies on `tree_size` for
//! anything load-bearing must cross-check it against the anchor's published
//! checkpoint at that size, where the mismatch shows up immediately.
//!
//! Fixing it in the format would mean moving the proof into the protected
//! header or signing `root ‖ tree_size`, and both break RFC 9942 conformance
//! — a high price for a low-impact, externally-detectable malleability. The
//! choice is to conform and document.
use crate;
use crateEd25519Identity;
use crateraw_leaf_hash;
use crateAnchorReceipt;
use crate;
use crate::;
/// Issue a receipt for `leaf` at `leaf_index` in a tree of `tree_size`,
/// signing the reconstructed Merkle root with the anchor's key.
///
/// `leaf` is the raw leaf bytes as logged — for an anchor notarizing a child
/// log, the child's checkpoint bytes, which are self-describing (origin, size,
/// root). That is why no separate binding between receipt and child is needed:
/// verifying the receipt establishes *which* child root was notarized.
///
/// `inclusion_path` is the RFC 6962 path for `leaf_index` in a tree of
/// `tree_size`, leaf-ward first — exactly what
/// [`AppendOnlyTree`](crate::merkle::AppendOnlyTree) produces.
///
/// # Errors
///
/// Returns [`TrustError::MerkleTree`] with an actionable reason if the proof is
/// not self-consistent — a path whose length disagrees with
/// `(leaf_index, tree_size)`, an index outside the tree, an empty tree, or a
/// path longer than any `u64`-sized tree could require. Errors here are
/// deliberately descriptive rather than non-oracle: this is the anchor
/// operating on its own tree, no secret participates in the outcome, and the
/// caller already knows every input.
///
/// `tree_size == 1` is refused. RFC 9942's CDDL types the inclusion path as
/// `[+ bstr]` — *one or more* — and the sole leaf of a one-leaf tree has an
/// empty path, so a receipt for it could not be a conforming artifact. The
/// remedy is operational and cheap: seed the anchor's log with a genesis leaf
/// at initialisation, after which `tree_size >= 2` always holds and every path
/// has at least one node. Emitting a technically non-conforming *first*
/// receipt would be the worst possible one to get wrong, since the earliest
/// artifacts are the ones others reach for as interop test vectors.
/// Verify that `receipt` is a valid inclusion receipt for `leaf`, issued by
/// the anchor whose key is `expected_anchor_public_key`.
///
/// Three things must hold, and all three failures are indistinguishable:
///
/// 1. The receipt names the expected anchor. Without this the check would
/// prove only that *someone* signed, which is not a trust statement — see
/// the module docs.
/// 2. The proof is internally consistent, so a root can be reconstructed from
/// `leaf` and the path at the claimed index and size.
/// 3. The anchor's signature verifies over that **reconstructed** root. This
/// is what authenticates the unprotected proof: a tampered path, index or
/// size yields a root the anchor never signed.
///
/// Verification uses strict Ed25519, rejecting malleable signatures and
/// small-order keys — non-repudiation requires a unique valid signature.
///
/// # Errors
///
/// Returns [`TrustError::ReceiptVerification`] for every failure. A receipt from
/// the wrong anchor, an inconsistent proof, a tampered path and a forged
/// signature are deliberately indistinguishable: a receipt verifier is exactly
/// the network-exposed surface where a distinguishable error becomes a parsing
/// oracle. Use [`AnchorReceipt::reconstructed_root`] when diagnosing a receipt
/// you own.
/// Parse a tagged `COSE_Sign1` receipt and verify it against `leaf` and the
/// expected anchor in one step, returning the parsed [`AnchorReceipt`] — the
/// bytes-in convenience for consumers holding raw artifact bytes.
///
/// Equivalent to [`AnchorReceipt::from_cose_bytes`] followed by
/// [`verify_receipt`].
///
/// # Errors
///
/// Returns [`TrustError::ReceiptVerification`] for every failure — a malformed or
/// non-canonical artifact, the wrong anchor, an inconsistent proof and an
/// invalid signature are deliberately indistinguishable (non-oracle).