Skip to content

feat: signature verification, keyid resolution, and endorsements (JS/Go/PHP)#1

Merged
jt55401 merged 2 commits into
mainfrom
feat/protocol-conformance
May 13, 2026
Merged

feat: signature verification, keyid resolution, and endorsements (JS/Go/PHP)#1
jt55401 merged 2 commits into
mainfrom
feat/protocol-conformance

Conversation

@jt55401
Copy link
Copy Markdown
Contributor

@jt55401 jt55401 commented Apr 29, 2026

Summary

Brings the JS, Go, and PHP canonicalization bindings to parity with spec §2.1, §2.2, and §2.5. After this PR, the library is the cross-platform reference for clients performing local signature verification per spec §3.1, not just text canonicalization.

New surface (all three bindings)

  • buildSignatureBinding({contentHash, claimsHash, domain, signedAt}) — canonical {content-hash}:{claims-hash}:{domain}:{signed-at} per §2.1
  • verifySignature(message, signatureB64, publicKeyPem, algorithm) — ed25519, ecdsa (SHA-256), rsa (RSA-SHA256); padded or unpadded base64; case-insensitive algorithm
  • KeyResolver chain: didWebResolver, directUrlResolver, trustDirectoryResolver, with resolveKey() walking the chain. None is privileged per §2.2; callers compose them.
  • canonicalizeClaims for sorted name=value serialization
  • buildEndorsementBinding + verifyEndorsement for §2.5 endorsements

Cross-platform decisions

  • Endorsement algorithm precedence: resolver-declared algorithm wins over endorsement.algorithm. The key knows what it is; the field is a hint. Aligned across JS and Go (Go agent flagged it; JS updated to match).
  • JS environment detection: prefers SubtleCrypto in browsers, falls back to node:crypto.
  • Go: stdlib only (crypto/ed25519, crypto/ecdsa, crypto/rsa); no new module deps. RE2 has no backreferences, so excluded-elements regex is split into per-tag-name compiled REs (output equivalence verified by extraction tests).
  • PHP: sodium_crypto_sign_verify_detached for ed25519, openssl_verify for ecdsa/rsa. Resolvers accept an injected fetcher callable for testability.

Test plan

  • cd javascript && node test.js — 29/29 passing
  • cd go && go test ./... — 38/38 passing
  • cd php && composer install && vendor/bin/phpunitneeds verification; PHP not installed on the dev machine where this was written. Tests written carefully against PHP 7.2+ contract with PSR-4 + injected fetchers; please run locally and report any failures.
  • Cross-platform conformance vectors (JS/Go output byte-identity) — deferred to a follow-up; the new symbols are tested independently in each binding.

🤖 Generated with Claude Code

jt55401 and others added 2 commits April 10, 2026 22:21
Adds README scaffolds for two new language bindings of the HTMLTrust
canonicalization library. Both MUST produce byte-identical output to
the existing JS, Go, and PHP implementations for every test vector in
a shared conformance suite (TBD).

Python uses stdlib unicodedata plus beautifulsoup4/lxml for the
extract_canonical_text HTML parser.

Rust uses unicode-normalization plus scraper/html5ever.

Implementation to follow in separate commits; test vectors must be
centralized before implementation to enforce cross-language parity.

See TODO-Cleanup.md at the umbrella project root for implementation
task tracking.
Brings JS, Go, and PHP bindings to parity with spec §2.1, §2.2, and §2.5.
The canonicalization library is now the cross-platform reference for
clients performing local signature verification per spec §3.1, not just
text canonicalization.

New surface in all three bindings:
- buildSignatureBinding({contentHash, claimsHash, domain, signedAt})
  builds the canonical "{content-hash}:{claims-hash}:{domain}:{signed-at}"
  string per spec §2.1
- verifySignature(message, signatureB64, publicKeyPem, algorithm)
  supports ed25519, ecdsa (SHA-256), and rsa (RSA-SHA256); accepts both
  padded and unpadded base64; algorithm matching is case-insensitive
- KeyResolver chain with three implementations (didWebResolver,
  directUrlResolver, trustDirectoryResolver) and resolveKey() walking
  the chain. None is privileged per spec §2.2; callers compose them.
- canonicalizeClaims for sorted name=value serialization
- buildEndorsementBinding + verifyEndorsement for §2.5 endorsements as
  standalone signed JSON blobs

Cross-platform decisions:
- Endorsement signature algorithm: resolver-declared algorithm wins
  over the endorsement.algorithm field. The key knows what it is; the
  endorsement field is a hint. Aligned across JS and Go.
- JS library detects environment: prefers SubtleCrypto in browsers,
  falls back to node:crypto in Node.js
- Go uses crypto/ed25519, crypto/ecdsa, crypto/rsa from stdlib; no new
  module deps
- PHP uses sodium_crypto_sign_verify_detached for ed25519, openssl_verify
  for ecdsa/rsa; resolvers accept an injected fetcher callable for
  testability

Tests: JS 29/29, Go 38/38, PHP written but unverified locally
(PHP toolchain not installed on this dev machine).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jt55401 jt55401 merged commit 67e13aa into main May 13, 2026
3 checks passed
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.

1 participant