mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
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>
34 lines
1.5 KiB
TypeScript
34 lines
1.5 KiB
TypeScript
import type { AnsName } from './types.js';
|
|
|
|
const LABEL = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i;
|
|
// ans://v<major.minor.patch[-prerelease]>.<agent>.<domain>
|
|
// The version's dotted core (X.Y.Z) is anchored so it doesn't get confused with
|
|
// the agent/domain labels. Prerelease is dot-free in M1 to keep the boundary
|
|
// unambiguous; build metadata (+...) is M2.
|
|
const ANS = /^ans:\/\/v(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+)?)\.([a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\.(.+)$/i;
|
|
|
|
/**
|
|
* Parse an `ans://v<semver>.<agent>.<domain>` name. The version is `v<semver>`,
|
|
* the agent is a single DNS label, and the domain is one or more labels after
|
|
* it. Throws on a malformed name.
|
|
*/
|
|
export function parseAnsName(raw: string): AnsName {
|
|
const match = ANS.exec(raw.trim());
|
|
if (!match) throw new Error(`malformed ans name — got ${raw}`);
|
|
const [, version, agent, domain] = match;
|
|
|
|
if (!LABEL.test(agent)) throw new Error(`ans name has invalid agent label — got ${agent}`);
|
|
if (!domain || domain.split('.').some((label) => !LABEL.test(label))) {
|
|
throw new Error(`ans name has invalid domain — got ${domain}`);
|
|
}
|
|
|
|
return { raw: `ans://v${version}.${agent}.${domain}`, version, agent, domain };
|
|
}
|
|
|
|
export function formatAnsName(parts: { version: string; agent: string; domain: string }): string {
|
|
return parseAnsName(`ans://v${parts.version}.${parts.agent}.${parts.domain}`).raw;
|
|
}
|
|
|
|
export function toAnsName(name: string | AnsName): AnsName {
|
|
return typeof name === 'string' ? parseAnsName(name) : name;
|
|
}
|