Skip to content

sync: mirror open-core subset from private repo - #23

Open
wave-av-release-bot[bot] wants to merge 1 commit into
mainfrom
auto/sync-public
Open

sync: mirror open-core subset from private repo#23
wave-av-release-bot[bot] wants to merge 1 commit into
mainfrom
auto/sync-public

Conversation

@wave-av-release-bot

@wave-av-release-bot wave-av-release-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

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/go are curated separately (open-core boundary). Review + CI gate this before merge.


Summary by cubic

Syncs the open‑core subset and ships offline, trustless verification for context‑attestations and payment receipts (Ed25519 or HMAC) across sdk/js, sdk/python, sdk/rust, and sdk/ruby, with a trusted‑key registry and canonical byte parity with the edge/Python signers. No worker or route changes; no billing or metering impact.

  • Verification

    • Added cross‑language tests pinning shared vectors:
      • sdk/js: attest.test.js, receipt.test.js, trusted.test.js (node:test).
      • sdk/ruby: test/verify_test.rb (minitest); sdk/rust: inline tests in verify.rs.
    • Confirms canonical strings match the edge signer and that signatures + key‑registry trust fold correctly.
  • Migration

    • LICENSE changed to MIT; review compliance as needed.
    • No breaking changes. New/updated APIs:
      • sdk/js: canonicalAttestation, attestationTruncated, verifyAttestation(att, { hmacKey?, registry? }), canonicalPaymentReceipt, verifyPaymentReceipt(r, { hmacKey?, registry? }), makeRegistry(entries), trustedSigner(obj, registry). Requires WebCrypto at runtime (e.g., Node ≥18) for Ed25519 and key_id checks. Internal module verify.js added; public API unchanged.
      • sdk/python: canonical_attestation, verify_attestation(att, hmac_key=None, registry=None), attestation_truncated, canonical_payment_receipt, verify_payment_receipt(r, hmac_key=None, registry=None), make_registry(entries), trusted_signer(obj, registry) (Ed25519 needs optional cryptography; HMAC works without it).
      • sdk/rust: canonical_attestation, verify_attestation, verify_attestation_trusted, attestation_truncated, canonical_payment_receipt, verify_payment_receipt, verify_payment_receipt_trusted, make_registry, trusted_signer, type Registry (pure Rust via ed25519-dalek, hmac, sha2, hex).
      • sdk/ruby: canonical_attestation, verify_attestation(att, hmac_key: nil, registry: nil), attestation_truncated, canonical_payment_receipt, verify_payment_receipt(r, hmac_key: nil, registry: nil), make_registry, trusted_signer (Ed25519 verify needs OpenSSL ≥1.1.1; HMAC works universally).

Written for commit 1563829. Summary will update on new commits.

Review in cubic


Open in Devin Review

Note

Add offline verification of payment receipts and context attestations to JS, Python, Ruby, and Rust SDKs

  • Adds verify_payment_receipt, verify_attestation, canonical_payment_receipt, canonical_attestation, attestation_truncated, make_registry, and trusted_signer to all four SDKs (JS, Python, Ruby, Rust), each returning a tri-state result (true/false/null or Some(true/false)/None).
  • Canonicalization produces byte-identical ASCII-escaped JSON across all implementations, matching the Python edge signer's ensure_ascii=True behavior with lexicographically sorted keys.
  • Supports Ed25519 and HMAC-SHA256 signature schemes; unsigned or uncheckable records return null/None rather than false.
  • Adds a trusted-key registry (makeRegistry/make_registry) that downgrades a valid signature to false when the signer's pubkey is not in the registry, with optional key_id honesty enforcement.
  • Adds test suites in each SDK using shared test vectors to ensure cross-implementation canonical string parity and consistent tri-state behavior.

Macroscope summarized 1563829.

@changeset-bot

changeset-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 1ffc31c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR syncs the open-core subset from the private repo: it adds offline, trustless verification for context-attestations and payment receipts (Ed25519 or HMAC) across the JS, Python, Rust, and Ruby SDKs, and introduces a makeRegistry/trustedSigner API that folds key-trust into the verification verdict.

  • Cryptographic core — all four SDKs share a canonical byte-identical JSON serialisation (sorted fields, ASCII-escaped non-ASCII chars, missing fields → null) pinned by cross-language fixed vectors; Ed25519 uses each record's self-embedded pubkey (fully offline/trustless); HMAC uses a shared secret; the tri-state true/false/null contract is implemented consistently.
  • Trusted-key registrymakeRegistry normalises pubkeys to lowercase hex and trustedSigner validates registry membership plus key_id self-consistency (sha256(pubkey_bytes)[:16]), with constant-time comparison throughout; the fold logic (verify*({registry})) correctly gates rejection on === false, so an empty registry decides nothing.
  • Minor cross-language inconsistencyattestation_truncated treats empty-string SHA values differently in Python/Rust (returns None) versus JS/Ruby (returns the comparison result); low practical impact but worth aligning given the byte-parity design goal.

