first commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# inclusion-proof
|
||||
|
||||
## Purpose
|
||||
|
||||
Verifies an RFC 6962 Merkle inclusion proof as returned by Sigstore Rekor.
|
||||
Given a leaf (the canonical body a Rekor entry was logged as), a sibling
|
||||
audit path, the leaf's log index, the tree size at inclusion time, and the
|
||||
signed root hash, recomputes the root and confirms it matches. This is the
|
||||
cryptographic half of Rekor trust — once the root matches, the transparency
|
||||
log has published the entry and cannot later lie about having done so.
|
||||
|
||||
## Features
|
||||
|
||||
- RFC 6962 hashing with explicit domain separators (`0x00` for leaves,
|
||||
`0x01` for interior nodes).
|
||||
- Correct handling of last-node-at-level carrying (odd tree sizes).
|
||||
- Structured `RekorInclusionError` when `logIndex` is out of range.
|
||||
- Exposes `rfc6962LeafHash` for batch / cache use-cases.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { verifyRfc6962InclusionProof } from "@suaveplan/slsa";
|
||||
|
||||
const { valid, computedRootHex } = verifyRfc6962InclusionProof({
|
||||
leafBytes: new TextEncoder().encode(entry.body),
|
||||
hashes: entry.verification.inclusionProof.hashes,
|
||||
logIndex: entry.logIndex,
|
||||
treeSize: entry.verification.inclusionProof.treeSize,
|
||||
expectedRootHex: entry.verification.inclusionProof.rootHash,
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
RekorClient,
|
||||
rfc6962LeafHash,
|
||||
verifyRfc6962InclusionProof,
|
||||
} from "@suaveplan/slsa";
|
||||
|
||||
const client = new RekorClient();
|
||||
const entry = await client.uploadHashedrekord(rekord);
|
||||
const leafBytes = new TextEncoder().encode(entry.body);
|
||||
|
||||
const leafHex = rfc6962LeafHash(leafBytes);
|
||||
log.info({ leafHex, logIndex: entry.logIndex }, "rekor entry hashed");
|
||||
|
||||
const proof = entry.verification?.inclusionProof;
|
||||
if (proof) {
|
||||
const { valid } = verifyRfc6962InclusionProof({
|
||||
leafBytes,
|
||||
hashes: proof.hashes,
|
||||
logIndex: entry.logIndex,
|
||||
treeSize: proof.treeSize,
|
||||
expectedRootHex: proof.rootHash,
|
||||
});
|
||||
if (!valid) throw new Error("inclusion failed");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `verifyRfc6962InclusionProof(args): { valid, computedRootHex }` — returns
|
||||
whether the computed root matches. Throws `RekorInclusionError` when
|
||||
`logIndex >= treeSize` or `logIndex < 0`.
|
||||
- `rfc6962LeafHash(body: Uint8Array): string` — lowercase hex SHA-256 with
|
||||
the `0x00` leaf prefix.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The carry-up branch (odd-count level) handles trees where the last node at a
|
||||
given level has no sibling — rather than duplicating, RFC 6962 propagates
|
||||
the node up unchanged. Rekor's server-side proof generator will either
|
||||
produce a shorter path for such leaves or include a duplicate; this
|
||||
implementation tolerates the former. String comparison is
|
||||
case-insensitive on the expected root to match Sigstore's mixed-case hex
|
||||
output.
|
||||
Reference in New Issue
Block a user