@real-life/trust-protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +99 -0
  2. package/dist/core.d.ts +32 -0
  3. package/dist/core.d.ts.map +1 -0
  4. package/dist/core.js +312 -0
  5. package/dist/core.js.map +1 -0
  6. package/dist/crypto.d.ts +32 -0
  7. package/dist/crypto.d.ts.map +1 -0
  8. package/dist/crypto.js +114 -0
  9. package/dist/crypto.js.map +1 -0
  10. package/dist/delivery.d.ts +51 -0
  11. package/dist/delivery.d.ts.map +1 -0
  12. package/dist/delivery.js +170 -0
  13. package/dist/delivery.js.map +1 -0
  14. package/dist/encounter.d.ts +63 -0
  15. package/dist/encounter.d.ts.map +1 -0
  16. package/dist/encounter.js +56 -0
  17. package/dist/encounter.js.map +1 -0
  18. package/dist/identity.d.ts +25 -0
  19. package/dist/identity.d.ts.map +1 -0
  20. package/dist/identity.js +87 -0
  21. package/dist/identity.js.map +1 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +29 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/probe/continuity.d.ts +99 -0
  27. package/dist/probe/continuity.d.ts.map +1 -0
  28. package/dist/probe/continuity.js +303 -0
  29. package/dist/probe/continuity.js.map +1 -0
  30. package/dist/probe/deps.d.ts +61 -0
  31. package/dist/probe/deps.d.ts.map +1 -0
  32. package/dist/probe/deps.js +54 -0
  33. package/dist/probe/deps.js.map +1 -0
  34. package/dist/probe/introduce.d.ts +70 -0
  35. package/dist/probe/introduce.d.ts.map +1 -0
  36. package/dist/probe/introduce.js +295 -0
  37. package/dist/probe/introduce.js.map +1 -0
  38. package/dist/probe/membership.d.ts +90 -0
  39. package/dist/probe/membership.d.ts.map +1 -0
  40. package/dist/probe/membership.js +450 -0
  41. package/dist/probe/membership.js.map +1 -0
  42. package/dist/probe/trust.d.ts +116 -0
  43. package/dist/probe/trust.d.ts.map +1 -0
  44. package/dist/probe/trust.js +345 -0
  45. package/dist/probe/trust.js.map +1 -0
  46. package/dist/probe.d.ts +8 -0
  47. package/dist/probe.d.ts.map +1 -0
  48. package/dist/probe.js +41 -0
  49. package/dist/probe.js.map +1 -0
  50. package/dist/schemas.d.ts +2 -0
  51. package/dist/schemas.d.ts.map +1 -0
  52. package/dist/schemas.js +51 -0
  53. package/dist/schemas.js.map +1 -0
  54. package/dist/unicode15.d.ts +8 -0
  55. package/dist/unicode15.d.ts.map +1 -0
  56. package/dist/unicode15.js +55 -0
  57. package/dist/unicode15.js.map +1 -0
  58. package/package.json +55 -0
