@interop/wallet-core 0.41.0 → 0.43.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 (121) hide show
  1. package/README.md +17 -4
  2. package/dist/clients/rosterPolicy.d.ts +28 -16
  3. package/dist/clients/rosterPolicy.d.ts.map +1 -1
  4. package/dist/clients/rosterPolicy.js +51 -26
  5. package/dist/clients/rosterPolicy.js.map +1 -1
  6. package/dist/descriptors/index.d.ts +1 -1
  7. package/dist/descriptors/index.d.ts.map +1 -1
  8. package/dist/descriptors/index.js +1 -1
  9. package/dist/descriptors/index.js.map +1 -1
  10. package/dist/descriptors/logSource.d.ts +58 -6
  11. package/dist/descriptors/logSource.d.ts.map +1 -1
  12. package/dist/descriptors/logSource.js +57 -15
  13. package/dist/descriptors/logSource.js.map +1 -1
  14. package/dist/enrollment/enrollment.d.ts.map +1 -1
  15. package/dist/enrollment/enrollment.js +14 -26
  16. package/dist/enrollment/enrollment.js.map +1 -1
  17. package/dist/keyring/fetch.d.ts +12 -0
  18. package/dist/keyring/fetch.d.ts.map +1 -1
  19. package/dist/keyring/fetch.js +0 -2
  20. package/dist/keyring/fetch.js.map +1 -1
  21. package/dist/keyring/index.d.ts +1 -1
  22. package/dist/keyring/index.d.ts.map +1 -1
  23. package/dist/keyring/index.js +1 -1
  24. package/dist/keyring/index.js.map +1 -1
  25. package/dist/keyring/kdf.d.ts +22 -6
  26. package/dist/keyring/kdf.d.ts.map +1 -1
  27. package/dist/keyring/kdf.js +9 -14
  28. package/dist/keyring/kdf.js.map +1 -1
  29. package/dist/keys/index.d.ts +1 -1
  30. package/dist/keys/index.d.ts.map +1 -1
  31. package/dist/keys/index.js +1 -1
  32. package/dist/keys/index.js.map +1 -1
  33. package/dist/keys/rosterLogStore.d.ts +4 -1
  34. package/dist/keys/rosterLogStore.d.ts.map +1 -1
  35. package/dist/keys/rosterLogStore.js +13 -16
  36. package/dist/keys/rosterLogStore.js.map +1 -1
  37. package/dist/keys/rosterStore.d.ts +13 -1
  38. package/dist/keys/rosterStore.d.ts.map +1 -1
  39. package/dist/keys/rosterStore.js +18 -0
  40. package/dist/keys/rosterStore.js.map +1 -1
  41. package/dist/keys/userKeyRoster.d.ts +17 -5
  42. package/dist/keys/userKeyRoster.d.ts.map +1 -1
  43. package/dist/keys/userKeyRoster.js +38 -13
  44. package/dist/keys/userKeyRoster.js.map +1 -1
  45. package/dist/recovery/index.d.ts +14 -12
  46. package/dist/recovery/index.d.ts.map +1 -1
  47. package/dist/recovery/index.js +13 -11
  48. package/dist/recovery/index.js.map +1 -1
  49. package/dist/recovery/recoveryCode.d.ts +5 -17
  50. package/dist/recovery/recoveryCode.d.ts.map +1 -1
  51. package/dist/recovery/recoveryCode.js +4 -14
  52. package/dist/recovery/recoveryCode.js.map +1 -1
  53. package/dist/recovery/recoveryDelegation.d.ts +50 -19
  54. package/dist/recovery/recoveryDelegation.d.ts.map +1 -1
  55. package/dist/recovery/recoveryDelegation.js +86 -41
  56. package/dist/recovery/recoveryDelegation.js.map +1 -1
  57. package/dist/recovery/recoveryWebvh.d.ts +13 -22
  58. package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
  59. package/dist/recovery/recoveryWebvh.js +37 -129
  60. package/dist/recovery/recoveryWebvh.js.map +1 -1
  61. package/dist/resourceLog/append.d.ts +12 -3
  62. package/dist/resourceLog/append.d.ts.map +1 -1
  63. package/dist/resourceLog/append.js +17 -10
  64. package/dist/resourceLog/append.js.map +1 -1
  65. package/dist/resourceLog/index.d.ts +1 -1
  66. package/dist/resourceLog/index.d.ts.map +1 -1
  67. package/dist/resourceLog/index.js +1 -1
  68. package/dist/resourceLog/index.js.map +1 -1
  69. package/dist/resourceLog/pin.d.ts +46 -8
  70. package/dist/resourceLog/pin.d.ts.map +1 -1
  71. package/dist/resourceLog/pin.js +32 -7
  72. package/dist/resourceLog/pin.js.map +1 -1
  73. package/dist/resourceLog/seal.d.ts +4 -1
  74. package/dist/resourceLog/seal.d.ts.map +1 -1
  75. package/dist/resourceLog/seal.js +6 -2
  76. package/dist/resourceLog/seal.js.map +1 -1
  77. package/dist/unlock/index.d.ts +40 -0
  78. package/dist/unlock/index.d.ts.map +1 -0
  79. package/dist/unlock/index.js +36 -0
  80. package/dist/unlock/index.js.map +1 -0
  81. package/dist/unlock/ladder.d.ts +100 -0
  82. package/dist/unlock/ladder.d.ts.map +1 -0
  83. package/dist/unlock/ladder.js +144 -0
  84. package/dist/unlock/ladder.js.map +1 -0
  85. package/dist/unlock/selfEnroll.d.ts +83 -0
  86. package/dist/unlock/selfEnroll.d.ts.map +1 -0
  87. package/dist/unlock/selfEnroll.js +126 -0
  88. package/dist/unlock/selfEnroll.js.map +1 -0
  89. package/dist/unlock/standingClient.d.ts +68 -0
  90. package/dist/unlock/standingClient.d.ts.map +1 -0
  91. package/dist/unlock/standingClient.js +81 -0
  92. package/dist/unlock/standingClient.js.map +1 -0
  93. package/dist/unlock/standingWebvh.d.ts +153 -0
  94. package/dist/unlock/standingWebvh.d.ts.map +1 -0
  95. package/dist/unlock/standingWebvh.js +370 -0
  96. package/dist/unlock/standingWebvh.js.map +1 -0
  97. package/dist/unlock/unlockRecord.d.ts +207 -0
  98. package/dist/unlock/unlockRecord.d.ts.map +1 -0
  99. package/dist/unlock/unlockRecord.js +435 -0
  100. package/dist/unlock/unlockRecord.js.map +1 -0
  101. package/dist/webvh/didWebvh.d.ts +94 -6
  102. package/dist/webvh/didWebvh.d.ts.map +1 -1
  103. package/dist/webvh/didWebvh.js +175 -19
  104. package/dist/webvh/didWebvh.js.map +1 -1
  105. package/dist/webvh/index.d.ts +7 -2
  106. package/dist/webvh/index.d.ts.map +1 -1
  107. package/dist/webvh/index.js +7 -2
  108. package/dist/webvh/index.js.map +1 -1
  109. package/dist/webvh/keyAgreement.d.ts +22 -6
  110. package/dist/webvh/keyAgreement.d.ts.map +1 -1
  111. package/dist/webvh/keyAgreement.js +1 -1
  112. package/dist/webvh/keyAgreement.js.map +1 -1
  113. package/dist/webvh/verifyLog.d.ts +15 -2
  114. package/dist/webvh/verifyLog.d.ts.map +1 -1
  115. package/dist/webvh/verifyLog.js +22 -3
  116. package/dist/webvh/verifyLog.js.map +1 -1
  117. package/package.json +11 -5
  118. package/dist/recovery/recoveryRecord.d.ts +0 -172
  119. package/dist/recovery/recoveryRecord.d.ts.map +0 -1
  120. package/dist/recovery/recoveryRecord.js +0 -288
  121. package/dist/recovery/recoveryRecord.js.map +0 -1