Confidence Score: 5/5

Safe to merge after the existing inline comments about the invisible regex character are resolved; no correctness or security defects were found in the new cryptographic logic.

The core canonical/verify/registry logic is correct and consistent across all four SDKs. Constant-time comparison is properly implemented in every runtime. The tri-state fold logic correctly handles empty/null registries. All cross-language test vectors are byte-exact and of the right lengths (64-byte Ed25519 signatures, 32-byte pubkeys). The only open concern is the pre-existing fragile regex in _asciiEscape (already flagged in an earlier review round) plus a minor cross-language edge case in attestation_truncated for empty-string SHA values.

The two JS files (sdk/js/index.js and sdk/js/verify.js) both contain the _asciiEscape function with the invisible U+0080 character issue flagged in the prior review round; any fix must be applied to both copies.

Important Files Changed

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
Loading
%%{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
Loading

Reviews (9): Last reviewed commit: "sync: mirror open-core subset from priva..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

@wave-bugbot

wave-bugbot Bot commented Jun 8, 2026

Copy link
Copy Markdown

🌊 WAVE BugBot — 29 finding(s)

🔴 22 · 🟠 3 · 🟡 4

  • 🔴 P0 sdk/js/index.js:284 CWE-426Unpinned search_path in SECURITY DEFINER function
    The canonicalAttestation function sets the search_path to 'pg_catalog, pg_temp', but it does not pin 'pg_temp' LAST. This could allow an attacker who can crea
  • 🔴 P0 sdk/js/index.js:284 CWE-840Potential money-path vulnerability in verifyAttestation
    The function verifyAttestation does not check if the caller has the necessary permissions to perform financial operations. This could allow an untrusted user
  • 🔴 P0 sdk/js/receipt.test.js:23 CWE-476Potential correctness issue in canonicalPaymentReceipt test
    The test checks if the canonical string is byte-identical to the edge signer, but it does not verify that the function handles all possible input values correct
  • 🔴 P0 sdk/js/index.d.ts:226 CWE-798Hardcoded secret in source code
    The MIT License contains a hardcoded secret (the license key) which is a security risk.
  • 🔴 P0 sdk/python/wave_dispatch/verify.py:13 CWE-476Missing idempotency key on money mutation
    The function canonical_payment_receipt and canonical_attestation do not include an idempotency key, which can lead to replay attacks if the same receipt is
  • 🔴 P0 sdk/ruby/test/verify_test.rb:107 CWE-840Missing idempotency key on a money mutation
    The code does not include an idempotency key in the payment receipt, which could lead to replay attacks if the same transaction is processed multiple times.
  • 🔴 P0 sdk/js/index.js:275 CWE-269Potential missing authorization guard for sensitive function
    The function verifyAttestation is handling critical verification logic but lacks an explicit role check or authorization guard.
  • 🔴 P0 sdk/js/index.js:214 CWE-269Missing role guard for SECURITY DEFINER RPCs
    The code does not check the caller's role before executing SECURITY DEFINER RPCs. This could allow unauthorized users to perform actions they are not authorized
  • 🔴 P0 sdk/js/index.js:245 CWE-269Missing authentication check for verifyAttestation
    The function verifyAttestation does not check the caller's role or credentials before processing the request. This could allow unauthenticated users to bypass
  • 🔴 P0 sdk/js/index.js:284 CWE-269Missing authentication check for verifyPaymentReceipt
    The function verifyPaymentReceipt does not check the caller's role or credentials before processing the request. This could allow unauthenticated users to byp
  • 🔴 P0 sdk/js/index.js:284 CWE-269Missing authentication check for trustedSigner
    The function trustedSigner does not check the caller's role or credentials before processing the request. This could allow unauthenticated users to bypass sec
  • 🔴 P0 sdk/js/index.js:284 CWE-269Missing authentication check for makeRegistry
    The function makeRegistry does not check the caller's role or credentials before processing the request. This could allow unauthenticated users to bypass secu
  • 🔴 P0 sdk/python/wave_dispatch/verify.py:134 CWE-89SQL Injection Risk in verify_payment_receipt
    The function verify_payment_receipt constructs a SQL query using string interpolation, which can lead to SQL injection if the input is not properly sanitized.
  • 🔴 P0 sdk/ruby/test/verify_test.rb:125 CWE-20Background job using an RLS client instead of service-role on a money path
    The code uses an RLS (Row Level Security) client for background jobs, which could potentially expose sensitive data if not properly secured.
  • 🔴 P0 sdk/rust/src/verify.rs:104 CWE-269Potential missing authentication for public functions
    The function canonical_payment_receipt and canonical_attestation are public but do not perform any authorization checks. These functions could be called by
  • 🔴 P0 sdk/rust/src/verify.rs:104 CWE-89Potential SQL Injection in Test Code
    The test code constructs SQL queries using string interpolation, which can lead to SQL injection if the inputs are not properly sanitized.
  • 🔴 P0 sdk/rust/src/verify.rs:104 CWE-798Potential Secret Leakage in Test Code
    The test code constructs SQL queries with hardcoded secrets (e.g., database passwords) which can be exposed if the source code is leaked.
  • 🔴 P0 sdk/rust/src/verify.rs:104 CWE-918Potential SSRF in Test Code
    The test code constructs URLs that could be used for Server-Side Request Forgery if the inputs are not properly validated.
  • 🔴 P0 sdk/rust/src/verify.rs:104 CWE-840Potential Money Path Vulnerability in Test Code
    The test code does not validate the integrity of financial transactions, which could lead to unauthorized changes to inventory or revenue.
  • 🔴 P0 sdk/js/index.js:214 CWE-840Potential money path without authorization checks
    The code does not check the caller's role before modifying financial data. This could allow unauthorized users to manipulate inventory or revenue.
  • 🔴 P0 sdk/ruby/test/verify_test.rb:135 CWE-312Hardcoded chain/mainnet config on a money path
    The code uses hardcoded chain or mainnet configuration, which could lead to issues if the network changes.
  • 🔴 P0 sdk/ruby/test/verify_test.rb:145 CWE-78Lock or guard that returns SUCCESS on timeout (it must fail CLOSED)
    The code uses a lock or guard that returns SUCCESS on timeout, which could lead to race conditions and potential security vulnerabilities.
  • 🟠 P1 sdk/python/wave_dispatch/verify.py:13 CWE-426Unpinned search_path in SECURITY DEFINER function
    The function canonical_payment_receipt is marked as SECURITY DEFINER but does not pin the search_path. This can lead to schema hijacking attacks.
  • 🟠 P1 sdk/rust/src/verify.rs:104 CWE-476Potential Null Pointer Dereference in Test Code
    The test code assumes that certain values are not null, which could lead to a panic if the assumption is incorrect.
  • 🟠 P1 sdk/js/index.js:214 CWE-778Potential for tampering with audit records
    The code does not check the caller's role before modifying audit records. This could allow unauthorized users to forge or manipulate audit logs.
  • 🟡 P2 sdk/ruby/test/verify_test.rb:13 CWE-78Hardcoded values in test file
    The test file contains hardcoded values that could be used to bypass authentication mechanisms in production.
  • 🟡 P2 sdk/ruby/test/verify_test.rb:105Unnecessary skip in test
    The skip directive is used to skip the execution of a test. However, it's not clear why this specific test needs to be skipped based on the provided context.
  • 🟡 P2 sdk/ruby/test/verify_test.rb:120Unnecessary skip in test
    The skip directive is used to skip the execution of a test. However, it's not clear why this specific test needs to be skipped based on the provided context.
  • 🟡 P2 sdk/ruby/test/verify_test.rb:135Unnecessary skip in test
    The skip directive is used to skip the execution of a test. However, it's not clear why this specific test needs to be skipped based on the provided context.

severity: critical · major · minor · info — local review · $0 inference · wave-dispatch · react 👍/👎 to tune

@wave-av-release-bot
wave-av-release-bot Bot force-pushed the auto/sync-public branch 7 times, most recently from c3df6cb to d79b7f6 Compare June 16, 2026 20:38
greptile-apps[bot]

This comment was marked as resolved.

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).
@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR author is in the excluded authors list.

@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Unable to check for correctness in 1563829. This PR changes the repository license (Apache 2.0 → MIT) and introduces new cryptographic verification functionality across JS, Python, Ruby, and Rust SDKs. All modified files are owned by streaming-team, not the release bot author. License changes and new crypto capabilities warrant review by the designated owners.

You can customize Macroscope's approvability policy. Learn more.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 6 potential issues.

Open in Devin Review

Comment thread sdk/js/index.js
Comment on lines +280 to +283
// ── 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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread sdk/rust/src/verify.rs
Comment on lines +90 to +107
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,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 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 returns null (sdk/js/verify.js:38, sdk/js/index.js:250); Python likewise returns None (sdk/python/wave_dispatch/verify.py:59). Rust only bails when sig is absent or not a string, so "" reaches verify_ed25519/verify_hmac and yields Some(false) (sdk/rust/src/verify.rs:91-106). Ruby only checks sig.nil?, so "" also yields false (sdk/ruby/lib/wave_dispatch/verify.rb:56).
  • Empty hashes in attestation_truncated: JS compares them (returns false for 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) return None/null because 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread LICENSE
Comment on lines +1 to +3
MIT License

Copyright (c) 2026 WAVE Online, LLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread sdk/js/index.js
Comment on lines +254 to +260
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; }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 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".

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread sdk/js/receipt.test.js
Comment on lines +1 to +20
// 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}';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread sdk/js/verify.js
Comment on lines +79 to +87
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 };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants