@agentcontextdistributionprotocol/acdp-wasm 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # acdp-wasm — ACDP WebAssembly verification core
2
+
3
+ The **browser / edge / WASI** member of the ACDP binding family
4
+ (`bindings/acdp-py`, `bindings/acdp-node`). A pure, **offline**
5
+ cryptographic verifier: it lets a consumer render an ACDP context and
6
+ independently reach a real verification **verdict** — the producer
7
+ signature, the `content_hash`, a registry receipt (RFC-ACDP-0010), a
8
+ lineage-head receipt (RFC-ACDP-0011), a transparency-log checkpoint /
9
+ inclusion / consistency proof (RFC-ACDP-0012), a lifecycle event
10
+ (RFC-ACDP-0013), a key revocation (RFC-ACDP-0014), and witness
11
+ cosignatures + quorum (RFC-ACDP-0015) — **without trusting any server to
12
+ have done it**. This is the client-side verification core the console's
13
+ verdicts consume.
14
+
15
+ It is a standalone Cargo package (its own `[workspace]`) that depends on
16
+ the umbrella `acdp` crate with `default-features = false`, so
17
+ `reqwest` / `tokio` / `rustls` never enter the `.wasm` binary. See
18
+ `docs/research/wasm-target.md` for the design rationale.
19
+
20
+ ## Design (same rules as the Python / Node bindings)
21
+
22
+ - **JSON across the boundary.** Every export takes JSON strings and
23
+ returns a JSON string — a **verdict object** (`{"valid": true, ...}` /
24
+ `{"valid": false, "code"?, "error"}`) for verification outcomes, or a
25
+ result string for constructors/resolvers. Malformed *host* input
26
+ throws a `JsError`; a failed *verification* is `{"valid": false}`,
27
+ never a throw.
28
+ - **Crypto in Rust, HTTP in the host.** No network calls. `did:web`
29
+ resolution and all transport stay in JS (`fetch` the DID document /
30
+ receipt / body, pass the JSON in). `did:key` verification is fully
31
+ offline and needs no host help — the highest-value browser path.
32
+ - **No crypto reimplemented.** Every check delegates to the same `acdp`
33
+ core the native library, Python, and Node bindings use. The 0.3/0.4
34
+ verdict logic (`src/v030.rs`, `src/v040.rs`) is lifted **verbatim,
35
+ byte-identical** from the other bindings.
36
+
37
+ ## Exported surface
38
+
39
+ wasm-bindgen exports (camelCase in JS/TypeScript):
40
+
41
+ | Export | RFC | Purpose |
42
+ |---|---|---|
43
+ | `verifyContentHash` | 0001 §5.7 | recompute `sha256(JCS(producer_content))` |
44
+ | `verifySignatureEd25519` / `verifySignatureP256` | 0001 §5.8 | signature over the ASCII `"sha256:<hex>"` string |
45
+ | `verifyBodyOffline` / `verifyPublishRequestOffline` | 0002 | full `did:key` context verify (no resolution) |
46
+ | `fingerprintEd25519` / `verifyReceipt` | 0010 | registry-receipt verification |
47
+ | `verifyLineageHeadReceipt` | 0011 | lineage-head receipt |
48
+ | `verifyLogCheckpoint` / `verifyLogInclusion` / `verifyLogConsistency` / `buildLogLeaf` / `merkleLeafHash` / `merkleNodeHash` / `merkleRootHash` | 0012 | transparency log |
49
+ | `verifyLifecycleEvent` | 0013 | lifecycle event |
50
+ | `parseKeyRevocation` / `classifyUnderRevocation` | 0014 | key revocation |
51
+ | `buildWitnessCosignature` / `verifyWitnessCosignature` / `evaluateWitnessQuorum` | 0015 | witness cosignatures + quorum |
52
+ | `resolveDidKey` | — | offline `did:key` → public key |
53
+ | `canonicalPreimage` / `explainHashMismatch` | — | hash-divergence diagnostics |
54
+
55
+ ## Install
56
+
57
+ Published to npm as **`@agentcontextdistributionprotocol/acdp-wasm`** (a
58
+ public scoped package, provenance-signed on GitHub Actions), so the console
59
+ and other consumers depend on it without a sibling checkout:
60
+
61
+ ```bash
62
+ npm install @agentcontextdistributionprotocol/acdp-wasm
63
+ ```
64
+
65
+ ```js
66
+ import init, { verifyContentHash, verifySignatureEd25519 }
67
+ from "@agentcontextdistributionprotocol/acdp-wasm";
68
+ await init();
69
+ const verdict = JSON.parse(verifyContentHash(bodyJson, body.content_hash));
70
+ if (verdict.valid) { /* the hash the consumer recomputed itself checks out */ }
71
+ ```
72
+
73
+ The package is built with `wasm-pack --target web`: an ESM module you
74
+ initialize once with `await init()`. This is the target the crate is
75
+ designed and CI-tested around, and it loads cleanly in browsers and in
76
+ Next.js client components (webpack 5 resolves the `new URL(..., import.meta.url)`
77
+ wasm asset the `web` target emits — no `experiments.asyncWebAssembly`
78
+ webpack override, which the `bundler` target would require).
79
+
80
+ ## Build
81
+
82
+ ```bash
83
+ rustup target add wasm32-unknown-unknown
84
+
85
+ # Raw wasm (regression build-check):
86
+ cargo build --target wasm32-unknown-unknown # verify-only default
87
+
88
+ # Browser package (.js / .wasm / .d.ts) with wasm-pack. `--scope` makes
89
+ # wasm-pack emit the package name `@agentcontextdistributionprotocol/acdp-wasm`
90
+ # (the same rename the release workflow performs):
91
+ wasm-pack build --target web --out-dir pkg --scope agentcontextdistributionprotocol
92
+ ```
93
+
94
+ Import from a browser (local, unpublished build):
95
+
96
+ ```js
97
+ import init, { verifyContentHash, verifySignatureEd25519 } from "./pkg/acdp_wasm.js";
98
+ await init();
99
+ const verdict = JSON.parse(verifyContentHash(bodyJson, body.content_hash));
100
+ if (verdict.valid) { /* the hash the consumer recomputed itself checks out */ }
101
+ ```
102
+
103
+ ## Golden-vector parity
104
+
105
+ `tests/golden.rs` is a **native** `cargo test` that runs the canonical
106
+ `sig-001` (content hash + Ed25519 signature) and `wit-001` (witness
107
+ cosignature mint + verify) spec fixtures through the same pure `core`
108
+ functions the wasm exports wrap, asserting byte-for-byte reproduction of
109
+ the pinned golden values — the same constants the `acdp-py` / `acdp-node`
110
+ suites pin. It locates fixtures via `ACDP_SPEC_DIR` and skips gracefully
111
+ when absent:
112
+
113
+ ```bash
114
+ ACDP_SPEC_DIR=../../../agentcontextdistributionprotocol cargo test
115
+ ```
116
+
117
+ `tests/wasm.rs` is a `wasm-bindgen-test` real-engine smoke test of the
118
+ exported wrappers (inline sig-001 constants, no fixtures needed):
119
+
120
+ ```bash
121
+ wasm-pack test --node
122
+ ```
123
+
124
+ ## Randomness — a correction to the research memo
125
+
126
+ `docs/research/wasm-target.md` §4 predicted a **verify-only** build would
127
+ need **no `getrandom` backend** on any wasm target. That holds at
128
+ *runtime* — no exported verification (nor the deterministic Ed25519
129
+ witness mint) draws randomness. It does **not** hold at *compile time*
130
+ for `wasm32-unknown-unknown`: the core crates pull two randomness sources
131
+ **unconditionally**, and each emits a hard `compile_error!` on that
132
+ target unless a backend is wired:
133
+
134
+ 1. **`getrandom 0.2`** via `rand_core 0.6` / `OsRng` (a non-optional
135
+ dependency of `acdp-crypto`) → enabled with the getrandom **`js`
136
+ feature**.
137
+ 2. **`getrandom 0.4`** via **`uuid` v4** (a non-optional dependency of
138
+ `acdp-primitives`) → enabled with the getrandom **`wasm_js` feature**
139
+ *plus* `--cfg getrandom_backend="wasm_js"` (set in
140
+ `.cargo/config.toml`). `uuid` additionally needs its own **`js`
141
+ feature** for its v4 RNG shim.
142
+
143
+ All three are wired here, **target-gated to `wasm32` only** (see
144
+ `Cargo.toml` and `.cargo/config.toml`), so a native `cargo test` is
145
+ untouched. `crypto.getRandomValues` (the `js` backend) is the CSPRNG
146
+ RFC-ACDP-0001 §5.10 names for the browser, so the choice is spec-blessed
147
+ — but it is never invoked on the verify path; it is present only to link.
148
+
149
+ A future `producer` feature (reserved, not yet wired) would expose fresh
150
+ key generation (`SigningKey::generate`, the only `OsRng` caller) and is
151
+ where a runtime randomness draw would actually occur.
152
+
153
+ ## Security notes
154
+
155
+ - This artifact is a **verifier, not a fetcher**. `RegistryClient`,
156
+ `CrossRegistryResolver`, and `did:web` resolution do NOT move to wasm;
157
+ the host owns HTTP and the RFC-ACDP-0006 §7 / RFC-ACDP-0008 SSRF
158
+ defenses. Pass fetched documents in as JSON.
159
+ - A browser tab is not an HSM. Verification holds no secrets, but a
160
+ future `producer`/keygen surface would place the signing seed in wasm
161
+ linear memory during a call (RFC-ACDP-0001 §5.10).
package/acdp_wasm.d.ts ADDED
@@ -0,0 +1,191 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Build the canonical §4 log leaf from a VERIFIED receipt (§9.1 step 1).
6
+ */
7
+ export function buildLogLeaf(receipt_json: string): string;
8
+
9
+ /**
10
+ * Mint a signed witness cosignature (RFC-ACDP-0015 §5). Deterministic.
11
+ */
12
+ export function buildWitnessCosignature(witnessed_checkpoint_json: string, witness_did: string, witness_seed_hex: string, witnessed_at_rfc3339: string): string;
13
+
14
+ /**
15
+ * The exact JCS canonical preimage hashed for `content_hash`.
16
+ */
17
+ export function canonicalPreimage(body_json: string): string;
18
+
19
+ /**
20
+ * Apply the RFC-ACDP-0014 §7 compromise-boundary rule (fail-closed).
21
+ */
22
+ export function classifyUnderRevocation(revocations_json: string, signer_fingerprint: string, receipt_created_at_rfc3339?: string | null): string;
23
+
24
+ /**
25
+ * Compute the RFC-ACDP-0015 §8 N-witnessed quorum report. Returns a
26
+ * report JSON.
27
+ */
28
+ export function evaluateWitnessQuorum(cosignatures_json: string, expected_checkpoint_json: string, trusted_witness_dids_json: string, witness_did_docs_json: string, policy_json: string, now_rfc3339?: string | null): string;
29
+
30
+ /**
31
+ * Diagnose a `content_hash` mismatch (human-readable, never a verdict).
32
+ */
33
+ export function explainHashMismatch(body_json: string, expected_hash: string): string;
34
+
35
+ /**
36
+ * `"sha256:" + hex(SHA-256(raw 32-byte key))`.
37
+ */
38
+ export function fingerprintEd25519(public_key_b64: string): string;
39
+
40
+ /**
41
+ * §5.1 leaf hash `SHA-256(0x00 ‖ JCS(leaf))`.
42
+ */
43
+ export function merkleLeafHash(leaf_json: string): string;
44
+
45
+ /**
46
+ * §5.1 interior-node hash `SHA-256(0x01 ‖ left ‖ right)`.
47
+ */
48
+ export function merkleNodeHash(left_hash: string, right_hash: string): string;
49
+
50
+ /**
51
+ * §5.2 RFC 6962 Merkle tree hash over an ordered array of leaf hashes.
52
+ */
53
+ export function merkleRootHash(leaf_hashes_json: string): string;
54
+
55
+ /**
56
+ * Parse + shape-validate a `key-revocation` body (RFC-ACDP-0014 §4).
57
+ */
58
+ export function parseKeyRevocation(body_json: string, signer_fingerprint?: string | null): string;
59
+
60
+ /**
61
+ * Resolve a `did:key:z…` DID to its public key material — offline.
62
+ */
63
+ export function resolveDidKey(did: string): string;
64
+
65
+ /**
66
+ * Fully verify a `did:key` body offline (ACDP 0.2). Returns a verdict.
67
+ */
68
+ export function verifyBodyOffline(body_json: string): string;
69
+
70
+ /**
71
+ * Recompute `content_hash` over the body and compare to `expectedHash`.
72
+ * Returns a verdict JSON string. Throws on malformed input.
73
+ */
74
+ export function verifyContentHash(body_json: string, expected_hash: string): string;
75
+
76
+ /**
77
+ * Verify one lifecycle event offline (RFC-ACDP-0013 §5).
78
+ * `actorDidDocJson` is `null`/`undefined` for a `did:key` actor.
79
+ */
80
+ export function verifyLifecycleEvent(event_json: string, actor_did_doc_json: string | null | undefined, expected_ctx_id: string): string;
81
+
82
+ /**
83
+ * Verify a lineage-head receipt offline (RFC-ACDP-0011 §7). Returns a
84
+ * verdict.
85
+ */
86
+ export function verifyLineageHeadReceipt(receipt_json: string, expected_json: string, registry_did_doc_json: string, now_rfc3339?: string | null, max_skew_secs?: bigint | null, max_age_secs?: bigint | null): string;
87
+
88
+ /**
89
+ * Verify a transparency-log checkpoint offline (RFC-ACDP-0012 §9.3).
90
+ */
91
+ export function verifyLogCheckpoint(checkpoint_json: string, registry_did_doc_json: string, expected_log_id?: string | null, now_rfc3339?: string | null, max_skew_secs?: bigint | null): string;
92
+
93
+ /**
94
+ * Verify a transparency-log consistency proof offline (RFC-ACDP-0012
95
+ * §9.2).
96
+ */
97
+ export function verifyLogConsistency(consistency_json: string, checkpoint_json: string, first_root_hash: string): string;
98
+
99
+ /**
100
+ * Verify a transparency-log inclusion proof offline (RFC-ACDP-0012
101
+ * §9.1).
102
+ */
103
+ export function verifyLogInclusion(inclusion_json: string, checkpoint_json: string, reconstructed_leaf_json: string): string;
104
+
105
+ /**
106
+ * Fully verify a `did:key` PublishRequest offline (ACDP 0.2). Returns a
107
+ * verdict.
108
+ */
109
+ export function verifyPublishRequestOffline(request_json: string): string;
110
+
111
+ /**
112
+ * Verify a registry receipt (RFC-ACDP-0010) against the consumer's own
113
+ * recomputed body hash / expected ctx_id / producer key fingerprint.
114
+ * Returns a verdict.
115
+ */
116
+ export function verifyReceipt(receipt_json: string, registry_public_key_b64: string, expected_ctx_id: string, recomputed_body_hash: string, producer_key_fingerprint: string): string;
117
+
118
+ /**
119
+ * Verify an Ed25519 signature over the ASCII `"sha256:<hex>"` string.
120
+ * `pubKeyB64` is the raw 32-byte key, base64. Returns a verdict.
121
+ */
122
+ export function verifySignatureEd25519(pub_key_b64: string, sig_b64: string, content_hash: string): string;
123
+
124
+ /**
125
+ * Verify an ECDSA-P256 signature (IEEE-1363 `r‖s`, base64) over the
126
+ * ASCII `"sha256:<hex>"` string. `pubKeySec1B64` is the 65-byte
127
+ * SEC1-uncompressed key, base64. Returns a verdict.
128
+ */
129
+ export function verifySignatureP256(pub_key_sec1_b64: string, sig_b64: string, content_hash: string): string;
130
+
131
+ /**
132
+ * Verify one witness cosignature offline (RFC-ACDP-0015 §8). Returns a
133
+ * verdict.
134
+ */
135
+ export function verifyWitnessCosignature(cosig_json: string, witness_did_doc_json: string, expected_checkpoint_json: string, now_rfc3339?: string | null, max_clock_skew_secs?: bigint | null): string;
136
+
137
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
138
+
139
+ export interface InitOutput {
140
+ readonly memory: WebAssembly.Memory;
141
+ readonly buildLogLeaf: (a: number, b: number, c: number) => void;
142
+ readonly buildWitnessCosignature: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
143
+ readonly canonicalPreimage: (a: number, b: number, c: number) => void;
144
+ readonly classifyUnderRevocation: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
145
+ readonly evaluateWitnessQuorum: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number) => void;
146
+ readonly explainHashMismatch: (a: number, b: number, c: number, d: number, e: number) => void;
147
+ readonly fingerprintEd25519: (a: number, b: number, c: number) => void;
148
+ readonly merkleLeafHash: (a: number, b: number, c: number) => void;
149
+ readonly merkleNodeHash: (a: number, b: number, c: number, d: number, e: number) => void;
150
+ readonly merkleRootHash: (a: number, b: number, c: number) => void;
151
+ readonly parseKeyRevocation: (a: number, b: number, c: number, d: number, e: number) => void;
152
+ readonly resolveDidKey: (a: number, b: number, c: number) => void;
153
+ readonly verifyBodyOffline: (a: number, b: number, c: number) => void;
154
+ readonly verifyContentHash: (a: number, b: number, c: number, d: number, e: number) => void;
155
+ readonly verifyLifecycleEvent: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
156
+ readonly verifyLineageHeadReceipt: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: bigint, l: number, m: bigint) => void;
157
+ readonly verifyLogCheckpoint: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: bigint) => void;
158
+ readonly verifyLogConsistency: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
159
+ readonly verifyLogInclusion: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
160
+ readonly verifyPublishRequestOffline: (a: number, b: number, c: number) => void;
161
+ readonly verifyReceipt: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => void;
162
+ readonly verifySignatureEd25519: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
163
+ readonly verifySignatureP256: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
164
+ readonly verifyWitnessCosignature: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: bigint) => void;
165
+ readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
166
+ readonly __wbindgen_export: (a: number, b: number) => number;
167
+ readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
168
+ readonly __wbindgen_export3: (a: number, b: number, c: number) => void;
169
+ }
170
+
171
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
172
+
173
+ /**
174
+ * Instantiates the given `module`, which can either be bytes or
175
+ * a precompiled `WebAssembly.Module`.
176
+ *
177
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
178
+ *
179
+ * @returns {InitOutput}
180
+ */
181
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
182
+
183
+ /**
184
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
185
+ * for everything else, calls `WebAssembly.instantiate` directly.
186
+ *
187
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
188
+ *
189
+ * @returns {Promise<InitOutput>}
190
+ */
191
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;