@@ -0,0 +1,144 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The update-key ladder: a standing unlock credential's latent-and-consumed
6
+ * did:webvh update authority. Rungs derive deterministically from a random
7
+ * 32-byte ladder seed carried inside the credential's unlock record -- never
8
+ * from the unlock secret itself, because a revealed rung lives verbatim in
9
+ * the world-readable `updateKeys` forever, where no hash commitment can
10
+ * protect it, so a secret-derived rung would be a standing offline grind
11
+ * oracle against the credential.
12
+ *
13
+ * Between uses only `hash(rung i)` stands in the document's `nextKeyHashes`;
14
+ * each self-enrollment is one loud reveal-and-commit entry signing with rung
15
+ * `i` and committing `hash(rung i + 1)`, after which the add entry retires
16
+ * the spent rung. The credential never holds a standing `updateKeys` member.
17
+ *
18
+ * There is no stored counter: which rung is current is recovered by
19
+ * re-deriving and scanning the published log's standing parameters
20
+ * ({@link attributeLadderRung}), and ambiguity fails closed rather than
21
+ * guessing -- the clients-listing attribution precedent. A lost
22
+ * compare-and-swap race resolves by determinism: the winner's entry commits
23
+ * `hash(rung i + 1)`, which IS the loser's retry key, so a re-run
24
+ * re-attributes and climbs one rung.
25
+ *
26
+ * The rung derivation is wire-level (both wallet apps must climb the same
27
+ * ladder from the same seed), so the salt and info labels are permanent.
28
+ */
29
+ import { deriveNextKeyHash } from '@interop/did-method-webvh';
30
+ import { hkdf } from '@noble/hashes/hkdf.js';
31
+ import { sha256 } from '@noble/hashes/sha2.js';
32
+ import { updateKeyMultibase } from '../webvh/didWebvh.js';
33
+ /**
34
+ * The byte length of a ladder seed: 32 random bytes, minted at bind time and
35
+ * carried only inside the unlock record's sealed ladder member.
36
+ */
37
+ export const LADDER_SEED_BYTES = 32;
38
+ /**
39
+ * The HKDF salt for rung derivation and the per-rung info prefix (the rung
40
+ * index in decimal follows it). Both permanent -- changing either orphans
41
+ * every bound credential's ladder.
42
+ */
43
+ const LADDER_SALT = 'freewallet/unlock/update-ladder/v1';
44
+ const LADDER_RUNG_INFO_PREFIX = 'rung/';
45
+ /**
46
+ * How many rungs {@link attributeLadderRung} derives before concluding the
47
+ * log commits none of them. Generous: one rung is consumed per
48
+ * self-enrollment, so a real ladder's standing commitment sits at the number
49
+ * of self-enrollments the credential has ever performed.
50
+ */
51
+ export const LADDER_MAX_SCAN = 128;
52
+ /**
53
+ * Thrown when the published log's standing parameters match no derivable rung
54
+ * -- the credential's posture was revoked (or never published), the ladder
55
+ * seed does not belong to this account, or the scan bound was exceeded -- or
56
+ * when they match more than one rung in the same role, which no legitimate
57
+ * history produces. Self-enrollment refuses loudly rather than guessing.
58
+ */
59
+ export class LadderAttributionError extends Error {
60
+ constructor(message) {
61
+ super(message);
62
+ this.name = 'LadderAttributionError';
63
+ }
64
+ }
65
+ /**
66
+ * Generates a fresh random ladder seed.
67
+ *
68
+ * @returns {Uint8Array}
69
+ */
70
+ export function generateLadderSeed() {
71
+ return crypto.getRandomValues(new Uint8Array(LADDER_SEED_BYTES));
72
+ }
73
+ /**
74
+ * Derives the 32-byte update-key seed of rung `index`.
75
+ *
76
+ * @param options {object}
77
+ * @param options.ladderSeed {Uint8Array}
78
+ * @param options.index {number} the rung index, from 0
79
+ * @returns {Uint8Array}
80
+ */
81
+ export function ladderRungSeed({ ladderSeed, index }) {
82
+ if (!Number.isInteger(index) || index < 0) {
83
+ throw new Error(`Invalid ladder rung index "${String(index)}".`);
84
+ }
85
+ return hkdf(sha256, ladderSeed, new TextEncoder().encode(LADDER_SALT), new TextEncoder().encode(`${LADDER_RUNG_INFO_PREFIX}${index}`), 32);
86
+ }
87
+ /**
88
+ * Derives rung `index` in full: seed and public multibase.
89
+ *
90
+ * @param options {object}
91
+ * @param options.ladderSeed {Uint8Array}
92
+ * @param options.index {number} the rung index, from 0
93
+ * @returns {Promise<LadderRung>}
94
+ */
95
+ export async function ladderRung({ ladderSeed, index }) {
96
+ const seed = ladderRungSeed({ ladderSeed, index });
97
+ return { index, seed, keyMultibase: await updateKeyMultibase({ seed }) };
98
+ }
99
+ /**
100
+ * Recovers the ladder's current rung from the published log's standing
101
+ * parameters -- the counter recovery that replaces any stored counter. Scans
102
+ * rungs `0..maxScan - 1`; a rung whose key stands in `updateKeys` is a torn
103
+ * self-enrollment to resume (`'revealed'`), else a rung whose hash stands in
104
+ * `nextKeyHashes` is the standing commitment (`'committed'`). Exactly one
105
+ * revealed rung, or exactly one committed rung beside it, is legitimate --
106
+ * the reveal entry keeps the spent rung's hash committed so a resumed run can
107
+ * re-state it, which is why a revealed rung wins over a committed one.
108
+ * Anything else fails closed with {@link LadderAttributionError}.
109
+ *
110
+ * @param options {object}
111
+ * @param options.ladderSeed {Uint8Array}
112
+ * @param options.published {object} the resolved log's standing parameters
113
+ * @param options.published.updateKeys {string[]}
114
+ * @param options.published.nextKeyHashes {string[]}
115
+ * @param [options.maxScan] {number} how many rungs to derive before giving
116
+ * up; defaults to {@link LADDER_MAX_SCAN}
117
+ * @returns {Promise<{ rung: LadderRung, state: LadderRungState }>}
118
+ */
119
+ export async function attributeLadderRung({ ladderSeed, published, maxScan = LADDER_MAX_SCAN }) {
120
+ const revealed = [];
121
+ const committed = [];
122
+ for (let index = 0; index < maxScan; index++) {
123
+ const rung = await ladderRung({ ladderSeed, index });
124
+ if (published.updateKeys.includes(rung.keyMultibase)) {
125
+ revealed.push(rung);
126
+ }
127
+ else if (published.nextKeyHashes.includes(await deriveNextKeyHash(rung.keyMultibase))) {
128
+ committed.push(rung);
129
+ }
130
+ }
131
+ if (revealed.length > 1 || committed.length > 1) {
132
+ throw new LadderAttributionError('The published log commits more than one rung of this ladder in the ' +
133
+ 'same role; refusing to self-enroll on an ambiguous attribution.');
134
+ }
135
+ if (revealed.length === 1) {
136
+ return { rung: revealed[0], state: 'revealed' };
137
+ }
138
+ if (committed.length === 1) {
139
+ return { rung: committed[0], state: 'committed' };
140
+ }
141
+ throw new LadderAttributionError('The published log commits no rung of this ladder; the credential has ' +
142
+ 'been revoked, was never published, or does not belong to this account.');
143
+ }
144
+ //# sourceMappingURL=ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ladder.js","sourceRoot":"","sources":["../../src/unlock/ladder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The self-enrollment completion core: what a fresh browser runs once its
6
+ * typed credential has located the account, proven the unlock record genuine,
7
+ * and handed over the bridge delegation and the ladder seed. One call turns
8
+ * the credential's latent authority into an ordinary enrolled client, with no
9
+ * second party involved:
10
+ *
11
+ * 1. Mint the new client's whole key set locally (client seed, did:webvh
12
+ * update-key seeds). Nothing is durable before the ceremony succeeds.
13
+ * 2. Write the self-enrolling continuation through the delegated `did.jsonl`
14
+ * bridge ({@link selfEnrollWebvhClient}): the reveal-and-commit entry
15
+ * signed by the attributed ladder rung, then the add entry that publishes
16
+ * the new client and retires the rung. Loud by construction -- the
17
+ * world-readable, hash-chained log extends before a single byte can be
18
+ * read.
19
+ * 3. Verify the account log locally and perform the first roster read,
20
+ * signed with the just-published `<did:webvh>#<multibase>` key and
21
+ * unwrapping the user key from the CREDENTIAL's standing wrap (escrowed
22
+ * into every epoch at bind time, kept alive by rotation fan-out).
23
+ * 4. Escrow the new client into the roster as its own recipient
24
+ * (`addUserKeyRosterRecipient`, epochs unwrapped with the credential's
25
+ * key-agreement key), so later logins on this browser read the roster the
26
+ * ordinary enrolled way.
27
+ *
28
+ * Persisting the key set under the app's own unlock layer is the caller's
29
+ * job, exactly as with the enrollment ceremony's completion: this module
30
+ * hands back the key set, the user key, and the roster epoch to pin, and
31
+ * stops there. Every stage is idempotent, so a torn run converges by
32
+ * re-running with the same credential (step 2 resumes from the standing
33
+ * commitments; a re-run after completion mints a fresh client, which is the
34
+ * ordinary next self-enrollment).
35
+ */
36
+ import type { IKeyAgreementKey } from '@interop/data-integrity-core';
37
+ import type { UserKey } from '../keys/userKey.js';
38
+ import type { AccountPointer } from '../keyring/record.js';
39
+ import { type ResourceLogPinStore } from '../resourceLog/index.js';
40
+ import type { ClientWebvhUpdateKeys } from '../webvh/didWebvh.js';
41
+ import type { UnlockLogStore } from './standingWebvh.js';
42
+ /**
43
+ * Runs the whole self-enrollment described in the module doc. The caller has
44
+ * already unwrapped the credential's unlock record (so it holds the
45
+ * credential-authenticated pointer, the bridge delegation behind `logStore`,
46
+ * and the ladder seed) and derived the credential's client identity (so it
47
+ * holds the key-agreement key with its secret half).
48
+ *
49
+ * @param options {object}
50
+ * @param options.pointer {AccountPointer} the credential-authenticated
51
+ * account pointer; must name a did:webvh
52
+ * @param options.ladderSeed {Uint8Array} the credential's update-key ladder
53
+ * seed, from its unlock record
54
+ * @param options.credentialKeyAgreementKey {IKeyAgreementKey} the
55
+ * credential's own key-agreement key (secret half included) -- its roster
56
+ * entry, which the user key is unwrapped from and every epoch re-wrapped
57
+ * with
58
+ * @param options.logStore {UnlockLogStore} the public log read plus the
59
+ * delegated `did.jsonl` PUT, built by the app around the record's bridge
60
+ * delegation
61
+ * @param [options.accountLogPinStore] {ResourceLogPinStore} this client's
62
+ * chain-head pin for the account log. A fresh browser normally has none
63
+ * (this is its first contact), which is exactly the pin's
64
+ * trust-on-first-use establishment
65
+ * @returns {Promise<object>} the new client's key set (for the caller to
66
+ * persist under its unlock layer), its did:key, the account DID, the user
67
+ * key, and the roster epoch to pin
68
+ */
69
+ export declare function selfEnrollClientCore({ pointer, ladderSeed, credentialKeyAgreementKey, logStore, accountLogPinStore }: {
70
+ pointer: AccountPointer;
71
+ ladderSeed: Uint8Array;
72
+ credentialKeyAgreementKey: IKeyAgreementKey;
73
+ logStore: UnlockLogStore;
74
+ accountLogPinStore?: ResourceLogPinStore;
75
+ }): Promise<{
76
+ clientSeed: Uint8Array;
77
+ webvhUpdateKeys: ClientWebvhUpdateKeys;
78
+ clientDid: string;
79
+ did: string;
80
+ userKey: UserKey;
81
+ latestEpochId: string;
82
+ }>;
83
+ //# sourceMappingURL=selfEnroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selfEnroll.d.ts","sourceRoot":"","sources":["../../src/unlock/selfEnroll.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AASpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAA;AAKhC,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,sBAAsB,CAAA;AAQ7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,OAAO,EACP,UAAU,EACV,yBAAyB,EACzB,QAAQ,EACR,kBAAkB,EACnB,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,UAAU,CAAA;IACtB,yBAAyB,EAAE,gBAAgB,CAAA;IAC3C,QAAQ,EAAE,cAAc,CAAA;IACxB,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;CACzC,GAAG,OAAO,CAAC;IACV,UAAU,EAAE,UAAU,CAAA;IACtB,eAAe,EAAE,qBAAqB,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;CACtB,CAAC,CAqGD"}
@@ -0,0 +1,126 @@
1
+ import { agentsFromSeed } from '../identity/agents.js';
2
+ import { addUserKeyRosterRecipient, readUserKeyRoster, rosterRecipientKid, userKeyRosterLogSigner } from '../keys/userKeyRoster.js';
3
+ import { userKeyRosterDescriptorStore } from '../keys/rosterStore.js';
4
+ import { memoryResourceLogPinStore, webvhResourceLogController } from '../resourceLog/index.js';
5
+ import { mintClientWebvhUpdateKeys, updateKeyMultibase } from '../webvh/didWebvh.js';
6
+ import { verifyAccountLog } from '../webvh/verifyLog.js';
7
+ import { clientSigningKeyMultibase, isWebvhDid, webvhZcapClient } from '../webvh/zcap.js';
8
+ import { selfEnrollWebvhClient } from './standingWebvh.js';
9
+ /**
10
+ * Runs the whole self-enrollment described in the module doc. The caller has
11
+ * already unwrapped the credential's unlock record (so it holds the
12
+ * credential-authenticated pointer, the bridge delegation behind `logStore`,
13
+ * and the ladder seed) and derived the credential's client identity (so it
14
+ * holds the key-agreement key with its secret half).
15
+ *
16
+ * @param options {object}
17
+ * @param options.pointer {AccountPointer} the credential-authenticated
18
+ * account pointer; must name a did:webvh
19
+ * @param options.ladderSeed {Uint8Array} the credential's update-key ladder
20
+ * seed, from its unlock record
21
+ * @param options.credentialKeyAgreementKey {IKeyAgreementKey} the
22
+ * credential's own key-agreement key (secret half included) -- its roster
23
+ * entry, which the user key is unwrapped from and every epoch re-wrapped
24
+ * with
25
+ * @param options.logStore {UnlockLogStore} the public log read plus the
26
+ * delegated `did.jsonl` PUT, built by the app around the record's bridge
27
+ * delegation
28
+ * @param [options.accountLogPinStore] {ResourceLogPinStore} this client's
29
+ * chain-head pin for the account log. A fresh browser normally has none
30
+ * (this is its first contact), which is exactly the pin's
31
+ * trust-on-first-use establishment
32
+ * @returns {Promise<object>} the new client's key set (for the caller to
33
+ * persist under its unlock layer), its did:key, the account DID, the user
34
+ * key, and the roster epoch to pin
35
+ */
36
+ export async function selfEnrollClientCore({ pointer, ladderSeed, credentialKeyAgreementKey, logStore, accountLogPinStore }) {
37
+ const expectedDid = pointer.did;
38
+ if (!expectedDid || !isWebvhDid(expectedDid)) {
39
+ throw new Error('The account pointer names no did:webvh; only a promoted account can ' +
40
+ 'self-enroll a client.');
41
+ }
42
+ // The new client's whole key set, minted locally; nothing travels but the
43
+ // public halves the log entries publish.
44
+ const clientSeed = crypto.getRandomValues(new Uint8Array(32));
45
+ const { keyAgent, keyAgreementKey } = await agentsFromSeed({
46
+ seed: clientSeed
47
+ });
48
+ const { publicKeyMultibase: keyAgreementKeyMultibase } = keyAgreementKey;
49
+ if (!keyAgreementKeyMultibase) {
50
+ throw new Error('The minted key-agreement key has no public multibase.');
51
+ }
52
+ const signingKeyMultibase = clientSigningKeyMultibase({ keyAgent });
53
+ const webvhUpdateKeys = await mintClientWebvhUpdateKeys();
54
+ const newClientKeys = {
55
+ signingKeyMultibase,
56
+ keyAgreementKeyMultibase,
57
+ updateKeyMultibase: await updateKeyMultibase({
58
+ seed: webvhUpdateKeys.updateSeed
59
+ }),
60
+ stagedUpdateKeyMultibase: await updateKeyMultibase({
61
+ seed: webvhUpdateKeys.stagedSeed
62
+ })
63
+ };
64
+ // The loud half: two log entries through the delegated bridge.
65
+ await selfEnrollWebvhClient({
66
+ store: logStore,
67
+ ladderSeed,
68
+ newClientKeys,
69
+ newClientUpdateSeeds: webvhUpdateKeys,
70
+ expectedDid
71
+ });
72
+ // Verify the continuation from the world-readable log -- the same
73
+ // first-contact read an enrollee's completion runs, and the controller the
74
+ // roster log's entry proofs are checked against.
75
+ const verified = await verifyAccountLog({
76
+ did: expectedDid,
77
+ spaceId: pointer.spaceId,
78
+ host: pointer.host,
79
+ ...(accountLogPinStore ? { pinStore: accountLogPinStore } : {})
80
+ });
81
+ // The first roster read: signed with the `<did:webvh>#<multibase>` keyId
82
+ // the add entry just published, unwrapping the user key from the
83
+ // CREDENTIAL's standing wrap. First contact: the chain-head pin this read
84
+ // establishes is session-local here; the app's durable pin is established
85
+ // by its own first login read.
86
+ const zcapClient = webvhZcapClient({ keyAgent, did: expectedDid });
87
+ const store = userKeyRosterDescriptorStore({
88
+ storageServerUrl: pointer.host,
89
+ zcapClient,
90
+ spaceId: pointer.spaceId,
91
+ resolveController: async () => webvhResourceLogController({ did: expectedDid, log: verified.log }),
92
+ pinStore: memoryResourceLogPinStore(),
93
+ signer: userKeyRosterLogSigner({ keyAgent })
94
+ });
95
+ const read = await readUserKeyRoster({
96
+ store,
97
+ clientKeyAgreementKey: credentialKeyAgreementKey
98
+ });
99
+ if (!read) {
100
+ throw new Error('The account has no user key roster; it must finish provisioning ' +
101
+ 'before a client can self-enroll.');
102
+ }
103
+ // Escrow the new client into the roster as its own recipient, so later
104
+ // logins on this browser read the roster the ordinary enrolled way.
105
+ // Idempotent: a wrap already standing is returned as-is.
106
+ await addUserKeyRosterRecipient({
107
+ store,
108
+ recipient: {
109
+ id: rosterRecipientKid({
110
+ signingKeyMultibase,
111
+ keyAgreementKeyMultibase
112
+ }),
113
+ publicKeyMultibase: keyAgreementKeyMultibase
114
+ },
115
+ ownerKeyAgreementKey: credentialKeyAgreementKey
116
+ });
117
+ return {
118
+ clientSeed,
119
+ webvhUpdateKeys,
120
+ clientDid: keyAgent.id,
121
+ did: expectedDid,
122
+ userKey: read.userKey,
123
+ latestEpochId: read.latestEpochId
124
+ };
125
+ }
126
+ //# sourceMappingURL=selfEnroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selfEnroll.js","sourceRoot":"","sources":["../../src/unlock/selfEnroll.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { ProfileAgents } from '../identity/agents.js';
2
+ /**
3
+ * The HKDF salt for a standing credential's client-side expansions, and the
4
+ * per-key info labels. All permanent. The salt differs from the recovery
5
+ * code's client salt (`freewallet/recovery/client-keys/v1`), so a code and a
6
+ * standing method that somehow shared input material could still never derive
7
+ * the same client identity.
8
+ */
9
+ export declare const STANDING_CLIENT_SALT = "freewallet/unlock/standing-client/v1";
10
+ /**
11
+ * A credential-derived client identity, assembled from its 32-byte client
12
+ * seed: the derived agents, the client did:key, the public multibases the
13
+ * document and roster carry, and the roster recipient kid. The shared shape
14
+ * of a standing credential's identity and a recovery code's (which extends it
15
+ * with the code's single update key).
16
+ */
17
+ export interface UnlockClientIdentity {
18
+ clientSeed: Uint8Array;
19
+ agents: ProfileAgents;
20
+ clientDid: string;
21
+ signingKeyMultibase: string;
22
+ keyAgreementKeyMultibase: string;
23
+ /**
24
+ * The kid of the credential's user-key-roster entry -- its key-agreement
25
+ * key's id exactly as `agentsFromSeed` derives it (`did:key:<ed>#<x>`), so
26
+ * the wrap minted at bind time is the one a fresh browser's roster read
27
+ * looks for.
28
+ */
29
+ recipientKid: string;
30
+ }
31
+ /**
32
+ * Assembles a client identity from its 32-byte client seed: the one place the
33
+ * agents, multibases, and roster kid are derived, shared by the standing
34
+ * credential derivation here and the recovery code's
35
+ * (`recoveryClientFromCode`), so the two postures can never disagree on how a
36
+ * seed becomes an identity.
37
+ *
38
+ * @param options {object}
39
+ * @param options.clientSeed {Uint8Array} the 32-byte client seed
40
+ * @returns {Promise<UnlockClientIdentity>}
41
+ */
42
+ export declare function unlockClientIdentityFromSeed({ clientSeed }: {
43
+ clientSeed: Uint8Array;
44
+ }): Promise<UnlockClientIdentity>;
45
+ /**
46
+ * A standing unlock credential's full client-side key set: the client
47
+ * identity plus the binding MAC key that authenticates the unlock record's
48
+ * account core (computed at bind time, verified before the pointer is
49
+ * trusted -- the storage host never holds it).
50
+ */
51
+ export interface StandingUnlockClient extends UnlockClientIdentity {
52
+ bindingMacKey: Uint8Array;
53
+ }
54
+ /**
55
+ * Derives a standing credential's client key set from the method's 32-byte
56
+ * unlock seed (the output of `deriveUnlockSeed` under the method's own KDF).
57
+ * Deterministic: the same secret under the same KDF always yields the same
58
+ * key set, which is what makes a fresh browser's self-enrollment possible
59
+ * with nothing but the credential in hand.
60
+ *
61
+ * @param options {object}
62
+ * @param options.unlockSeed {Uint8Array} the method's 32-byte unlock seed
63
+ * @returns {Promise<StandingUnlockClient>}
64
+ */
65
+ export declare function standingClientFromUnlockSeed({ unlockSeed }: {
66
+ unlockSeed: Uint8Array;
67
+ }): Promise<StandingUnlockClient>;
68
+ //# sourceMappingURL=standingClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standingClient.d.ts","sourceRoot":"","sources":["../../src/unlock/standingClient.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,yCAAyC,CAAA;AAI1E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,aAAa,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,wBAAwB,EAAE,MAAM,CAAA;IAChC;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAAC,EACjD,UAAU,EACX,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgBhC;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,aAAa,EAAE,UAAU,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,CAAC,EACjD,UAAU,EACX,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBhC"}
@@ -0,0 +1,81 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The standing unlock credential's client identity: the deterministic key set
6
+ * every unlock method (a passphrase, a passkey PRF output) derives from its
7
+ * own unlock seed under the standing-credential posture -- the recovery-code
8
+ * posture minus spend-on-use. The credential's key-agreement key holds a
9
+ * standing wrap in the user key roster (escrowed into every epoch, kept alive
10
+ * by rotation fan-out), and its binding MAC key authenticates the unlock
11
+ * record's account core, so a fresh browser holding nothing but the
12
+ * credential can locate the account, prove the record genuine, and decrypt.
13
+ * Update authority stays latent and rides the record instead: the ladder seed
14
+ * (`./ladder`) never derives from the secret.
15
+ *
16
+ * The derivations are wire-level: two wallet apps must produce byte-identical
17
+ * output for the same secret and KDF, so every salt and info label below is
18
+ * permanent. The HKDF input is the method's own 32-byte unlock seed
19
+ * (`deriveUnlockSeed`), so the expensive passphrase stretch runs once per
20
+ * typed secret and each unlock method's distinct KDF salt keeps two methods'
21
+ * client identities from ever colliding.
22
+ */
23
+ import { hkdf } from '@noble/hashes/hkdf.js';
24
+ import { sha256 } from '@noble/hashes/sha2.js';
25
+ import { agentsFromSeed } from '../identity/agents.js';
26
+ /**
27
+ * The HKDF salt for a standing credential's client-side expansions, and the
28
+ * per-key info labels. All permanent. The salt differs from the recovery
29
+ * code's client salt (`freewallet/recovery/client-keys/v1`), so a code and a
30
+ * standing method that somehow shared input material could still never derive
31
+ * the same client identity.
32
+ */
33
+ export const STANDING_CLIENT_SALT = 'freewallet/unlock/standing-client/v1';
34
+ const STANDING_CLIENT_SEED_INFO = 'client-seed';
35
+ const STANDING_BINDING_MAC_INFO = 'binding-mac';
36
+ /**
37
+ * Assembles a client identity from its 32-byte client seed: the one place the
38
+ * agents, multibases, and roster kid are derived, shared by the standing
39
+ * credential derivation here and the recovery code's
40
+ * (`recoveryClientFromCode`), so the two postures can never disagree on how a
41
+ * seed becomes an identity.
42
+ *
43
+ * @param options {object}
44
+ * @param options.clientSeed {Uint8Array} the 32-byte client seed
45
+ * @returns {Promise<UnlockClientIdentity>}
46
+ */
47
+ export async function unlockClientIdentityFromSeed({ clientSeed }) {
48
+ const agents = await agentsFromSeed({ seed: clientSeed });
49
+ const { publicKeyMultibase: keyAgreementKeyMultibase } = agents.keyAgreementKey;
50
+ if (!keyAgreementKeyMultibase) {
51
+ throw new Error('The derived key-agreement key has no public multibase.');
52
+ }
53
+ const [, , signingKeyMultibase] = agents.keyAgent.id.split(':');
54
+ return {
55
+ clientSeed,
56
+ agents,
57
+ clientDid: agents.keyAgent.id,
58
+ signingKeyMultibase: signingKeyMultibase,
59
+ keyAgreementKeyMultibase,
60
+ recipientKid: `${agents.keyAgent.id}#${keyAgreementKeyMultibase}`
61
+ };
62
+ }
63
+ /**
64
+ * Derives a standing credential's client key set from the method's 32-byte
65
+ * unlock seed (the output of `deriveUnlockSeed` under the method's own KDF).
66
+ * Deterministic: the same secret under the same KDF always yields the same
67
+ * key set, which is what makes a fresh browser's self-enrollment possible
68
+ * with nothing but the credential in hand.
69
+ *
70
+ * @param options {object}
71
+ * @param options.unlockSeed {Uint8Array} the method's 32-byte unlock seed
72
+ * @returns {Promise<StandingUnlockClient>}
73
+ */
74
+ export async function standingClientFromUnlockSeed({ unlockSeed }) {
75
+ const salt = new TextEncoder().encode(STANDING_CLIENT_SALT);
76
+ const clientSeed = hkdf(sha256, unlockSeed, salt, new TextEncoder().encode(STANDING_CLIENT_SEED_INFO), 32);
77
+ const bindingMacKey = hkdf(sha256, unlockSeed, salt, new TextEncoder().encode(STANDING_BINDING_MAC_INFO), 32);
78
+ const identity = await unlockClientIdentityFromSeed({ clientSeed });
79
+ return { ...identity, bindingMacKey };
80
+ }
81
+ //# sourceMappingURL=standingClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standingClient.js","sourceRoot":"","sources":["../../src/unlock/standingClient.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAGtD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,sCAAsC,CAAA;AAC1E,MAAM,yBAAyB,GAAG,aAAa,CAAA;AAC/C,MAAM,yBAAyB,GAAG,aAAa,CAAA;AAwB/C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EACjD,UAAU,EAGX;IACC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAA;IACzD,MAAM,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,GACpD,MAAM,CAAC,eAA6D,CAAA;IACtE,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;IAC3E,CAAC;IACD,MAAM,CAAC,EAAE,AAAD,EAAG,mBAAmB,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC/D,OAAO;QACL,UAAU;QACV,MAAM;QACN,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QAC7B,mBAAmB,EAAE,mBAAoB;QACzC,wBAAwB;QACxB,YAAY,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,wBAAwB,EAAE;KAClE,CAAA;AACH,CAAC;AAYD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EACjD,UAAU,EAGX;IACC,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;IAC3D,MAAM,UAAU,GAAG,IAAI,CACrB,MAAM,EACN,UAAU,EACV,IAAI,EACJ,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yBAAyB,CAAC,EACnD,EAAE,CACH,CAAA;IACD,MAAM,aAAa,GAAG,IAAI,CACxB,MAAM,EACN,UAAU,EACV,IAAI,EACJ,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yBAAyB,CAAC,EACnD,EAAE,CACH,CAAA;IACD,MAAM,QAAQ,GAAG,MAAM,4BAA4B,CAAC,EAAE,UAAU,EAAE,CAAC,CAAA;IACnE,OAAO,EAAE,GAAG,QAAQ,EAAE,aAAa,EAAE,CAAA;AACvC,CAAC"}