feat(ans): implement @logicsrc/ans M1 — resolver + offline verifier

M1 of the ANS SDK (docs/ans-sdk.md):

- name: parse/format ans://v<semver>.<agent>.<domain>
- cbor: minimal RFC 8949 codec for the COSE_Sign1 subset
- verify/merkle: RFC 6962 leaf/node hashing, tree build, inclusion-proof
  generation + verification
- verify/es256 + cose: COSE_Sign1 build/parse, Sig_structure, ES256 (WebCrypto)
- verify/rootkeys: kid -> verifier key (JWKS entries; sumdb-note is M2)
- verify: verifyReceipt() + verifyResolution() (signature + inclusion proof +
  name binding), pure and offline
- client: AnsClient resolve/rootKeys/register/status over injectable fetch,
  with a DnsApplier hook for verify-dns

Tests (24) cover name parsing, CBOR round-trips/vectors, RFC 6962 proofs, a full
ES256+Merkle receipt round-trip with positive/negative cases, and the client
against mocked fetch. Wired into the root build script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-24 14:48:24 +00:00
parent 0ae24719e3
commit 29975b2df7
20 changed files with 1009 additions and 28 deletions

View file

@ -0,0 +1,15 @@
// ES256 (ECDSA P-256 / SHA-256) verification via WebCrypto. COSE signatures are
// raw IEEE-P1363 (r || s, 64 bytes), which is exactly what WebCrypto expects.
export function importEs256VerifyKey(jwk: JsonWebKey): Promise<CryptoKey> {
return crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
}
export function verifyEs256(key: CryptoKey, signature: Uint8Array, message: Uint8Array): Promise<boolean> {
return crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
key,
signature as unknown as ArrayBuffer,
message as unknown as ArrayBuffer,
);
}