package/dist/crypto.js ADDED
@@ -0,0 +1,114 @@
1
+ // crypto — the primitives, and nothing above them.
2
+ //
3
+ // Byte helpers, hashes, HKDF, key pairs (Ed25519 signing, X25519
4
+ // agreement), the did:key / multikey renderings, ECDH — plus the
5
+ // eddsa-jcs-2022 Data Integrity proof, which every layer signs with.
6
+ //
7
+ // This module answers "how", never "what": what a proof MEANS is decided
8
+ // by the layer that issues it. Runs on globalThis.crypto.subtle in
9
+ // browsers, Node >= 20, Deno and Bun alike.
10
+ //
11
+ // Determinism: every construction takes its entropy from the CALLER
12
+ // (nonces, ephemeral seeds). Random helpers exist, but nothing here
13
+ // calls them implicitly — that is what makes the whole stack replayable
14
+ // and vector-testable.
15
+ import { jcs, base58, fromBase58, b64uOf, fromB64u, calOK } from './core.js';
16
+ export { b64uOf, fromB64u, base58, fromBase58 };
17
+ const te = new TextEncoder();
18
+ const S = globalThis.crypto.subtle;
19
+ // ── bytes ───────────────────────────────────────────────────────────────
20
+ export const cat = (...bs) => {
21
+ const o = new Uint8Array(bs.reduce((n, b) => n + b.length, 0));
22
+ let i = 0;
23
+ for (const b of bs) {
24
+ o.set(b, i);
25
+ i += b.length;
26
+ }
27
+ return o;
28
+ };
29
+ export const rand = (n) => globalThis.crypto.getRandomValues(new Uint8Array(n));
30
+ // ── hashes, KDF, digests ────────────────────────────────────────────────
31
+ export const sha = async (bytes) => new Uint8Array(await S.digest('SHA-256', bytes));
32
+ export const hkdf = async (ikm, info, len = 32) => {
33
+ const k = await S.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
34
+ return new Uint8Array(await S.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: te.encode(info) }, k, len * 8));
35
+ };
36
+ // multibase multihash (u, sha2-256) over raw BYTES — pair labels digest the nonce bytes
37
+ export const digestBytes = async (bytes) => 'u' + b64uOf(cat(Uint8Array.from([0x12, 0x20]), await sha(bytes)));
38
+ // …and over the JCS of a document — the document digest of the stack
39
+ export const digestDoc = async (obj) => digestBytes(te.encode(jcs(obj)));
40
+ // ── keys (Ed25519 signing, X25519 agreement; did:key / multikey) ────────
41
+ const ED_PKCS8 = Uint8Array.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20]);
42
+ const X_PKCS8 = Uint8Array.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20]);
43
+ const jwkX = async (priv) => fromB64u((await S.exportKey('jwk', priv)).x);
44
+ export async function edFromSeed(seed) {
45
+ const priv = await S.importKey('pkcs8', cat(ED_PKCS8, seed), { name: 'Ed25519' }, true, ['sign']);
46
+ return { priv, pubRaw: await jwkX(priv) };
47
+ }
48
+ export async function xFromSeed(seed) {
49
+ const priv = await S.importKey('pkcs8', cat(X_PKCS8, seed), { name: 'X25519' }, true, ['deriveBits']);
50
+ return { priv, pubRaw: await jwkX(priv) };
51
+ }
52
+ export const anchorOfEd = (pubRaw) => 'did:key:z' + base58(cat(Uint8Array.from([0xed, 0x01]), pubRaw));
53
+ export const mkOfX = (pubRaw) => 'z' + base58(cat(Uint8Array.from([0xec, 0x01]), pubRaw));
54
+ export const edRawOfAnchor = (did) => {
55
+ if (typeof did !== 'string' || !did.startsWith('did:key:z'))
56
+ return null;
57
+ const b = fromBase58(did.slice('did:key:z'.length));
58
+ return (b && b.length === 34 && b[0] === 0xed && b[1] === 0x01) ? b.subarray(2) : null;
59
+ };
60
+ export const xRawOfMk = (mk) => {
61
+ if (typeof mk !== 'string' || mk[0] !== 'z')
62
+ return null;
63
+ const b = fromBase58(mk.slice(1));
64
+ return (b && b.length === 34 && b[0] === 0xec && b[1] === 0x01) ? b.subarray(2) : null;
65
+ };
66
+ export const ecdh = async (xPriv, theirPubRaw) => {
67
+ const pub = await S.importKey('raw', theirPubRaw, { name: 'X25519' }, false, []);
68
+ return new Uint8Array(await S.deriveBits({ name: 'X25519', public: pub }, xPriv, 256));
69
+ };
70
+ export async function diSign(ctx, doc, created) {
71
+ const cfg = { type: 'DataIntegrityProof', cryptosuite: 'eddsa-jcs-2022', created, verificationMethod: ctx.anchor + '#' + ctx.anchor.slice(8), proofPurpose: 'assertionMethod' };
72
+ if ('@context' in doc)
73
+ cfg['@context'] = doc['@context'];
74
+ const hashData = cat(await sha(te.encode(jcs(cfg))), await sha(te.encode(jcs(doc))));
75
+ const sig = new Uint8Array(await S.sign({ name: 'Ed25519' }, ctx.ed.priv, hashData));
76
+ return { ...doc, proof: { ...cfg, proofValue: 'z' + base58(sig) } };
77
+ }
78
+ export async function diVerify(doc, expectedAnchor) {
79
+ const { proof, ...rest } = doc ?? {};
80
+ if (typeof proof?.proofValue !== 'string' || typeof proof?.verificationMethod !== 'string')
81
+ return false;
82
+ // M-3 (review 1): this predicate verifies THE RLTP proof, not any
83
+ // signature that happens to be valid under the key — a config with a
84
+ // foreign type, suite, purpose or timestamp form is false here, whether
85
+ // or not a schema runs first.
86
+ if (proof.type !== 'DataIntegrityProof' || proof.cryptosuite !== 'eddsa-jcs-2022')
87
+ return false;
88
+ if (proof.proofPurpose !== 'assertionMethod')
89
+ return false;
90
+ if (typeof proof.created !== 'string' || !calOK(proof.created))
91
+ return false;
92
+ const vmDid = proof.verificationMethod.split('#')[0];
93
+ if (proof.verificationMethod !== vmDid + '#' + vmDid.slice(8))
94
+ return false;
95
+ if (expectedAnchor && vmDid !== expectedAnchor)
96
+ return false;
97
+ if ('@context' in rest && jcs(proof['@context'] ?? null) !== jcs(rest['@context']))
98
+ return false;
99
+ const raw = edRawOfAnchor(vmDid);
100
+ if (!raw)
101
+ return false;
102
+ const zsig = proof.proofValue;
103
+ if (typeof zsig !== 'string' || zsig[0] !== 'z')
104
+ return false;
105
+ const sig = fromBase58(zsig.slice(1));
106
+ // exactly 64 bytes, canonical base58btc — no left-pad repair (Encounter 2.3)
107
+ if (!sig || sig.length !== 64 || 'z' + base58(sig) !== zsig)
108
+ return false;
109
+ const { proofValue, ...cfg } = proof;
110
+ const hashData = cat(await sha(te.encode(jcs(cfg))), await sha(te.encode(jcs(rest))));
111
+ const pub = await S.importKey('raw', raw, { name: 'Ed25519' }, false, ['verify']);
112
+ return S.verify({ name: 'Ed25519' }, pub, sig, hashData);
113
+ }
114
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,iEAAiE;AACjE,iEAAiE;AACjE,qEAAqE;AACrE,EAAE;AACF,yEAAyE;AACzE,mEAAmE;AACnE,4CAA4C;AAC5C,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,wEAAwE;AACxE,uBAAuB;AACvB,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAC5E,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;AAE/C,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAA;AAC5B,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAA;AAElC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,EAAgB,EAAc,EAAE;IACrD,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9D,IAAI,CAAC,GAAG,CAAC,CAAC;IAAC,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;IAAC,CAAC;IAC7D,OAAO,CAAC,CAAA;AACV,CAAC,CAAA;AACD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAc,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;AAEnG,2EAA2E;AAC3E,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,EAAE,KAAiB,EAAuB,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAA;AACrH,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EAAE,GAAe,EAAE,IAAY,EAAE,GAAG,GAAG,EAAE,EAAuB,EAAE;IACzF,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,CAAC,CAAA;IACtE,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;AAC1I,CAAC,CAAA;AACD,wFAAwF;AACxF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,KAAiB,EAAmB,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAC3I,qEAAqE;AACrE,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,GAAY,EAAmB,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAElG,2EAA2E;AAC3E,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAClI,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AACjI,MAAM,IAAI,GAAG,KAAK,EAAE,IAAe,EAAuB,EAAE,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAE,CAAC,CAAA;AAI1G,MAAM,CAAC,KAAK,UAAU,UAAU,CAAE,IAAgB;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IACjG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAC3C,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAE,IAAgB;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAA;IACrG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAC3C,CAAC;AACD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAkB,EAAU,EAAE,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAC1H,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,MAAkB,EAAU,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAC7G,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAY,EAAqB,EAAE;IAC/D,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAA;IACxE,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACxF,CAAC,CAAA;AACD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,EAAW,EAAqB,EAAE;IACzD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IACxD,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IACjC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACxF,CAAC,CAAA;AACD,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EAAE,KAAgB,EAAE,WAAuB,EAAuB,EAAE;IAC3F,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IAChF,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAA;AACxF,CAAC,CAAA;AASD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAiC,GAAW,EAAE,GAAM,EAAE,OAAe;IAC/F,MAAM,GAAG,GAAwB,EAAE,IAAI,EAAE,oBAAoB,EAAE,WAAW,EAAE,gBAAgB,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACpM,IAAI,UAAU,IAAI,GAAG;QAAE,GAAG,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAA;IACxD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACpF,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;IACpF,OAAO,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAA;AACrE,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAE,GAAQ,EAAE,cAAuB;IAC/D,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,CAAA;IACpC,IAAI,OAAO,KAAK,EAAE,UAAU,KAAK,QAAQ,IAAI,OAAO,KAAK,EAAE,kBAAkB,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACxG,kEAAkE;IAClE,qEAAqE;IACrE,wEAAwE;IACxE,8BAA8B;IAC9B,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,IAAI,KAAK,CAAC,WAAW,KAAK,gBAAgB;QAAE,OAAO,KAAK,CAAA;IAC/F,IAAI,KAAK,CAAC,YAAY,KAAK,iBAAiB;QAAE,OAAO,KAAK,CAAA;IAC1D,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IAC5E,MAAM,KAAK,GAAG,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAW,CAAA;IAC9D,IAAI,KAAK,CAAC,kBAAkB,KAAK,KAAK,GAAG,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3E,IAAI,cAAc,IAAI,KAAK,KAAK,cAAc;QAAE,OAAO,KAAK,CAAA;IAC5D,IAAI,UAAU,IAAI,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAChG,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IACtB,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAA;IAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IACrC,6EAA6E;IAC7E,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IACzE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,EAAE,GAAG,KAAK,CAAA;IACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IACrF,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACjF,OAAO,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;AAC1D,CAAC","sourcesContent":["// crypto — the primitives, and nothing above them.\n//\n// Byte helpers, hashes, HKDF, key pairs (Ed25519 signing, X25519\n// agreement), the did:key / multikey renderings, ECDH — plus the\n// eddsa-jcs-2022 Data Integrity proof, which every layer signs with.\n//\n// This module answers \"how\", never \"what\": what a proof MEANS is decided\n// by the layer that issues it. Runs on globalThis.crypto.subtle in\n// browsers, Node >= 20, Deno and Bun alike.\n//\n// Determinism: every construction takes its entropy from the CALLER\n// (nonces, ephemeral seeds). Random helpers exist, but nothing here\n// calls them implicitly — that is what makes the whole stack replayable\n// and vector-testable.\nimport { jcs, base58, fromBase58, b64uOf, fromB64u, calOK } from './core.js'\nexport { b64uOf, fromB64u, base58, fromBase58 }\n\nconst te = new TextEncoder()\nconst S = globalThis.crypto.subtle\n\n// ── bytes ───────────────────────────────────────────────────────────────\nexport const cat = (...bs: Uint8Array[]): Uint8Array => {\n const o = new Uint8Array(bs.reduce((n, b) => n + b.length, 0))\n let i = 0; for (const b of bs) { o.set(b, i); i += b.length }\n return o\n}\nexport const rand = (n: number): Uint8Array => globalThis.crypto.getRandomValues(new Uint8Array(n))\n\n// ── hashes, KDF, digests ────────────────────────────────────────────────\nexport const sha = async (bytes: Uint8Array): Promise<Uint8Array> => new Uint8Array(await S.digest('SHA-256', bytes))\nexport const hkdf = async (ikm: Uint8Array, info: string, len = 32): Promise<Uint8Array> => {\n const k = await S.importKey('raw', ikm, 'HKDF', false, ['deriveBits'])\n return new Uint8Array(await S.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: te.encode(info) }, k, len * 8))\n}\n// multibase multihash (u, sha2-256) over raw BYTES — pair labels digest the nonce bytes\nexport const digestBytes = async (bytes: Uint8Array): Promise<string> => 'u' + b64uOf(cat(Uint8Array.from([0x12, 0x20]), await sha(bytes)))\n// …and over the JCS of a document — the document digest of the stack\nexport const digestDoc = async (obj: unknown): Promise<string> => digestBytes(te.encode(jcs(obj)))\n\n// ── keys (Ed25519 signing, X25519 agreement; did:key / multikey) ────────\nconst ED_PKCS8 = Uint8Array.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20])\nconst X_PKCS8 = Uint8Array.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20])\nconst jwkX = async (priv: CryptoKey): Promise<Uint8Array> => fromB64u((await S.exportKey('jwk', priv)).x!)\n/** A key pair as this library carries it: the private CryptoKey plus the raw public bytes. */\nexport interface KeyPair { priv: CryptoKey, pubRaw: Uint8Array }\n\nexport async function edFromSeed (seed: Uint8Array): Promise<KeyPair> {\n const priv = await S.importKey('pkcs8', cat(ED_PKCS8, seed), { name: 'Ed25519' }, true, ['sign'])\n return { priv, pubRaw: await jwkX(priv) }\n}\nexport async function xFromSeed (seed: Uint8Array): Promise<KeyPair> {\n const priv = await S.importKey('pkcs8', cat(X_PKCS8, seed), { name: 'X25519' }, true, ['deriveBits'])\n return { priv, pubRaw: await jwkX(priv) }\n}\nexport const anchorOfEd = (pubRaw: Uint8Array): string => 'did:key:z' + base58(cat(Uint8Array.from([0xed, 0x01]), pubRaw))\nexport const mkOfX = (pubRaw: Uint8Array): string => 'z' + base58(cat(Uint8Array.from([0xec, 0x01]), pubRaw))\nexport const edRawOfAnchor = (did: unknown): Uint8Array | null => {\n if (typeof did !== 'string' || !did.startsWith('did:key:z')) return null\n const b = fromBase58(did.slice('did:key:z'.length))\n return (b && b.length === 34 && b[0] === 0xed && b[1] === 0x01) ? b.subarray(2) : null\n}\nexport const xRawOfMk = (mk: unknown): Uint8Array | null => {\n if (typeof mk !== 'string' || mk[0] !== 'z') return null\n const b = fromBase58(mk.slice(1))\n return (b && b.length === 34 && b[0] === 0xec && b[1] === 0x01) ? b.subarray(2) : null\n}\nexport const ecdh = async (xPriv: CryptoKey, theirPubRaw: Uint8Array): Promise<Uint8Array> => {\n const pub = await S.importKey('raw', theirPubRaw, { name: 'X25519' }, false, [])\n return new Uint8Array(await S.deriveBits({ name: 'X25519', public: pub }, xPriv, 256))\n}\n\n// ── eddsa-jcs-2022 Data Integrity (W3C-true; Encounter §2.3) ──────────\n// hashData = SHA256(JCS(proofConfig incl. @context where the document has\n// one)) || SHA256(JCS(document without proof)); Ed25519 over hashData;\n// proofValue = z-base58btc of the exactly-64-byte signature.\n/** What a signer must offer: its anchor and its Ed25519 key. A full identity Context satisfies this. */\nexport interface Signer { anchor: string, ed: { priv: CryptoKey } }\n\nexport async function diSign<T extends Record<string, any>> (ctx: Signer, doc: T, created: string): Promise<T & { proof: Record<string, any> }> {\n const cfg: Record<string, any> = { type: 'DataIntegrityProof', cryptosuite: 'eddsa-jcs-2022', created, verificationMethod: ctx.anchor + '#' + ctx.anchor.slice(8), proofPurpose: 'assertionMethod' }\n if ('@context' in doc) cfg['@context'] = doc['@context']\n const hashData = cat(await sha(te.encode(jcs(cfg))), await sha(te.encode(jcs(doc))))\n const sig = new Uint8Array(await S.sign({ name: 'Ed25519' }, ctx.ed.priv, hashData))\n return { ...doc, proof: { ...cfg, proofValue: 'z' + base58(sig) } }\n}\nexport async function diVerify (doc: any, expectedAnchor?: string): Promise<boolean> {\n const { proof, ...rest } = doc ?? {}\n if (typeof proof?.proofValue !== 'string' || typeof proof?.verificationMethod !== 'string') return false\n // M-3 (review 1): this predicate verifies THE RLTP proof, not any\n // signature that happens to be valid under the key — a config with a\n // foreign type, suite, purpose or timestamp form is false here, whether\n // or not a schema runs first.\n if (proof.type !== 'DataIntegrityProof' || proof.cryptosuite !== 'eddsa-jcs-2022') return false\n if (proof.proofPurpose !== 'assertionMethod') return false\n if (typeof proof.created !== 'string' || !calOK(proof.created)) return false\n const vmDid = proof.verificationMethod.split('#')[0] as string\n if (proof.verificationMethod !== vmDid + '#' + vmDid.slice(8)) return false\n if (expectedAnchor && vmDid !== expectedAnchor) return false\n if ('@context' in rest && jcs(proof['@context'] ?? null) !== jcs(rest['@context'])) return false\n const raw = edRawOfAnchor(vmDid)\n if (!raw) return false\n const zsig = proof.proofValue\n if (typeof zsig !== 'string' || zsig[0] !== 'z') return false\n const sig = fromBase58(zsig.slice(1))\n // exactly 64 bytes, canonical base58btc — no left-pad repair (Encounter 2.3)\n if (!sig || sig.length !== 64 || 'z' + base58(sig) !== zsig) return false\n const { proofValue, ...cfg } = proof\n const hashData = cat(await sha(te.encode(jcs(cfg))), await sha(te.encode(jcs(rest))))\n const pub = await S.importKey('raw', raw, { name: 'Ed25519' }, false, ['verify'])\n return S.verify({ name: 'Ed25519' }, pub, sig, hashData)\n}\n\n"]}
@@ -0,0 +1,51 @@
1
+ import type { Json } from './core.js';
2
+ /** A sealed envelope as it travels: recipient key id, ephemeral public key, nonce, ciphertext. */
3
+ export interface Envelope {
4
+ rkid: string;
5
+ epk: string;
6
+ nonce: string;
7
+ ciphertext: string;
8
+ }
9
+ /** Caller-supplied entropy — the invariant that makes every construction replayable. */
10
+ export interface SealEntropy {
11
+ ephSeed?: Uint8Array;
12
+ nonce?: Uint8Array;
13
+ }
14
+ export declare function seal(document: Json, recipientKeyAgreement: string, { ephSeed, nonce }?: SealEntropy): Promise<Envelope>;
15
+ /** Opening an envelope never throws: every failure is a named reason. */
16
+ export type Unsealed = {
17
+ document: Json;
18
+ error?: undefined;
19
+ } | {
20
+ document?: undefined;
21
+ error: 'malformed' | 'oversize' | 'decryption-failed';
22
+ };
23
+ export declare function unseal(env: Envelope, xPriv: CryptoKey): Promise<Unsealed>;
24
+ export interface ReceiveStage {
25
+ stage: 1 | 2 | 3 | 4;
26
+ label: string;
27
+ ok: boolean;
28
+ }
29
+ export type Disposition = 'unique' | 'duplicate-known' | 'failed(oversize)' | 'failed(malformed)' | 'failed(decryption-failed)';
30
+ export interface Received {
31
+ stages: ReceiveStage[];
32
+ disposition: Disposition;
33
+ /** present when the chain passed stage 4 (also on duplicate-known) */
34
+ document?: Json;
35
+ digest?: string;
36
+ }
37
+ /**
38
+ * The generic stages every delivery passes before its layer sees it.
39
+ * World-neutral: keys come from the caller's resolver, the
40
+ * completed-effect cache is the caller's set of digests. This function
41
+ * only READS the cache — the caller adds a digest after (and only
42
+ * after) the type-specific effect completed.
43
+ *
44
+ * The resolver speaks three words (Delivery §4, stage 2/3): a CryptoKey
45
+ * means "known, here is the key"; null means "known, but the key is
46
+ * gone" (a tombstone — the envelope passes stage 2 and dies at stage 3
47
+ * as decryption-failed, indistinguishable from any other undecryptable
48
+ * envelope); undefined means "unknown rkid" and fails stage 2.
49
+ */
50
+ export declare function receive(env: unknown, keyForRkid: (rkid: string) => CryptoKey | null | undefined, completedEffects?: Set<string>): Promise<Received>;
51
+ //# sourceMappingURL=delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAsBrC,kGAAkG;AAClG,MAAM,WAAW,QAAQ;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;AAC1F,wFAAwF;AACxF,MAAM,WAAW,WAAW;IAAG,OAAO,CAAC,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,UAAU,CAAA;CAAE;AAEzE,wBAAsB,IAAI,CACxB,QAAQ,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,GAAE,WAAgB,GAClF,OAAO,CAAC,QAAQ,CAAC,CAcnB;AAED,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,mBAAmB,CAAA;CAAE,CAAA;AAE9I,wBAAsB,MAAM,CAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkBhF;AAGD,MAAM,WAAW,YAAY;IAAG,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAA;CAAE;AAClF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,mBAAmB,GAAG,2BAA2B,CAAA;AAC/H,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;IACxB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,IAAI,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,GAAG,SAAS,EAC1D,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAwCnB"}
@@ -0,0 +1,170 @@
1
+ // delivery — the Delivery Contract: how documents travel, and what a
2
+ // receiver must check before anything takes effect.
3
+ //
4
+ // The sealed envelope (X25519 ephemeral -> HKDF -> AES-256-GCM) and the
5
+ // generic receive chain in its declared ORDER: the size bound holds
6
+ // BEFORE decryption, the envelope form before that, and the digest is
7
+ // taken over the canonical form of the PARSED document — never over the
8
+ // raw bytes. Each stage names the disposition it produces, so a
9
+ // rejection can always say where it happened.
10
+ //
11
+ // What this module deliberately does NOT carry: the type-specific stages
12
+ // (proofs, bindings, effects) — those belong to the artifact's layer —
13
+ // and the completed-effect CACHE itself. The cache contract is the
14
+ // caller's to keep: only a COMPLETED effect's digest goes in (a
15
+ // rejection is not an effect and stays repeatable); a cached digest
16
+ // answers 'duplicate-known' forever after, and a second delivery never
17
+ // produces a second effect. receive() honors such a cache when given one.
18
+ import { jcs, b64uOf, fromB64u } from './core.js';
19
+ import { hkdf, xFromSeed, xRawOfMk, ecdh, digestDoc, rand } from './crypto.js';
20
+ const te = new TextEncoder();
21
+ const S = globalThis.crypto.subtle;
22
+ // canonical base64url or nothing — a second spelling of the same bytes
23
+ // is a different string, and dedup digests are computed over strings
24
+ const canonicalB64u = (s) => {
25
+ if (typeof s !== 'string')
26
+ return null;
27
+ let b;
28
+ try {
29
+ b = fromB64u(s);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ return b64uOf(b) === s ? b : null;
35
+ };
36
+ // ── sealed envelope (Delivery §5) ───────────────────────────────────────
37
+ // X25519 ephemeral → HKDF('rltp/v1/seal') → AES-256-GCM (12-byte nonce,
38
+ // 16-byte tag appended). Entropy is caller-suppliable for replay.
39
+ const aesKey = async (shared, usage) => {
40
+ if (shared.every((b) => b === 0))
41
+ throw new Error('all-zero shared secret');
42
+ return S.importKey('raw', await hkdf(shared, 'rltp/v1/seal'), { name: 'AES-GCM' }, false, [usage]);
43
+ };
44
+ export async function seal(document, recipientKeyAgreement, { ephSeed, nonce } = {}) {
45
+ // a producer API never emits what the contract rejects (B-3): the only
46
+ // envelope nonce is 96 bits, the only ephemeral seed 32 bytes
47
+ if (ephSeed !== undefined && !(ephSeed instanceof Uint8Array && ephSeed.length === 32))
48
+ throw new Error('ephSeed must be exactly 32 bytes');
49
+ if (nonce !== undefined && !(nonce instanceof Uint8Array && nonce.length === 12))
50
+ throw new Error('envelope nonce must be exactly 12 bytes (Delivery §5)');
51
+ const eph = await xFromSeed(ephSeed ?? rand(32));
52
+ const theirRaw = xRawOfMk(recipientKeyAgreement);
53
+ if (!theirRaw)
54
+ throw new Error('malformed recipient keyAgreement');
55
+ const key = await aesKey(await ecdh(eph.priv, theirRaw), 'encrypt');
56
+ const n = nonce ?? rand(12);
57
+ const plaintext = te.encode(jcs(document));
58
+ if (plaintext.length > 65536)
59
+ throw new Error('oversize');
60
+ const ct = new Uint8Array(await S.encrypt({ name: 'AES-GCM', iv: n }, key, plaintext));
61
+ return { rkid: recipientKeyAgreement, epk: b64uOf(eph.pubRaw), nonce: b64uOf(n), ciphertext: b64uOf(ct) };
62
+ }
63
+ export async function unseal(env, xPriv) {
64
+ // total over ARBITRARY input (B-4): a foreign envelope with invalid or
65
+ // non-canonical base64url is 'malformed', never an exception
66
+ const nonce = canonicalB64u(env?.nonce), epkRaw = canonicalB64u(env?.epk), raw = canonicalB64u(env?.ciphertext);
67
+ if (!nonce || !epkRaw || !raw)
68
+ return { error: 'malformed' };
69
+ if (nonce.length !== 12 || epkRaw.length !== 32 || raw.length < 16)
70
+ return { error: 'malformed' };
71
+ if (raw.length - 16 > 65536)
72
+ return { error: 'oversize' }; // size bound BEFORE decryption
73
+ let key;
74
+ try {
75
+ key = await aesKey(await ecdh(xPriv, epkRaw), 'decrypt');
76
+ }
77
+ catch {
78
+ return { error: 'decryption-failed' };
79
+ }
80
+ let bytes;
81
+ try {
82
+ bytes = await S.decrypt({ name: 'AES-GCM', iv: nonce }, key, raw);
83
+ }
84
+ catch {
85
+ return { error: 'decryption-failed' };
86
+ }
87
+ // from here the cryptography has SPOKEN — everything below is form:
88
+ // authenticated invalid UTF-8 and unparseable JSON are 'malformed'
89
+ // (stage 4 in the receive chain), never a crypto failure (review 3, M-2)
90
+ let plaintext;
91
+ try {
92
+ plaintext = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
93
+ }
94
+ catch {
95
+ return { error: 'malformed' };
96
+ }
97
+ try {
98
+ return { document: JSON.parse(plaintext) };
99
+ }
100
+ catch {
101
+ return { error: 'malformed' };
102
+ }
103
+ }
104
+ /**
105
+ * The generic stages every delivery passes before its layer sees it.
106
+ * World-neutral: keys come from the caller's resolver, the
107
+ * completed-effect cache is the caller's set of digests. This function
108
+ * only READS the cache — the caller adds a digest after (and only
109
+ * after) the type-specific effect completed.
110
+ *
111
+ * The resolver speaks three words (Delivery §4, stage 2/3): a CryptoKey
112
+ * means "known, here is the key"; null means "known, but the key is
113
+ * gone" (a tombstone — the envelope passes stage 2 and dies at stage 3
114
+ * as decryption-failed, indistinguishable from any other undecryptable
115
+ * envelope); undefined means "unknown rkid" and fails stage 2.
116
+ */
117
+ export async function receive(env, keyForRkid, completedEffects) {
118
+ const stages = [];
119
+ const step = (stage, label, ok) => { stages.push({ stage, label, ok }); return ok; };
120
+ const e = env;
121
+ // 1 — the size bound holds BEFORE any decoding or decryption work:
122
+ // estimated from the string alone, so an oversize envelope costs O(1)
123
+ const ctEst = typeof e?.ciphertext === 'string' ? Math.floor(e.ciphertext.length * 3 / 4) : 0;
124
+ if (!step(1, `size bound: ciphertext − tag ≤ 65536 B (~${Math.max(0, ctEst - 16)} B)`, ctEst - 16 <= 65536))
125
+ return { stages, disposition: 'failed(oversize)' };
126
+ // 2 — envelope form: the CLOSED shape — exactly the four fields, each
127
+ // canonical; the rkid a decodable multikey the resolver knows
128
+ const isObj = e !== null && typeof e === 'object' && !Array.isArray(e);
129
+ const ctBytes = isObj ? canonicalB64u(e.ciphertext) : null;
130
+ const closedForm = isObj && Object.keys(e).length === 4
131
+ && canonicalB64u(e.epk)?.length === 32 && canonicalB64u(e.nonce)?.length === 12
132
+ && ctBytes !== null && ctBytes.length >= 17 // tag + at least one byte (schema lower bound)
133
+ && typeof e.rkid === 'string' && xRawOfMk(e.rkid) !== null;
134
+ const key = closedForm ? keyForRkid(e.rkid) : undefined;
135
+ if (!step(2, 'envelope: closed form · base64url canonical · epk 32 B · nonce 12 B · rkid known', closedForm && key !== undefined))
136
+ return { stages, disposition: 'failed(malformed)' };
137
+ // 3 — decryption: X25519 + HKDF(rltp/v1/seal) + the AES-256-GCM tag.
138
+ // A tombstoned rkid (key === null) dies HERE, not at stage 2.
139
+ const opened = key === null ? { error: 'decryption-failed' } : await unseal(e, key);
140
+ // only CRYPTO failures belong to stage 3; an authenticated plaintext
141
+ // that defeats UTF-8 or JSON is a stage-4 malformed (review 3, M-2)
142
+ if (!step(3, 'decryption: X25519 + HKDF(rltp/v1/seal) + AES-256-GCM tag', opened.error !== 'decryption-failed' && opened.error !== 'oversize'))
143
+ return { stages, disposition: `failed(${opened.error})` };
144
+ if (opened.error === 'malformed') {
145
+ step(4, 'parse — invalid UTF-8 or not JSON', false);
146
+ return { stages, disposition: 'failed(malformed)' };
147
+ }
148
+ // 4 — parse: a JSON object; digest over its CANONICAL form; cache
149
+ // check. TOTAL: a document that parses but defeats the canonical
150
+ // form (JSON.parse turns 1e400 into Infinity) is malformed here,
151
+ // never an exception (review 2, B-2).
152
+ const doc = opened.document;
153
+ if (doc === null || doc === undefined || typeof doc !== 'object' || Array.isArray(doc)) {
154
+ step(4, 'parse — not a JSON object', false);
155
+ return { stages, disposition: 'failed(malformed)' };
156
+ }
157
+ let digest;
158
+ try {
159
+ digest = await digestDoc(doc);
160
+ }
161
+ catch {
162
+ step(4, 'parse — no canonical form (non-finite number)', false);
163
+ return { stages, disposition: 'failed(malformed)' };
164
+ }
165
+ step(4, `parse + digest ${digest.slice(0, 14)}… · completed-effect cache`, true);
166
+ if (completedEffects?.has(digest))
167
+ return { stages, disposition: 'duplicate-known', document: doc, digest };
168
+ return { stages, disposition: 'unique', document: doc, digest };
169
+ }
170
+ //# sourceMappingURL=delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.js","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"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","sourcesContent":["// delivery — the Delivery Contract: how documents travel, and what a\n// receiver must check before anything takes effect.\n//\n// The sealed envelope (X25519 ephemeral -> HKDF -> AES-256-GCM) and the\n// generic receive chain in its declared ORDER: the size bound holds\n// BEFORE decryption, the envelope form before that, and the digest is\n// taken over the canonical form of the PARSED document — never over the\n// raw bytes. Each stage names the disposition it produces, so a\n// rejection can always say where it happened.\n//\n// What this module deliberately does NOT carry: the type-specific stages\n// (proofs, bindings, effects) — those belong to the artifact's layer —\n// and the completed-effect CACHE itself. The cache contract is the\n// caller's to keep: only a COMPLETED effect's digest goes in (a\n// rejection is not an effect and stays repeatable); a cached digest\n// answers 'duplicate-known' forever after, and a second delivery never\n// produces a second effect. receive() honors such a cache when given one.\nimport { jcs, b64uOf, fromB64u } from './core.js'\nimport type { Json } from './core.js'\nimport { hkdf, xFromSeed, xRawOfMk, ecdh, digestDoc, rand } from './crypto.js'\n\nconst te = new TextEncoder()\nconst S = globalThis.crypto.subtle\n\n// canonical base64url or nothing — a second spelling of the same bytes\n// is a different string, and dedup digests are computed over strings\nconst canonicalB64u = (s: unknown): Uint8Array | null => {\n if (typeof s !== 'string') return null\n let b: Uint8Array\n try { b = fromB64u(s) } catch { return null }\n return b64uOf(b) === s ? b : null\n}\n\n// ── sealed envelope (Delivery §5) ───────────────────────────────────────\n// X25519 ephemeral → HKDF('rltp/v1/seal') → AES-256-GCM (12-byte nonce,\n// 16-byte tag appended). Entropy is caller-suppliable for replay.\nconst aesKey = async (shared: Uint8Array, usage: 'encrypt' | 'decrypt'): Promise<CryptoKey> => {\n if (shared.every((b) => b === 0)) throw new Error('all-zero shared secret')\n return S.importKey('raw', await hkdf(shared, 'rltp/v1/seal'), { name: 'AES-GCM' }, false, [usage])\n}\n/** A sealed envelope as it travels: recipient key id, ephemeral public key, nonce, ciphertext. */\nexport interface Envelope { rkid: string, epk: string, nonce: string, ciphertext: string }\n/** Caller-supplied entropy — the invariant that makes every construction replayable. */\nexport interface SealEntropy { ephSeed?: Uint8Array, nonce?: Uint8Array }\n\nexport async function seal (\n document: Json, recipientKeyAgreement: string, { ephSeed, nonce }: SealEntropy = {},\n): Promise<Envelope> {\n // a producer API never emits what the contract rejects (B-3): the only\n // envelope nonce is 96 bits, the only ephemeral seed 32 bytes\n if (ephSeed !== undefined && !(ephSeed instanceof Uint8Array && ephSeed.length === 32)) throw new Error('ephSeed must be exactly 32 bytes')\n if (nonce !== undefined && !(nonce instanceof Uint8Array && nonce.length === 12)) throw new Error('envelope nonce must be exactly 12 bytes (Delivery §5)')\n const eph = await xFromSeed(ephSeed ?? rand(32))\n const theirRaw = xRawOfMk(recipientKeyAgreement)\n if (!theirRaw) throw new Error('malformed recipient keyAgreement')\n const key = await aesKey(await ecdh(eph.priv, theirRaw), 'encrypt')\n const n = nonce ?? rand(12)\n const plaintext = te.encode(jcs(document))\n if (plaintext.length > 65536) throw new Error('oversize')\n const ct = new Uint8Array(await S.encrypt({ name: 'AES-GCM', iv: n }, key, plaintext))\n return { rkid: recipientKeyAgreement, epk: b64uOf(eph.pubRaw), nonce: b64uOf(n), ciphertext: b64uOf(ct) }\n}\n\n/** Opening an envelope never throws: every failure is a named reason. */\nexport type Unsealed = { document: Json, error?: undefined } | { document?: undefined, error: 'malformed' | 'oversize' | 'decryption-failed' }\n\nexport async function unseal (env: Envelope, xPriv: CryptoKey): Promise<Unsealed> {\n // total over ARBITRARY input (B-4): a foreign envelope with invalid or\n // non-canonical base64url is 'malformed', never an exception\n const nonce = canonicalB64u(env?.nonce), epkRaw = canonicalB64u(env?.epk), raw = canonicalB64u(env?.ciphertext)\n if (!nonce || !epkRaw || !raw) return { error: 'malformed' }\n if (nonce.length !== 12 || epkRaw.length !== 32 || raw.length < 16) return { error: 'malformed' }\n if (raw.length - 16 > 65536) return { error: 'oversize' } // size bound BEFORE decryption\n let key\n try { key = await aesKey(await ecdh(xPriv, epkRaw), 'decrypt') } catch { return { error: 'decryption-failed' } }\n let bytes\n try { bytes = await S.decrypt({ name: 'AES-GCM', iv: nonce }, key, raw) } catch { return { error: 'decryption-failed' } }\n // from here the cryptography has SPOKEN — everything below is form:\n // authenticated invalid UTF-8 and unparseable JSON are 'malformed'\n // (stage 4 in the receive chain), never a crypto failure (review 3, M-2)\n let plaintext\n try { plaintext = new TextDecoder('utf-8', { fatal: true }).decode(bytes) } catch { return { error: 'malformed' } }\n try { return { document: JSON.parse(plaintext) } }\n catch { return { error: 'malformed' } }\n}\n\n// ── the generic receive chain (Delivery §4: stages 1–4) ─────────────────\nexport interface ReceiveStage { stage: 1 | 2 | 3 | 4, label: string, ok: boolean }\nexport type Disposition = 'unique' | 'duplicate-known' | 'failed(oversize)' | 'failed(malformed)' | 'failed(decryption-failed)'\nexport interface Received {\n stages: ReceiveStage[]\n disposition: Disposition\n /** present when the chain passed stage 4 (also on duplicate-known) */\n document?: Json\n digest?: string\n}\n\n/**\n * The generic stages every delivery passes before its layer sees it.\n * World-neutral: keys come from the caller's resolver, the\n * completed-effect cache is the caller's set of digests. This function\n * only READS the cache — the caller adds a digest after (and only\n * after) the type-specific effect completed.\n *\n * The resolver speaks three words (Delivery §4, stage 2/3): a CryptoKey\n * means \"known, here is the key\"; null means \"known, but the key is\n * gone\" (a tombstone — the envelope passes stage 2 and dies at stage 3\n * as decryption-failed, indistinguishable from any other undecryptable\n * envelope); undefined means \"unknown rkid\" and fails stage 2.\n */\nexport async function receive (\n env: unknown,\n keyForRkid: (rkid: string) => CryptoKey | null | undefined,\n completedEffects?: Set<string>,\n): Promise<Received> {\n const stages: ReceiveStage[] = []\n const step = (stage: ReceiveStage['stage'], label: string, ok: boolean): boolean => { stages.push({ stage, label, ok }); return ok }\n const e = env as Partial<Envelope> | null | undefined\n\n // 1 — the size bound holds BEFORE any decoding or decryption work:\n // estimated from the string alone, so an oversize envelope costs O(1)\n const ctEst = typeof e?.ciphertext === 'string' ? Math.floor(e.ciphertext.length * 3 / 4) : 0\n if (!step(1, `size bound: ciphertext − tag ≤ 65536 B (~${Math.max(0, ctEst - 16)} B)`, ctEst - 16 <= 65536)) return { stages, disposition: 'failed(oversize)' }\n\n // 2 — envelope form: the CLOSED shape — exactly the four fields, each\n // canonical; the rkid a decodable multikey the resolver knows\n const isObj = e !== null && typeof e === 'object' && !Array.isArray(e)\n const ctBytes = isObj ? canonicalB64u((e as any).ciphertext) : null\n const closedForm = isObj && Object.keys(e).length === 4\n && canonicalB64u(e.epk)?.length === 32 && canonicalB64u(e.nonce)?.length === 12\n && ctBytes !== null && ctBytes.length >= 17 // tag + at least one byte (schema lower bound)\n && typeof e.rkid === 'string' && xRawOfMk(e.rkid) !== null\n const key = closedForm ? keyForRkid((e as Envelope).rkid) : undefined\n if (!step(2, 'envelope: closed form · base64url canonical · epk 32 B · nonce 12 B · rkid known', closedForm && key !== undefined)) return { stages, disposition: 'failed(malformed)' }\n\n // 3 — decryption: X25519 + HKDF(rltp/v1/seal) + the AES-256-GCM tag.\n // A tombstoned rkid (key === null) dies HERE, not at stage 2.\n const opened = key === null ? { error: 'decryption-failed' as const } : await unseal(e as Envelope, key as CryptoKey)\n // only CRYPTO failures belong to stage 3; an authenticated plaintext\n // that defeats UTF-8 or JSON is a stage-4 malformed (review 3, M-2)\n if (!step(3, 'decryption: X25519 + HKDF(rltp/v1/seal) + AES-256-GCM tag', opened.error !== 'decryption-failed' && opened.error !== 'oversize')) return { stages, disposition: `failed(${opened.error!})` as Disposition }\n if (opened.error === 'malformed') { step(4, 'parse — invalid UTF-8 or not JSON', false); return { stages, disposition: 'failed(malformed)' } }\n\n // 4 — parse: a JSON object; digest over its CANONICAL form; cache\n // check. TOTAL: a document that parses but defeats the canonical\n // form (JSON.parse turns 1e400 into Infinity) is malformed here,\n // never an exception (review 2, B-2).\n const doc = (opened as { document?: Json }).document\n if (doc === null || doc === undefined || typeof doc !== 'object' || Array.isArray(doc)) { step(4, 'parse — not a JSON object', false); return { stages, disposition: 'failed(malformed)' } }\n let digest: string\n try { digest = await digestDoc(doc) } catch { step(4, 'parse — no canonical form (non-finite number)', false); return { stages, disposition: 'failed(malformed)' } }\n step(4, `parse + digest ${digest.slice(0, 14)}… · completed-effect cache`, true)\n if (completedEffects?.has(digest)) return { stages, disposition: 'duplicate-known', document: doc, digest }\n return { stages, disposition: 'unique', document: doc, digest }\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import type { Signer } from './crypto.js';
2
+ import { iso } from './core.js';
3
+ export { iso };
4
+ export declare const CEREMONY = "encounter-scan@0.25";
5
+ export declare const CARD_VERSION = "rltp-card/0.25";
6
+ export declare const CRED_FORMAT = "rltp-encounter-credential/0.25";
7
+ export declare const binding: (ceremony: string, c1: string, c2: string) => Promise<string>;
8
+ export declare const challengeOf: (bytes17: Uint8Array) => string;
9
+ export declare const signCard: (ctx: Signer, body: CardBody, created: string) => Promise<CardBody & {
10
+ proof: Record<string, any>;
11
+ }>;
12
+ /** The challenge a displayed card carries: fresh value, whole-second issue time. */
13
+ export interface Challenge {
14
+ value: string;
15
+ issuedAt: string;
16
+ }
17
+ /**
18
+ * The spec knows exactly two disjoint card profiles (Encounter §5):
19
+ * a DISPLAYED card offers a challenge; a SENT card names its recipient
20
+ * AND the challenge it answers — always both, never one (M-2, review 1:
21
+ * the fields travel together or the card is neither profile).
22
+ */
23
+ export type CardFields = {
24
+ name?: string;
25
+ challenge?: Challenge;
26
+ sentTo?: undefined;
27
+ boundTo?: undefined;
28
+ } | {
29
+ name?: string;
30
+ challenge?: Challenge;
31
+ sentTo: string;
32
+ boundTo: string;
33
+ };
34
+ /** A card body as it goes under the signature. */
35
+ export interface CardBody {
36
+ version: string;
37
+ anchor: string;
38
+ keyAgreement: string;
39
+ name?: string;
40
+ challenge?: Challenge;
41
+ sentTo?: string;
42
+ boundTo?: string;
43
+ }
44
+ export declare function cardBody(ctx: {
45
+ anchor: string;
46
+ keyAgreement: string;
47
+ }, fields?: CardFields): CardBody;
48
+ export declare function issueCredential(ctx: Signer, subjectAnchor: string, ceremony: string, subjectChallenge: string, enactmentBinding: string, whenIso: string): Promise<{
49
+ '@context': string[];
50
+ type: string[];
51
+ issuer: string;
52
+ validFrom: string;
53
+ credentialSubject: {
54
+ id: string;
55
+ format: string;
56
+ ceremony: string;
57
+ challenge: string;
58
+ enactmentBinding: string;
59
+ };
60
+ } & {
61
+ proof: Record<string, any>;
62
+ }>;
63
+ //# sourceMappingURL=encounter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encounter.d.ts","sourceRoot":"","sources":["../src/encounter.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAU,GAAG,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,CAAA;AAGd,eAAO,MAAM,QAAQ,wBAAwB,CAAA;AAC7C,eAAO,MAAM,YAAY,mBAAmB,CAAA;AAC5C,eAAO,MAAM,WAAW,mCAAmC,CAAA;AAC3D,eAAO,MAAM,OAAO,GAAI,UAAU,MAAM,EAAE,IAAI,MAAM,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,MAAM,CAAyD,CAAA;AAC1I,eAAO,MAAM,WAAW,GAAI,SAAS,UAAU,KAAG,MAKjD,CAAA;AAID,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,MAAM,QAAQ,EAAE,SAAS,MAAM;;EAA+B,CAAA;AAEpG,oFAAoF;AACpF,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAE9D;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GACjF;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7E,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,QAAQ,CAAE,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,GAAE,UAAe,GAAG,QAAQ,CAU1G;AACD,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EACpD,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;GAUpE"}
@@ -0,0 +1,56 @@
1
+ // encounter — the Encounter Layer: how two people record that they met.
2
+ //
3
+ // The one registered ceremony (encounter-scan), the contact card each
4
+ // side displays, the challenge that makes an enactment fresh, the
5
+ // enactment binding both sides compute independently, and the encounter
6
+ // credential each issues about the other.
7
+ //
8
+ // The binding is what makes the pair an enactment rather than two
9
+ // unrelated claims: both sides derive it from the same two challenges,
10
+ // sorted, so neither can steer it.
11
+ import { digestDoc, diSign } from './crypto.js';
12
+ import { b64uOf, iso } from './core.js';
13
+ export { iso };
14
+ // ── wire builders (0.25 generation, DTG adoption) ───────────────────────
15
+ export const CEREMONY = 'encounter-scan@0.25';
16
+ export const CARD_VERSION = 'rltp-card/0.25';
17
+ export const CRED_FORMAT = 'rltp-encounter-credential/0.25';
18
+ export const binding = (ceremony, c1, c2) => digestDoc({ ceremony, challenges: [c1, c2].sort() });
19
+ export const challengeOf = (bytes17) => {
20
+ // a producer never emits what every conformant receiver must reject:
21
+ // the challenge is EXACTLY 17 random bytes (>= 128 bits, Encounter §5)
22
+ if (!(bytes17 instanceof Uint8Array) || bytes17.length !== 17)
23
+ throw new Error('challenge entropy must be exactly 17 bytes');
24
+ return challengeOfUnchecked(bytes17);
25
+ };
26
+ const challengeOfUnchecked = (bytes17) => b64uOf(bytes17).slice(0, 22);
27
+ // cards carry no @context (the proof carries none either — W3C-true)
28
+ export const signCard = (ctx, body, created) => diSign(ctx, body, created);
29
+ export function cardBody(ctx, fields = {}) {
30
+ const { name, challenge, sentTo, boundTo } = fields;
31
+ // runtime guard for JS callers the union cannot reach: the sent-card
32
+ // profile is atomic — half of it is not a card of either profile
33
+ if ((sentTo === undefined) !== (boundTo === undefined))
34
+ throw new Error('a sent card carries sentTo AND boundTo together (Encounter §5)');
35
+ const b = { version: CARD_VERSION, anchor: ctx.anchor, keyAgreement: ctx.keyAgreement };
36
+ if (name !== undefined)
37
+ b.name = name;
38
+ if (challenge !== undefined)
39
+ b.challenge = challenge;
40
+ if (sentTo !== undefined) {
41
+ b.sentTo = sentTo;
42
+ b.boundTo = boundTo;
43
+ }
44
+ return b;
45
+ }
46
+ export async function issueCredential(ctx, subjectAnchor, ceremony, subjectChallenge, enactmentBinding, whenIso) {
47
+ const body = {
48
+ '@context': ['https://www.w3.org/ns/credentials/v2', 'https://firstperson.network/credentials/dtg/v1', 'https://real-life.org/rltp/v1'],
49
+ type: ['VerifiableCredential', 'DTGCredential', 'RelationshipCredential', 'EncounterCredential'],
50
+ issuer: ctx.anchor,
51
+ validFrom: whenIso,
52
+ credentialSubject: { id: subjectAnchor, format: CRED_FORMAT, ceremony, challenge: subjectChallenge, enactmentBinding },
53
+ };
54
+ return diSign(ctx, body, whenIso);
55
+ }
56
+ //# sourceMappingURL=encounter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encounter.js","sourceRoot":"","sources":["../src/encounter.ts"],"names":[],"mappings":"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","sourcesContent":["// encounter — the Encounter Layer: how two people record that they met.\n//\n// The one registered ceremony (encounter-scan), the contact card each\n// side displays, the challenge that makes an enactment fresh, the\n// enactment binding both sides compute independently, and the encounter\n// credential each issues about the other.\n//\n// The binding is what makes the pair an enactment rather than two\n// unrelated claims: both sides derive it from the same two challenges,\n// sorted, so neither can steer it.\nimport { digestDoc, diSign } from './crypto.js'\nimport type { Signer } from './crypto.js'\nimport { b64uOf, iso } from './core.js'\nexport { iso }\n\n// ── wire builders (0.25 generation, DTG adoption) ───────────────────────\nexport const CEREMONY = 'encounter-scan@0.25'\nexport const CARD_VERSION = 'rltp-card/0.25'\nexport const CRED_FORMAT = 'rltp-encounter-credential/0.25'\nexport const binding = (ceremony: string, c1: string, c2: string): Promise<string> => digestDoc({ ceremony, challenges: [c1, c2].sort() })\nexport const challengeOf = (bytes17: Uint8Array): string => {\n // a producer never emits what every conformant receiver must reject:\n // the challenge is EXACTLY 17 random bytes (>= 128 bits, Encounter §5)\n if (!(bytes17 instanceof Uint8Array) || bytes17.length !== 17) throw new Error('challenge entropy must be exactly 17 bytes')\n return challengeOfUnchecked(bytes17)\n}\nconst challengeOfUnchecked = (bytes17: Uint8Array): string => b64uOf(bytes17).slice(0, 22)\n\n// cards carry no @context (the proof carries none either — W3C-true)\nexport const signCard = (ctx: Signer, body: CardBody, created: string) => diSign(ctx, body, created)\n\n/** The challenge a displayed card carries: fresh value, whole-second issue time. */\nexport interface Challenge { value: string, issuedAt: string }\n\n/**\n * The spec knows exactly two disjoint card profiles (Encounter §5):\n * a DISPLAYED card offers a challenge; a SENT card names its recipient\n * AND the challenge it answers — always both, never one (M-2, review 1:\n * the fields travel together or the card is neither profile).\n */\nexport type CardFields =\n | { name?: string, challenge?: Challenge, sentTo?: undefined, boundTo?: undefined }\n | { name?: string, challenge?: Challenge, sentTo: string, boundTo: string }\n\n/** A card body as it goes under the signature. */\nexport interface CardBody {\n version: string\n anchor: string\n keyAgreement: string\n name?: string\n challenge?: Challenge\n sentTo?: string\n boundTo?: string\n}\n\nexport function cardBody (ctx: { anchor: string, keyAgreement: string }, fields: CardFields = {}): CardBody {\n const { name, challenge, sentTo, boundTo } = fields as { name?: string, challenge?: Challenge, sentTo?: string, boundTo?: string }\n // runtime guard for JS callers the union cannot reach: the sent-card\n // profile is atomic — half of it is not a card of either profile\n if ((sentTo === undefined) !== (boundTo === undefined)) throw new Error('a sent card carries sentTo AND boundTo together (Encounter §5)')\n const b: CardBody = { version: CARD_VERSION, anchor: ctx.anchor, keyAgreement: ctx.keyAgreement }\n if (name !== undefined) b.name = name\n if (challenge !== undefined) b.challenge = challenge\n if (sentTo !== undefined) { b.sentTo = sentTo; b.boundTo = boundTo }\n return b\n}\nexport async function issueCredential (\n ctx: Signer, subjectAnchor: string, ceremony: string,\n subjectChallenge: string, enactmentBinding: string, whenIso: string,\n) {\n const body = {\n '@context': ['https://www.w3.org/ns/credentials/v2', 'https://firstperson.network/credentials/dtg/v1', 'https://real-life.org/rltp/v1'],\n type: ['VerifiableCredential', 'DTGCredential', 'RelationshipCredential', 'EncounterCredential'],\n issuer: ctx.anchor,\n validFrom: whenIso,\n credentialSubject: { id: subjectAnchor, format: CRED_FORMAT, ceremony, challenge: subjectChallenge, enactmentBinding },\n }\n return diSign(ctx, body, whenIso)\n}\n\n"]}
@@ -0,0 +1,25 @@
1
+ import type { KeyPair } from './crypto.js';
2
+ /**
3
+ * One derived identity context: its label, both key pairs, and the two
4
+ * public forms others see — the did:key anchor and the multikey for key
5
+ * agreement. Contexts never link to each other without a deliberate act.
6
+ */
7
+ export interface Context {
8
+ label: string;
9
+ ed: KeyPair;
10
+ x: KeyPair;
11
+ anchor: string;
12
+ keyAgreement: string;
13
+ }
14
+ /**
15
+ * The canonical form of a registry label, or null for everything the
16
+ * closed registry rejects. Personas come back NFC-normalized — for them
17
+ * the canonical label may differ from the input; label equality IS
18
+ * anchor identity, so derivation always uses the canonical form.
19
+ */
20
+ export declare function canonicalLabel(label: string): string | null;
21
+ export declare function labeledContext(rootIkm: Uint8Array, label: string): Promise<Context>;
22
+ export declare function pairContext(rootIkm: Uint8Array, nonce: Uint8Array): Promise<Context>;
23
+ /** The community anchor: an ordinary group context over the community's genesis digest. */
24
+ export declare const communityContext: (rootIkm: Uint8Array, genesisDigest: string) => Promise<Context>;
25
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAG1C;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,OAAO,CAAA;IACX,CAAC,EAAE,OAAO,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;CACrB;AAyBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,cAAc,CAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM1F;AAKD,wBAAsB,WAAW,CAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAG3F;AAED,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,GAAI,SAAS,UAAU,EAAE,eAAe,MAAM,KAAG,OAAO,CAAC,OAAO,CAC1C,CAAA"}