sync: mirror open-core subset from private repo - #23
sync: mirror open-core subset from private repo#23wave-av-release-bot[bot] wants to merge 1 commit into
Conversation
|
|
| Filename | Overview |
|---|---|
| sdk/js/index.js | Adds context-attestation verifier (canonical, _attSigValid, verifyAttestation) with Ed25519 + HMAC tri-state; re-exports payment-receipt + registry from verify.js. Contains a duplicate _asciiEscape with an invisible U+0080 character in the regex (already flagged). |
| sdk/js/verify.js | New self-contained payment-receipt verifier + trusted-key registry for JS. Contains a second copy of _asciiEscape with the same invisible U+0080 regex issue as index.js; both copies must be patched together. |
| sdk/python/wave_dispatch/verify.py | New offline verifier for both receipt types in Python; stdlib-only HMAC path, lazy Ed25519 via cryptography; make_registry returns frozenset; consistent tri-state logic mirrors JS byte-for-byte. |
| sdk/rust/src/verify.rs | Adds canonical/verify/trusted-key registry in pure Rust (ed25519-dalek, hmac, sha2, hex); ascii_escape correctly handles astral chars via UTF-16 surrogate pairs; inline #[cfg(test)] vectors are consistent with JS/Python pins. |
| sdk/ruby/lib/wave_dispatch/verify.rb | New Ruby verifier using JSON.generate(ascii_only:true) for canonical form and OpenSSL for Ed25519/HMAC; explicit delegators avoid Ruby 2.6/2.7 keyword splat pitfalls; correctly gates OpenSSL::PKey.new_raw_public_key on >= 1.1.1. |
| sdk/js/attest.test.js | Cross-language test pinning canonical bytes, Ed25519 verification, HMAC tri-state, and truncation detection against the Python-signer fixed vector. |
| sdk/js/trusted.test.js | Comprehensive registry + trustedSigner + fold tests including key_id lie detection and WebCrypto-absent simulation; shared fixed vectors with the other SDK test suites. |
| sdk/ruby/test/verify_test.rb | Minitest suite with cross-language byte-parity assertions; Ed25519 cases skip gracefully when OpenSSL < 1.1.1; registry/trusted-signer fold tested end-to-end. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant SDK as SDK (JS/Python/Rust/Ruby)
participant Registry as TrustedRegistry
Note over SDK: Offline, no network required
Client->>SDK: "verify*(record, {hmacKey?, registry?})"
SDK->>SDK: "canonical*(record)"
Note over SDK: Sort fields, JSON.stringify,<br/>ASCII-escape non-ASCII
alt "alg = ed25519"
SDK->>SDK: importKey(pubkey)
SDK->>SDK: subtle.verify(sig, canonicalBytes)
SDK-->>Client: true / false
else "alg = hmac-sha256"
alt hmacKey provided
SDK->>SDK: HMAC-SHA256(key, canonicalBytes)
SDK->>SDK: timingSafeEqual(computed, sig)
SDK-->>Client: true / false
else no hmacKey
SDK-->>Client: null (uncheckable)
end
else no alg or none
SDK-->>Client: null (unsigned)
end
opt "registry provided AND sig=true"
SDK->>Registry: trustedSigner(record, registry)
Registry->>Registry: "sha256(pubkey_bytes)[:16] == key_id?"
Registry->>Registry: registry.has(pubkey)?
Registry-->>SDK: true / false / null
alt "trustedSigner = false"
SDK-->>Client: false (valid sig, untrusted key)
end
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant SDK as SDK (JS/Python/Rust/Ruby)
participant Registry as TrustedRegistry
Note over SDK: Offline, no network required
Client->>SDK: "verify*(record, {hmacKey?, registry?})"
SDK->>SDK: "canonical*(record)"
Note over SDK: Sort fields, JSON.stringify,<br/>ASCII-escape non-ASCII
alt "alg = ed25519"
SDK->>SDK: importKey(pubkey)
SDK->>SDK: subtle.verify(sig, canonicalBytes)
SDK-->>Client: true / false
else "alg = hmac-sha256"
alt hmacKey provided
SDK->>SDK: HMAC-SHA256(key, canonicalBytes)
SDK->>SDK: timingSafeEqual(computed, sig)
SDK-->>Client: true / false
else no hmacKey
SDK-->>Client: null (uncheckable)
end
else no alg or none
SDK-->>Client: null (unsigned)
end
opt "registry provided AND sig=true"
SDK->>Registry: trustedSigner(record, registry)
Registry->>Registry: "sha256(pubkey_bytes)[:16] == key_id?"
Registry->>Registry: registry.has(pubkey)?
Registry-->>SDK: true / false / null
alt "trustedSigner = false"
SDK-->>Client: false (valid sig, untrusted key)
end
end
Reviews (9): Last reviewed commit: "sync: mirror open-core subset from priva..." | Re-trigger Greptile
🌊 WAVE BugBot — 29 finding(s)🔴 22 · 🟠 3 · 🟡 4
severity: critical · major · minor · info — local review · $0 inference · wave-dispatch · react 👍/👎 to tune |
c3df6cb to
d79b7f6
Compare
d79b7f6 to
1ffc31c
Compare
1ffc31c to
1563829
Compare
|
PR author is in the excluded authors list. |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces new cryptographic and payment-receipt verification APIs across four SDKs and changes Rust HTTP retry behavior, so it is substantially more than a documentation or mechanical sync change. Unresolved cross-SDK compatibility, trusted-key validation, and licensing concerns require review by the relevant owners. Not approved because:
Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more. |
| // ── Payment-Receipt + trusted-key registry: both live in ./verify.js (self-contained, zero-dep) so this | ||
| // entrypoint stays under the 300-line gate; re-exported so the public API is unchanged. ──────────────── | ||
| import { trustedSigner } from "./verify.js"; // local use by the {registry} fold above (imports are hoisted) | ||
| export { canonicalPaymentReceipt, verifyPaymentReceipt, makeRegistry, trustedSigner } from "./verify.js"; |
There was a problem hiding this comment.
🟡 Changelog not updated for the newly added public verification APIs
New user-facing APIs are added to all four client libraries (export { canonicalPaymentReceipt, verifyPaymentReceipt, makeRegistry, trustedSigner } at sdk/js/index.js:283) without any matching entry in the project's change log, so users upgrading have no record of the new capabilities.
Impact: Consumers of the published packages cannot discover the new offline verification features from the release notes.
Repository rule: CHANGELOG "Unreleased" must be updated for user-facing changes
AGENTS.md states: "Conventional Commit titles; update CHANGELOG.md (Unreleased) for user-facing changes." This PR adds new exported APIs in sdk/js/index.js, sdk/js/verify.js, sdk/python/wave_dispatch/verify.py, sdk/ruby/lib/wave_dispatch/verify.rb, and sdk/rust/src/verify.rs, plus a license change in LICENSE — all user-facing. CHANGELOG.md still contains only an empty ## [Unreleased] section and is not touched by the commit.
Prompt for agents
AGENTS.md requires updating CHANGELOG.md's Unreleased section for user-facing changes. This PR adds new public APIs across four SDKs (canonicalAttestation/verifyAttestation/attestationTruncated/canonicalPaymentReceipt/verifyPaymentReceipt/makeRegistry/trustedSigner in JS, the equivalents in sdk/python/wave_dispatch/verify.py, sdk/ruby/lib/wave_dispatch/verify.rb, sdk/rust/src/verify.rs) and relicenses the project from Apache-2.0 to MIT, but CHANGELOG.md still has an empty Unreleased section. Add entries under Unreleased describing the new verification APIs per SDK and the license change. Note the repo's sync script (scripts/sync-public.sh) may need to be taught to carry changelog updates across from the private repo.
Was this helpful? React with 👍 or 👎 to provide feedback.
| fn verify_record(record: &Value, canonical_str: &str, hmac_key: Option<&str>) -> Option<bool> { | ||
| let alg = record.get("alg").and_then(Value::as_str)?; | ||
| let sig = record.get("sig").and_then(Value::as_str)?; | ||
| if alg.is_empty() || alg == "none" { | ||
| return None; | ||
| } | ||
| match alg { | ||
| "ed25519" => { | ||
| let pubkey = record.get("pubkey").and_then(Value::as_str)?; // None if no pubkey | ||
| Some(verify_ed25519(pubkey, sig, canonical_str.as_bytes())) | ||
| } | ||
| "hmac-sha256" => { | ||
| let key = hmac_key?; // None if the key was not supplied | ||
| Some(verify_hmac(key, sig, canonical_str.as_bytes())) | ||
| } | ||
| _ => None, | ||
| } | ||
| } |
There was a problem hiding this comment.
🔍 Tri-state contract diverges across SDKs for empty-string sig / hash fields
The four verifiers agree on the documented tri-state for missing fields, but not for present-but-empty strings, which weakens the "identical behaviour in every runtime" claim:
- Empty
sig(sig: ""): JS treats it as falsy and returnsnull(sdk/js/verify.js:38,sdk/js/index.js:250); Python likewise returnsNone(sdk/python/wave_dispatch/verify.py:59). Rust only bails whensigis absent or not a string, so""reachesverify_ed25519/verify_hmacand yieldsSome(false)(sdk/rust/src/verify.rs:91-106). Ruby only checkssig.nil?, so""also yieldsfalse(sdk/ruby/lib/wave_dispatch/verify.rb:56). - Empty hashes in
attestation_truncated: JS compares them (returnsfalsefor two empty strings) (sdk/js/index.js:236), Ruby likewise (sdk/ruby/lib/wave_dispatch/verify.rb:159-165), while Python (sdk/python/wave_dispatch/verify.py:137) and Rust (sdk/rust/src/verify.rs:166) returnNone/nullbecause they additionally test for emptiness.
None of these are exercised by the shared vectors, so the cross-language pins pass regardless. Worth aligning if the tri-state is meant to be a contract.
Was this helpful? React with 👍 or 👎 to provide feedback.
| MIT License | ||
|
|
||
| Copyright (c) 2026 WAVE Online, LLC |
There was a problem hiding this comment.
🔍 NOTICE file still asserts Apache-2.0 terms after the relicense to MIT
LICENSE is replaced with MIT ("Copyright (c) 2026 WAVE Online, LLC"), but the repo-root NOTICE is untouched and still reads "are NOT licensed under the Apache License, Version 2.0. The Apache License grants rights to the software in this repository only..." and is copyrighted to "WAVE, Inc." rather than "WAVE Online, LLC". NOTICE is an Apache-2.0 construct (§4d) with no meaning under MIT; leaving it creates a contradictory licensing statement for downstream consumers. The sync script that generated this PR should either drop NOTICE or rewrite it for MIT + trademark reservation.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (att.alg === "ed25519") { | ||
| if (!att.pubkey) return null; | ||
| try { | ||
| const pub = await subtle.importKey("raw", _fromHex(att.pubkey), "Ed25519", false, ["verify"]); | ||
| return await subtle.verify("Ed25519", pub, _fromHex(att.sig), msg); // self-describing: anyone verifies | ||
| } catch { return false; } | ||
| } |
There was a problem hiding this comment.
🔍 Ed25519 in WebCrypto is not universally available on the declared Node >= 18 floor
The JS verifier documents "needs Node >=18 or a modern browser" and package.json sets engines.node >= 18, but WebCrypto's "Ed25519" algorithm only landed in Node's crypto.subtle in 18.4.0 (and is still gated/absent in several browsers). On an 18.0–18.3 runtime, subtle.importKey("raw", ..., "Ed25519", ...) throws and is swallowed by the catch { return false; }, so a perfectly valid attestation/receipt is reported as a cryptographic failure rather than an environment error — the opposite of the "fail loud" posture applied to the missing-WebCrypto case at sdk/js/verify.js:40. Consider distinguishing "unsupported algorithm" from "bad signature".
Was this helpful? React with 👍 or 👎 to provide feedback.
| // Zero-dep tests (node:test) for the SDK's offline Payment-Receipt verifier — the MONEY half of "the two | ||
| // receipts". The decisive test is CROSS-IMPLEMENTATION: EXPECTED_CANONICAL is the SAME constant pinned in | ||
| // edge-router/payment-receipt.test.ts, so a green here proves the SDK and the edge signer produce byte-for- | ||
| // byte identical canonical strings — "sign on the edge, verify in any SDK, trust no one" for payments. | ||
| import { test } from "node:test"; | ||
| import assert from "node:assert/strict"; | ||
| import { canonicalPaymentReceipt, verifyPaymentReceipt } from "./index.js"; | ||
|
|
||
| // Pinned identically in edge-router/payment-receipt.test.ts (the byte-parity vector). | ||
| const FIXED = { | ||
| v: "wave.payment-receipt/v0", ts: 1700000000, protocol: "x402", mode: "wave-x402", | ||
| resource: "/extract", network: "base", asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", | ||
| amount_atomic: "1000", pay_to: "0x0000000000000000000000000000000000000001", | ||
| tx_hash: "0xdeadbeef", verified: true, | ||
| }; | ||
| const EXPECTED_CANONICAL = | ||
| '{"amount_atomic":"1000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",' + | ||
| '"mode":"wave-x402","network":"base","pay_to":"0x0000000000000000000000000000000000000001",' + | ||
| '"protocol":"x402","resource":"/extract","ts":1700000000,"tx_hash":"0xdeadbeef",' + | ||
| '"v":"wave.payment-receipt/v0","verified":true}'; |
There was a problem hiding this comment.
🔍 Cross-language byte-parity vectors are pinned against signers not present in this repo
The tests assert byte-for-byte parity with edge-router/context-attest.ts, edge-router/payment-receipt.ts, and edge-router/*.test.ts, none of which exist in this repository (edge-router/ contains only worker.ts and wrangler.example.toml). Only the private repo can actually run the parity check between SDK and edge signer; here the vectors are hard-coded constants that four independent SDKs are compared against. That still catches SDK-vs-SDK drift, but a change to the edge signer will not be caught by this repo's CI — worth confirming the private repo gates the same constants.
Was this helpful? React with 👍 or 👎 to provide feedback.
| export function makeRegistry(entries) { | ||
| const list = Array.isArray(entries) ? entries : (entries && Array.isArray(entries.keys) ? entries.keys : []); | ||
| const set = new Set(); | ||
| for (const e of list) { | ||
| const pub = typeof e === "string" ? e : (e && e.pubkey); | ||
| if (_isHex(pub) && pub.length) set.add(pub.toLowerCase()); | ||
| } | ||
| return { has: (p) => typeof p === "string" && set.has(p.toLowerCase()), size: set.size }; | ||
| } |
There was a problem hiding this comment.
🟨 Trusted-key registry accepts any even-length hex string, including keys that cannot be valid Ed25519 pubkeys
makeRegistry (and the Python/Ruby/Rust equivalents) only checks that an entry is a non-empty, even-length hex string before adding it to the trusted-signer set (sdk/js/verify.js:79-87). It never enforces the 32-byte (64 hex char) Ed25519 public-key length. A malformed or truncated /.well-known/wave-keys.json payload (e.g. a value clipped to "8a88") is silently accepted as a trusted key rather than being rejected, and makeRegistry reports a non-zero size, which flips trustedSigner/the {registry} fold from the safe "cannot decide" (null) state into an active allow/deny decision built from garbage data.
Was this helpful? React with 👍 or 👎 to provide feedback.
1563829 to
3b739bc
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
3b739bc to
72b3af8
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
72b3af8 to
49df507
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
49df507 to
679f877
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
679f877 to
23e2606
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
23e2606 to
364a998
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
Automated by scripts/sync-public.sh — README, SDK READMEs, threat-model, LICENSE, wrangler example, and the JS/Python/Rust/Ruby thin clients. worker.ts + sdk/go are curated separately (boundary).
364a998 to
f3e730c
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Automated by
scripts/sync-public.sh. Mirrors README, SDK READMEs, threat-model, LICENSE, the wrangler example, and the JS/Python/Rust/Ruby thin clients from the private repo.worker.ts+sdk/goare curated separately (open-core boundary). Review + CI gate this before merge.Summary by cubic
Mirrors the open-core subset (README, SDK READMEs, threat-model, LICENSE, wrangler example, and the JS/Python/Ruby/Rust thin clients) from the private repo and adds offline, trustless verification of context-attestations and payment receipts (Ed25519 or HMAC) across all four SDKs, plus a trusted-key registry and byte-identical canonicalization to the edge/Python signers.
worker.tsandsdk/goare curated separately. No worker or route changes; no billing or metering impact.Verification
npm testpath; missing WebCrypto surfaces explicit errors; Ruby skips Ed25519 cases when OpenSSL < 1.1.1.Migration
BENCHMARKS.mddrops the gross-margin claim.typescript5.9→7.0 insdk/js;ureq2→3,base640.22→0.23, andrand_core→getrandominsdk/rust.sdk/js, optionalcryptographyinsdk/python, OpenSSL ≥1.1.1 insdk/ruby;sdk/rustaddsed25519-dalek,hmac,sha2,hex.canonical_*,verify_*,attestation_truncated,make_registry,trusted_signer).Written for commit f3e730c. Summary will update on new commits.
Note
Add offline verification of payment receipts and context attestations to JS, Python, Ruby, and Rust SDKs
verify_payment_receipt,verify_attestation,canonical_payment_receipt,canonical_attestation,attestation_truncated,make_registry, andtrusted_signerto all four SDKs (JS, Python, Ruby, Rust), each returning a tri-state result (true/false/null or Some(true/false)/None).ensure_ascii=Truebehavior with lexicographically sorted keys.makeRegistry/make_registry) that downgrades a valid signature to false when the signer's pubkey is not in the registry, with optionalkey_idhonesty enforcement.Macroscope summarized 1563829.
Summary by Sourcery
Mirror the open-core SDK subset and add cross-language offline verification for payment receipts and context attestations.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests:
Chores: