@bsv/wallet-toolbox 1.1.23 → 1.1.25

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 (201) hide show
  1. package/docs/client.md +2404 -870
  2. package/docs/setup.md +102 -134
  3. package/docs/wallet.md +2404 -870
  4. package/out/src/CWIStyleWalletManager.d.ts +411 -0
  5. package/out/src/CWIStyleWalletManager.d.ts.map +1 -0
  6. package/out/src/CWIStyleWalletManager.js +1131 -0
  7. package/out/src/CWIStyleWalletManager.js.map +1 -0
  8. package/out/src/Setup.d.ts +252 -8
  9. package/out/src/Setup.d.ts.map +1 -1
  10. package/out/src/Setup.js +299 -5
  11. package/out/src/Setup.js.map +1 -1
  12. package/out/src/SetupClient.d.ts +2 -16
  13. package/out/src/SetupClient.d.ts.map +1 -1
  14. package/out/src/SetupClient.js +8 -72
  15. package/out/src/SetupClient.js.map +1 -1
  16. package/out/src/SimpleWalletManager.d.ts +169 -0
  17. package/out/src/SimpleWalletManager.d.ts.map +1 -0
  18. package/out/src/SimpleWalletManager.js +315 -0
  19. package/out/src/SimpleWalletManager.js.map +1 -0
  20. package/out/src/Wallet.d.ts +6 -1
  21. package/out/src/Wallet.d.ts.map +1 -1
  22. package/out/src/Wallet.js +29 -2
  23. package/out/src/Wallet.js.map +1 -1
  24. package/out/src/WalletAuthenticationManager.d.ts +33 -0
  25. package/out/src/WalletAuthenticationManager.d.ts.map +1 -0
  26. package/out/src/WalletAuthenticationManager.js +107 -0
  27. package/out/src/WalletAuthenticationManager.js.map +1 -0
  28. package/out/src/WalletPermissionsManager.d.ts +575 -0
  29. package/out/src/WalletPermissionsManager.d.ts.map +1 -0
  30. package/out/src/WalletPermissionsManager.js +1807 -0
  31. package/out/src/WalletPermissionsManager.js.map +1 -0
  32. package/out/src/WalletSettingsManager.d.ts +59 -0
  33. package/out/src/WalletSettingsManager.d.ts.map +1 -0
  34. package/out/src/WalletSettingsManager.js +168 -0
  35. package/out/src/WalletSettingsManager.js.map +1 -0
  36. package/out/src/__tests/CWIStyleWalletManager.test.d.ts +2 -0
  37. package/out/src/__tests/CWIStyleWalletManager.test.d.ts.map +1 -0
  38. package/out/src/__tests/CWIStyleWalletManager.test.js +472 -0
  39. package/out/src/__tests/CWIStyleWalletManager.test.js.map +1 -0
  40. package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts +2 -0
  41. package/out/src/__tests/WalletPermissionsManager.callbacks.test.d.ts.map +1 -0
  42. package/out/src/__tests/WalletPermissionsManager.callbacks.test.js +239 -0
  43. package/out/src/__tests/WalletPermissionsManager.callbacks.test.js.map +1 -0
  44. package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts +2 -0
  45. package/out/src/__tests/WalletPermissionsManager.checks.test.d.ts.map +1 -0
  46. package/out/src/__tests/WalletPermissionsManager.checks.test.js +644 -0
  47. package/out/src/__tests/WalletPermissionsManager.checks.test.js.map +1 -0
  48. package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts +2 -0
  49. package/out/src/__tests/WalletPermissionsManager.encryption.test.d.ts.map +1 -0
  50. package/out/src/__tests/WalletPermissionsManager.encryption.test.js +295 -0
  51. package/out/src/__tests/WalletPermissionsManager.encryption.test.js.map +1 -0
  52. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts +82 -0
  53. package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -0
  54. package/out/src/__tests/WalletPermissionsManager.fixtures.js +260 -0
  55. package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -0
  56. package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts +2 -0
  57. package/out/src/__tests/WalletPermissionsManager.flows.test.d.ts.map +1 -0
  58. package/out/src/__tests/WalletPermissionsManager.flows.test.js +389 -0
  59. package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -0
  60. package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts +2 -0
  61. package/out/src/__tests/WalletPermissionsManager.initialization.test.d.ts.map +1 -0
  62. package/out/src/__tests/WalletPermissionsManager.initialization.test.js +227 -0
  63. package/out/src/__tests/WalletPermissionsManager.initialization.test.js.map +1 -0
  64. package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts +2 -0
  65. package/out/src/__tests/WalletPermissionsManager.proxying.test.d.ts.map +1 -0
  66. package/out/src/__tests/WalletPermissionsManager.proxying.test.js +566 -0
  67. package/out/src/__tests/WalletPermissionsManager.proxying.test.js.map +1 -0
  68. package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts +2 -0
  69. package/out/src/__tests/WalletPermissionsManager.tokens.test.d.ts.map +1 -0
  70. package/out/src/__tests/WalletPermissionsManager.tokens.test.js +460 -0
  71. package/out/src/__tests/WalletPermissionsManager.tokens.test.js.map +1 -0
  72. package/out/src/index.all.d.ts +9 -1
  73. package/out/src/index.all.d.ts.map +1 -1
  74. package/out/src/index.all.js +10 -3
  75. package/out/src/index.all.js.map +1 -1
  76. package/out/src/index.client.d.ts +9 -1
  77. package/out/src/index.client.d.ts.map +1 -1
  78. package/out/src/index.client.js +10 -3
  79. package/out/src/index.client.js.map +1 -1
  80. package/out/src/utility/identityUtils.d.ts +31 -0
  81. package/out/src/utility/identityUtils.d.ts.map +1 -0
  82. package/out/src/utility/identityUtils.js +114 -0
  83. package/out/src/utility/identityUtils.js.map +1 -0
  84. package/out/src/wab-client/WABClient.d.ts +38 -0
  85. package/out/src/wab-client/WABClient.d.ts.map +1 -0
  86. package/out/src/wab-client/WABClient.js +95 -0
  87. package/out/src/wab-client/WABClient.js.map +1 -0
  88. package/out/src/wab-client/__tests/WABClient.test.d.ts +2 -0
  89. package/out/src/wab-client/__tests/WABClient.test.d.ts.map +1 -0
  90. package/out/src/wab-client/__tests/WABClient.test.js +47 -0
  91. package/out/src/wab-client/__tests/WABClient.test.js.map +1 -0
  92. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts +34 -0
  93. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.d.ts.map +1 -0
  94. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js +16 -0
  95. package/out/src/wab-client/auth-method-interactors/AuthMethodInteractor.js.map +1 -0
  96. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts +7 -0
  97. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.d.ts.map +1 -0
  98. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js +40 -0
  99. package/out/src/wab-client/auth-method-interactors/PersonaIDInteractor.js.map +1 -0
  100. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts +28 -0
  101. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.d.ts.map +1 -0
  102. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js +73 -0
  103. package/out/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.js.map +1 -0
  104. package/out/test/Wallet/action/abortAction.test.d.ts.map +1 -0
  105. package/out/test/{wallet → Wallet}/action/abortAction.test.js.map +1 -1
  106. package/out/test/Wallet/action/createAction.test.d.ts.map +1 -0
  107. package/out/test/{wallet → Wallet}/action/createAction.test.js.map +1 -1
  108. package/out/test/{wallet → Wallet}/action/createAction2.test.d.ts.map +1 -1
  109. package/out/test/{wallet → Wallet}/action/createAction2.test.js.map +1 -1
  110. package/out/test/Wallet/action/createActionToGenerateBeefs.man.test.d.ts.map +1 -0
  111. package/out/test/{wallet → Wallet}/action/createActionToGenerateBeefs.man.test.js.map +1 -1
  112. package/out/test/Wallet/action/internalizeAction.test.d.ts.map +1 -0
  113. package/out/test/{wallet → Wallet}/action/internalizeAction.test.js.map +1 -1
  114. package/out/test/Wallet/action/relinquishOutput.test.d.ts.map +1 -0
  115. package/out/test/{wallet → Wallet}/action/relinquishOutput.test.js.map +1 -1
  116. package/out/test/Wallet/construct/Wallet.constructor.test.d.ts.map +1 -0
  117. package/out/test/{wallet → Wallet}/construct/Wallet.constructor.test.js.map +1 -1
  118. package/out/test/Wallet/list/listActions.test.d.ts.map +1 -0
  119. package/out/test/{wallet → Wallet}/list/listActions.test.js.map +1 -1
  120. package/out/test/Wallet/list/listActions2.test.d.ts.map +1 -0
  121. package/out/test/{wallet → Wallet}/list/listActions2.test.js.map +1 -1
  122. package/out/test/Wallet/list/listCertificates.test.d.ts.map +1 -0
  123. package/out/test/{wallet → Wallet}/list/listCertificates.test.js.map +1 -1
  124. package/out/test/Wallet/list/listOutputs.test.d.ts.map +1 -0
  125. package/out/test/{wallet → Wallet}/list/listOutputs.test.js.map +1 -1
  126. package/out/test/Wallet/sync/Wallet.sync.test.d.ts.map +1 -0
  127. package/out/test/{wallet → Wallet}/sync/Wallet.sync.test.js.map +1 -1
  128. package/out/tsconfig.all.tsbuildinfo +1 -1
  129. package/package.json +3 -3
  130. package/src/CWIStyleWalletManager.ts +1891 -0
  131. package/src/Setup.ts +514 -8
  132. package/src/SimpleWalletManager.ts +553 -0
  133. package/src/Wallet.ts +47 -3
  134. package/src/WalletAuthenticationManager.ts +183 -0
  135. package/src/WalletPermissionsManager.ts +2639 -0
  136. package/src/WalletSettingsManager.ts +241 -0
  137. package/src/__tests/CWIStyleWalletManager.test.ts +709 -0
  138. package/src/__tests/WalletPermissionsManager.callbacks.test.ts +328 -0
  139. package/src/__tests/WalletPermissionsManager.checks.test.ts +857 -0
  140. package/src/__tests/WalletPermissionsManager.encryption.test.ts +407 -0
  141. package/src/__tests/WalletPermissionsManager.fixtures.ts +283 -0
  142. package/src/__tests/WalletPermissionsManager.flows.test.ts +490 -0
  143. package/src/__tests/WalletPermissionsManager.initialization.test.ts +333 -0
  144. package/src/__tests/WalletPermissionsManager.proxying.test.ts +753 -0
  145. package/src/__tests/WalletPermissionsManager.tokens.test.ts +584 -0
  146. package/src/index.all.ts +9 -9
  147. package/src/index.client.ts +9 -1
  148. package/src/utility/identityUtils.ts +170 -0
  149. package/src/wab-client/WABClient.ts +103 -0
  150. package/src/wab-client/__tests/WABClient.test.ts +58 -0
  151. package/src/wab-client/auth-method-interactors/AuthMethodInteractor.ts +47 -0
  152. package/src/wab-client/auth-method-interactors/PersonaIDInteractor.ts +45 -0
  153. package/src/wab-client/auth-method-interactors/TwilioPhoneInteractor.ts +82 -0
  154. package/out/test/wallet/action/abortAction.test.d.ts.map +0 -1
  155. package/out/test/wallet/action/createAction.test.d.ts.map +0 -1
  156. package/out/test/wallet/action/createActionToGenerateBeefs.man.test.d.ts.map +0 -1
  157. package/out/test/wallet/action/internalizeAction.test.d.ts.map +0 -1
  158. package/out/test/wallet/action/relinquishOutput.test.d.ts.map +0 -1
  159. package/out/test/wallet/construct/Wallet.constructor.test.d.ts.map +0 -1
  160. package/out/test/wallet/list/listActions.test.d.ts.map +0 -1
  161. package/out/test/wallet/list/listActions2.test.d.ts.map +0 -1
  162. package/out/test/wallet/list/listCertificates.test.d.ts.map +0 -1
  163. package/out/test/wallet/list/listOutputs.test.d.ts.map +0 -1
  164. package/out/test/wallet/sync/Wallet.sync.test.d.ts.map +0 -1
  165. package/src/SetupClient.ts +0 -532
  166. /package/out/test/{wallet → Wallet}/action/abortAction.test.d.ts +0 -0
  167. /package/out/test/{wallet → Wallet}/action/abortAction.test.js +0 -0
  168. /package/out/test/{wallet → Wallet}/action/createAction.test.d.ts +0 -0
  169. /package/out/test/{wallet → Wallet}/action/createAction.test.js +0 -0
  170. /package/out/test/{wallet → Wallet}/action/createAction2.test.d.ts +0 -0
  171. /package/out/test/{wallet → Wallet}/action/createAction2.test.js +0 -0
  172. /package/out/test/{wallet → Wallet}/action/createActionToGenerateBeefs.man.test.d.ts +0 -0
  173. /package/out/test/{wallet → Wallet}/action/createActionToGenerateBeefs.man.test.js +0 -0
  174. /package/out/test/{wallet → Wallet}/action/internalizeAction.test.d.ts +0 -0
  175. /package/out/test/{wallet → Wallet}/action/internalizeAction.test.js +0 -0
  176. /package/out/test/{wallet → Wallet}/action/relinquishOutput.test.d.ts +0 -0
  177. /package/out/test/{wallet → Wallet}/action/relinquishOutput.test.js +0 -0
  178. /package/out/test/{wallet → Wallet}/construct/Wallet.constructor.test.d.ts +0 -0
  179. /package/out/test/{wallet → Wallet}/construct/Wallet.constructor.test.js +0 -0
  180. /package/out/test/{wallet → Wallet}/list/listActions.test.d.ts +0 -0
  181. /package/out/test/{wallet → Wallet}/list/listActions.test.js +0 -0
  182. /package/out/test/{wallet → Wallet}/list/listActions2.test.d.ts +0 -0
  183. /package/out/test/{wallet → Wallet}/list/listActions2.test.js +0 -0
  184. /package/out/test/{wallet → Wallet}/list/listCertificates.test.d.ts +0 -0
  185. /package/out/test/{wallet → Wallet}/list/listCertificates.test.js +0 -0
  186. /package/out/test/{wallet → Wallet}/list/listOutputs.test.d.ts +0 -0
  187. /package/out/test/{wallet → Wallet}/list/listOutputs.test.js +0 -0
  188. /package/out/test/{wallet → Wallet}/sync/Wallet.sync.test.d.ts +0 -0
  189. /package/out/test/{wallet → Wallet}/sync/Wallet.sync.test.js +0 -0
  190. /package/test/{wallet → Wallet}/action/abortAction.test.ts +0 -0
  191. /package/test/{wallet → Wallet}/action/createAction.test.ts +0 -0
  192. /package/test/{wallet → Wallet}/action/createAction2.test.ts +0 -0
  193. /package/test/{wallet → Wallet}/action/createActionToGenerateBeefs.man.test.ts +0 -0
  194. /package/test/{wallet → Wallet}/action/internalizeAction.test.ts +0 -0
  195. /package/test/{wallet → Wallet}/action/relinquishOutput.test.ts +0 -0
  196. /package/test/{wallet → Wallet}/construct/Wallet.constructor.test.ts +0 -0
  197. /package/test/{wallet → Wallet}/list/listActions.test.ts +0 -0
  198. /package/test/{wallet → Wallet}/list/listActions2.test.ts +0 -0
  199. /package/test/{wallet → Wallet}/list/listCertificates.test.ts +0 -0
  200. /package/test/{wallet → Wallet}/list/listOutputs.test.ts +0 -0
  201. /package/test/{wallet → Wallet}/sync/Wallet.sync.test.ts +0 -0
@@ -0,0 +1,1131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CWIStyleWalletManager = exports.OverlayUMPTokenInteractor = exports.PBKDF2_NUM_ROUNDS = void 0;
4
+ const sdk_1 = require("@bsv/sdk");
5
+ const PrivilegedKeyManager_1 = require("./sdk/PrivilegedKeyManager");
6
+ /**
7
+ * Number of rounds used in PBKDF2 for deriving password keys.
8
+ */
9
+ exports.PBKDF2_NUM_ROUNDS = 7777;
10
+ /**
11
+ * @class OverlayUMPTokenInteractor
12
+ *
13
+ * A concrete implementation of the UMPTokenInteractor interface that interacts
14
+ * with Overlay Services and the UMP (User Management Protocol) topic. This class
15
+ * is responsible for:
16
+ *
17
+ * 1) Locating UMP tokens via overlay lookups (ls_users).
18
+ * 2) Creating and publishing new or updated UMP token outputs on-chain under
19
+ * the "tm_users" topic.
20
+ * 3) Consuming (spending) an old token if provided.
21
+ */
22
+ class OverlayUMPTokenInteractor {
23
+ /**
24
+ * Construct a new OverlayUMPTokenInteractor.
25
+ *
26
+ * @param resolver A LookupResolver instance for performing overlay queries (ls_users).
27
+ * @param broadcaster A SHIPBroadcaster instance for sharing new or updated tokens across the `tm_users` overlay.
28
+ */
29
+ constructor(resolver = new sdk_1.LookupResolver(), broadcaster = new sdk_1.SHIPBroadcaster(['tm_users'])) {
30
+ this.resolver = resolver;
31
+ this.broadcaster = broadcaster;
32
+ }
33
+ /**
34
+ * Finds a UMP token on-chain by the given presentation key hash, if it exists.
35
+ * Uses the ls_users overlay service to perform the lookup.
36
+ *
37
+ * @param hash The 32-byte SHA-256 hash of the presentation key.
38
+ * @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
39
+ */
40
+ async findByPresentationKeyHash(hash) {
41
+ // Query ls_users for the given presentationHash
42
+ const question = {
43
+ service: 'ls_users',
44
+ query: { presentationHash: sdk_1.Utils.toHex(hash) }
45
+ };
46
+ const answer = await this.resolver.query(question);
47
+ return this.parseLookupAnswer(answer);
48
+ }
49
+ /**
50
+ * Finds a UMP token on-chain by the given recovery key hash, if it exists.
51
+ * Uses the ls_users overlay service to perform the lookup.
52
+ *
53
+ * @param hash The 32-byte SHA-256 hash of the recovery key.
54
+ * @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
55
+ */
56
+ async findByRecoveryKeyHash(hash) {
57
+ const question = {
58
+ service: 'ls_users',
59
+ query: { recoveryHash: sdk_1.Utils.toHex(hash) }
60
+ };
61
+ const answer = await this.resolver.query(question);
62
+ return this.parseLookupAnswer(answer);
63
+ }
64
+ /**
65
+ * Creates or updates (replaces) a UMP token on-chain. If `oldTokenToConsume` is provided,
66
+ * it is spent in the same transaction that creates the new token output. The new token is
67
+ * then broadcast and published under the `tm_users` topic using a SHIP broadcast, ensuring
68
+ * overlay participants see the updated token.
69
+ *
70
+ * @param wallet The wallet used to build and sign the transaction.
71
+ * @param adminOriginator The domain/FQDN of the administrative originator (wallet operator).
72
+ * @param token The new UMPToken to create on-chain.
73
+ * @param oldTokenToConsume Optionally, an existing token to consume/spend in the same transaction.
74
+ * @returns The outpoint of the newly created UMP token (e.g. "abcd1234...ef.0").
75
+ */
76
+ async buildAndSend(wallet, adminOriginator, token, oldTokenToConsume) {
77
+ // 1) Construct the data fields for the new UMP token in the same
78
+ // 11-field order used by the UMP protocol's PushDrop definition.
79
+ const fields = new Array(11);
80
+ // See: UMP field ordering
81
+ // 0 => passwordSalt
82
+ // 1 => passwordPresentationPrimary
83
+ // 2 => passwordRecoveryPrimary
84
+ // 3 => presentationRecoveryPrimary
85
+ // 4 => passwordPrimaryPrivileged
86
+ // 5 => presentationRecoveryPrivileged
87
+ // 6 => presentationHash
88
+ // 7 => recoveryHash
89
+ // 8 => presentationKeyEncrypted
90
+ // 9 => passwordKeyEncrypted
91
+ // 10 => recoveryKeyEncrypted
92
+ fields[0] = token.passwordSalt;
93
+ fields[1] = token.passwordPresentationPrimary;
94
+ fields[2] = token.passwordRecoveryPrimary;
95
+ fields[3] = token.presentationRecoveryPrimary;
96
+ fields[4] = token.passwordPrimaryPrivileged;
97
+ fields[5] = token.presentationRecoveryPrivileged;
98
+ fields[6] = token.presentationHash;
99
+ fields[7] = token.recoveryHash;
100
+ fields[8] = token.presentationKeyEncrypted;
101
+ fields[9] = token.passwordKeyEncrypted;
102
+ fields[10] = token.recoveryKeyEncrypted;
103
+ // 2) Create a PushDrop script referencing these fields, locked with the admin key (for easy revocation).
104
+ const script = await new sdk_1.PushDrop(wallet).lock(fields, [2, 'admin user-management token'], // protocolID
105
+ '1', // keyID
106
+ 'self', // counterparty
107
+ /*forSelf=*/ true,
108
+ /*includeSignature=*/ true);
109
+ // 3) Prepare the createAction call. If oldTokenToConsume is provided, we gather the outpoint.
110
+ const inputs = [];
111
+ let inputToken;
112
+ if (oldTokenToConsume === null || oldTokenToConsume === void 0 ? void 0 : oldTokenToConsume.currentOutpoint) {
113
+ inputs.push({
114
+ outpoint: oldTokenToConsume.currentOutpoint,
115
+ unlockingScriptLength: 73, // typical signature length
116
+ inputDescription: 'Consume old UMP token'
117
+ });
118
+ inputToken = await this.findByOutpoint(oldTokenToConsume.currentOutpoint);
119
+ }
120
+ const outputs = [
121
+ {
122
+ lockingScript: script.toHex(),
123
+ satoshis: 1,
124
+ outputDescription: 'New UMP token output'
125
+ }
126
+ ];
127
+ // 4) Build the partial transaction via createAction.
128
+ const createResult = await wallet.createAction({
129
+ description: oldTokenToConsume
130
+ ? 'Renew UMP token (consume old, create new)'
131
+ : 'Create new UMP token',
132
+ inputs,
133
+ outputs,
134
+ inputBEEF: inputToken === null || inputToken === void 0 ? void 0 : inputToken.beef
135
+ }, adminOriginator);
136
+ // If the transaction is fully processed by the wallet (some wallets might do signAndProcess automatically),
137
+ // we retrieve the final TXID from the result.
138
+ if (!createResult.signableTransaction) {
139
+ const finalTxid = createResult.txid ||
140
+ (createResult.tx
141
+ ? sdk_1.Transaction.fromAtomicBEEF(createResult.tx).id('hex')
142
+ : undefined);
143
+ if (!finalTxid) {
144
+ throw new Error('No signableTransaction and no final TX found.');
145
+ }
146
+ // Now broadcast to `tm_users` using SHIP
147
+ const broadcastTx = sdk_1.Transaction.fromAtomicBEEF(createResult.tx);
148
+ await this.broadcaster.broadcast(broadcastTx);
149
+ return `${finalTxid}.0`;
150
+ }
151
+ // 5) If oldTokenToConsume is present, we must sign the input referencing it.
152
+ // (If there's no old token, there's nothing to sign for the input.)
153
+ let finalTxid = '';
154
+ const reference = createResult.signableTransaction.reference;
155
+ const partialTx = sdk_1.Transaction.fromBEEF(createResult.signableTransaction.tx);
156
+ if (oldTokenToConsume === null || oldTokenToConsume === void 0 ? void 0 : oldTokenToConsume.currentOutpoint) {
157
+ // Unlock the old token with a matching PushDrop unlocker
158
+ const unlocker = new sdk_1.PushDrop(wallet).unlock([2, 'admin user-management token'], '1', 'self');
159
+ const unlockingScript = await unlocker.sign(partialTx, 0);
160
+ // Provide it to the wallet
161
+ const signResult = await wallet.signAction({
162
+ reference,
163
+ spends: {
164
+ 0: {
165
+ unlockingScript: unlockingScript.toHex()
166
+ }
167
+ }
168
+ }, adminOriginator);
169
+ finalTxid =
170
+ signResult.txid ||
171
+ (signResult.tx
172
+ ? sdk_1.Transaction.fromAtomicBEEF(signResult.tx).id('hex')
173
+ : '');
174
+ if (!finalTxid) {
175
+ throw new Error('Could not finalize transaction for renewed UMP token.');
176
+ }
177
+ // 6) Broadcast to `tm_users`
178
+ const finalAtomicTx = signResult.tx;
179
+ const broadcastTx = sdk_1.Transaction.fromAtomicBEEF(finalAtomicTx);
180
+ await this.broadcaster.broadcast(broadcastTx);
181
+ return `${finalTxid}.0`;
182
+ }
183
+ else {
184
+ // Fallbaack
185
+ const signResult = await wallet.signAction({ reference, spends: {} }, adminOriginator);
186
+ finalTxid =
187
+ signResult.txid ||
188
+ (signResult.tx
189
+ ? sdk_1.Transaction.fromAtomicBEEF(signResult.tx).id('hex')
190
+ : '');
191
+ if (!finalTxid) {
192
+ throw new Error('Failed to finalize new UMP token transaction.');
193
+ }
194
+ const finalAtomicTx = signResult.tx;
195
+ const broadcastTx = sdk_1.Transaction.fromAtomicBEEF(finalAtomicTx);
196
+ await this.broadcaster.broadcast(broadcastTx);
197
+ return `${finalTxid}.0`;
198
+ }
199
+ }
200
+ /**
201
+ * Attempts to parse a LookupAnswer from the UMP lookup service. If successful,
202
+ * extracts the token fields from the resulting transaction and constructs
203
+ * a UMPToken object.
204
+ *
205
+ * @param answer The LookupAnswer returned by a query to ls_users.
206
+ * @returns The parsed UMPToken or `undefined` if none found/decodable.
207
+ */
208
+ parseLookupAnswer(answer) {
209
+ if (answer.type !== 'output-list') {
210
+ return undefined;
211
+ }
212
+ if (!answer.outputs || answer.outputs.length === 0) {
213
+ return undefined;
214
+ }
215
+ // We expect only one relevant UMP token in most queries, so let's parse the first.
216
+ // If multiple are returned, we can parse the first.
217
+ const { beef, outputIndex } = answer.outputs[0];
218
+ try {
219
+ const tx = sdk_1.Transaction.fromBEEF(beef);
220
+ const outpoint = `${tx.id('hex')}.${outputIndex}`;
221
+ const decoded = sdk_1.PushDrop.decode(tx.outputs[outputIndex].lockingScript);
222
+ // Expecting 11 fields for UMP
223
+ if (!decoded.fields || decoded.fields.length < 11)
224
+ return undefined;
225
+ // Build the UMP token from these fields, preserving outpoint
226
+ const t = {
227
+ passwordSalt: decoded.fields[0],
228
+ passwordPresentationPrimary: decoded.fields[1],
229
+ passwordRecoveryPrimary: decoded.fields[2],
230
+ presentationRecoveryPrimary: decoded.fields[3],
231
+ passwordPrimaryPrivileged: decoded.fields[4],
232
+ presentationRecoveryPrivileged: decoded.fields[5],
233
+ presentationHash: decoded.fields[6],
234
+ recoveryHash: decoded.fields[7],
235
+ presentationKeyEncrypted: decoded.fields[8],
236
+ passwordKeyEncrypted: decoded.fields[9],
237
+ recoveryKeyEncrypted: decoded.fields[10],
238
+ currentOutpoint: outpoint
239
+ };
240
+ return t;
241
+ }
242
+ catch (e) {
243
+ // If we fail to parse or decode, return undefined
244
+ return undefined;
245
+ }
246
+ }
247
+ /**
248
+ * Finds by outpoint for unlocking / spending previous tokens.
249
+ * @param outpoint The outpoint we are searching by
250
+ * @returns The result so that we can use it to unlock the transaction
251
+ */
252
+ async findByOutpoint(outpoint) {
253
+ const results = await this.resolver.query({
254
+ service: 'ls_ump',
255
+ query: {
256
+ outpoint
257
+ }
258
+ });
259
+ if (results.type !== 'output-list') {
260
+ return undefined;
261
+ }
262
+ if (!results.outputs.length) {
263
+ return undefined;
264
+ }
265
+ return results.outputs[0];
266
+ }
267
+ }
268
+ exports.OverlayUMPTokenInteractor = OverlayUMPTokenInteractor;
269
+ /**
270
+ * Manages a "CWI-style" wallet that uses a UMP token and a
271
+ * multi-key authentication scheme (password, presentation key, and recovery key).
272
+ */
273
+ class CWIStyleWalletManager {
274
+ /**
275
+ * Constructs a new CWIStyleWalletManager.
276
+ *
277
+ * @param adminOriginator The domain name of the administrative originator.
278
+ * @param walletBuilder A function that can build an underlying wallet instance
279
+ * from a primary key and a privileged key manager
280
+ * @param interactor An instance of UMPTokenInteractor capable of managing UMP tokens.
281
+ * @param recoveryKeySaver A function that can persist or display a newly generated recovery key.
282
+ * @param passwordRetriever A function to request the user's password, given a reason and a test function.
283
+ * @param newWalletFunder An optional function called with the presentation key and a new Wallet post-construction to fund it before use.
284
+ * @param stateSnapshot If provided, a previously saved snapshot of the wallet's state.
285
+ */
286
+ constructor(adminOriginator, walletBuilder, interactor = new OverlayUMPTokenInteractor(), recoveryKeySaver, passwordRetriever, newWalletFunder, stateSnapshot) {
287
+ /**
288
+ * The current mode of authentication:
289
+ * - 'presentation-key-and-password'
290
+ * - 'presentation-key-and-recovery-key'
291
+ * - 'recovery-key-and-password'
292
+ */
293
+ this.authenticationMode = 'presentation-key-and-password';
294
+ /**
295
+ * Indicates whether this is a new user or an existing user flow:
296
+ * - 'new-user'
297
+ * - 'existing-user'
298
+ */
299
+ this.authenticationFlow = 'new-user';
300
+ this.adminOriginator = adminOriginator;
301
+ this.walletBuilder = walletBuilder;
302
+ this.UMPTokenInteractor = interactor;
303
+ this.recoveryKeySaver = recoveryKeySaver;
304
+ this.passwordRetriever = passwordRetriever;
305
+ this.authenticated = false;
306
+ this.newWalletFunder = newWalletFunder;
307
+ // If a saved snapshot is provided, attempt to load it.
308
+ if (stateSnapshot) {
309
+ this.loadSnapshot(stateSnapshot);
310
+ }
311
+ }
312
+ /**
313
+ * Provides the presentation key in an authentication mode that requires it.
314
+ * If a UMP token is found based on the key's hash, this is an existing-user flow.
315
+ * Otherwise, it is treated as a new-user flow.
316
+ *
317
+ * @param key The user's presentation key (32 bytes).
318
+ * @throws {Error} if user is already authenticated, or if the current mode does not require a presentation key.
319
+ */
320
+ async providePresentationKey(key) {
321
+ if (this.authenticated) {
322
+ throw new Error('User is already authenticated');
323
+ }
324
+ if (this.authenticationMode === 'recovery-key-and-password') {
325
+ throw new Error('Presentation key is not needed in this mode');
326
+ }
327
+ const hash = sdk_1.Hash.sha256(key);
328
+ const token = await this.UMPTokenInteractor.findByPresentationKeyHash(hash);
329
+ if (!token) {
330
+ // No token found -> New user
331
+ this.authenticationFlow = 'new-user';
332
+ this.presentationKey = key;
333
+ }
334
+ else {
335
+ // Found token -> existing user
336
+ this.authenticationFlow = 'existing-user';
337
+ this.presentationKey = key;
338
+ this.currentUMPToken = token;
339
+ }
340
+ }
341
+ /**
342
+ * Provides the password in an authentication mode that requires it.
343
+ *
344
+ * - **Existing user**:
345
+ * Decrypts the primary key using the provided password (and either the presentation key or recovery key, depending on the mode).
346
+ * Then builds the underlying wallet, marking the user as authenticated.
347
+ *
348
+ * - **New user**:
349
+ * Generates a new UMP token with fresh keys (primary, privileged, recovery). Publishes it on-chain and builds the wallet.
350
+ *
351
+ * @param password The user's password as a string.
352
+ * @throws {Error} If the user is already authenticated, if the mode does not use a password, or if required keys are missing.
353
+ */
354
+ async providePassword(password) {
355
+ if (this.authenticated) {
356
+ throw new Error('User is already authenticated');
357
+ }
358
+ if (this.authenticationMode === 'presentation-key-and-recovery-key') {
359
+ throw new Error('Password is not needed in this mode');
360
+ }
361
+ // If we detect an existing user flow:
362
+ if (this.authenticationFlow === 'existing-user') {
363
+ if (!this.currentUMPToken) {
364
+ throw new Error('Provide either a presentation key or a recovery key first, depending on the authentication mode.');
365
+ }
366
+ const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
367
+ if (this.authenticationMode === 'presentation-key-and-password') {
368
+ if (!this.presentationKey) {
369
+ throw new Error('No presentation key found!');
370
+ }
371
+ // Decrypt the primary key with XOR(presentationKey, derivedPasswordKey).
372
+ const xorKey = this.XOR(this.presentationKey, derivedPasswordKey);
373
+ const decryptedPrimary = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.passwordPresentationPrimary);
374
+ await this.buildUnderlying(decryptedPrimary);
375
+ }
376
+ else {
377
+ // 'recovery-key-and-password' mode
378
+ if (!this.recoveryKey) {
379
+ throw new Error('No recovery key found!');
380
+ }
381
+ // Decrypt the primary key with XOR(recoveryKey, derivedPasswordKey).
382
+ const primaryDecryptionKey = this.XOR(this.recoveryKey, derivedPasswordKey);
383
+ const decryptedPrimary = new sdk_1.SymmetricKey(primaryDecryptionKey).decrypt(this.currentUMPToken.passwordRecoveryPrimary);
384
+ // Decrypt the privileged key for immediate use.
385
+ const privilegedDecryptionKey = this.XOR(decryptedPrimary, derivedPasswordKey);
386
+ const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptionKey).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
387
+ await this.buildUnderlying(decryptedPrimary, decryptedPrivileged);
388
+ }
389
+ return;
390
+ }
391
+ // Otherwise, handle new user flow (only valid in 'presentation-key-and-password').
392
+ if (this.authenticationMode !== 'presentation-key-and-password') {
393
+ throw new Error('New-user flow requires presentation key and password, not recovery key mode.');
394
+ }
395
+ if (!this.presentationKey) {
396
+ throw new Error('No presentation key provided for new-user flow.');
397
+ }
398
+ // Generate new random keys/salt and create a new UMP token.
399
+ const recoveryKey = (0, sdk_1.Random)(32);
400
+ await this.recoveryKeySaver(recoveryKey);
401
+ const passwordSalt = (0, sdk_1.Random)(32);
402
+ const passwordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
403
+ const primaryKey = (0, sdk_1.Random)(32);
404
+ const privilegedKey = (0, sdk_1.Random)(32);
405
+ // Build XOR-based symmetrical keys:
406
+ const presentationPassword = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, passwordKey));
407
+ const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(this.presentationKey, recoveryKey));
408
+ const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
409
+ const primaryPassword = new sdk_1.SymmetricKey(this.XOR(primaryKey, passwordKey));
410
+ // Temporarily create a privileged key manager for encrypting the keys in the token.
411
+ const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(privilegedKey));
412
+ // Build the new UMP token:
413
+ const newToken = {
414
+ passwordSalt,
415
+ passwordPresentationPrimary: presentationPassword.encrypt(primaryKey),
416
+ passwordRecoveryPrimary: recoveryPassword.encrypt(primaryKey),
417
+ presentationRecoveryPrimary: presentationRecovery.encrypt(primaryKey),
418
+ passwordPrimaryPrivileged: primaryPassword.encrypt(privilegedKey),
419
+ presentationRecoveryPrivileged: presentationRecovery.encrypt(privilegedKey),
420
+ presentationHash: sdk_1.Hash.sha256(this.presentationKey),
421
+ recoveryHash: sdk_1.Hash.sha256(recoveryKey),
422
+ presentationKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
423
+ plaintext: this.presentationKey,
424
+ protocolID: [2, 'admin key wrapping'],
425
+ keyID: '1'
426
+ })).ciphertext,
427
+ passwordKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
428
+ plaintext: passwordKey,
429
+ protocolID: [2, 'admin key wrapping'],
430
+ keyID: '1'
431
+ })).ciphertext,
432
+ recoveryKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
433
+ plaintext: recoveryKey,
434
+ protocolID: [2, 'admin key wrapping'],
435
+ keyID: '1'
436
+ })).ciphertext
437
+ };
438
+ // Now, we can create our new wallet!
439
+ this.currentUMPToken = newToken;
440
+ await this.buildUnderlying(primaryKey);
441
+ // Before we do anything, the new wallet is most likely empty right now.
442
+ // We want to provide a chance for someone to fund it, if they want.
443
+ if (this.newWalletFunder) {
444
+ try {
445
+ await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
446
+ }
447
+ catch (e) {
448
+ // swallow error
449
+ }
450
+ }
451
+ // Publish the new UMP token on-chain and store the resulting outpoint.
452
+ this.currentUMPToken.currentOutpoint =
453
+ await this.UMPTokenInteractor.buildAndSend(this.underlying, this.adminOriginator, newToken);
454
+ }
455
+ /**
456
+ * Provides the recovery key in an authentication flow that requires it.
457
+ *
458
+ * @param recoveryKey The user's recovery key (32 bytes).
459
+ * @throws {Error} if user is already authenticated, if the mode does not use a recovery key,
460
+ * or if a required presentation key is missing in "presentation-key-and-recovery-key" mode.
461
+ */
462
+ async provideRecoveryKey(recoveryKey) {
463
+ if (this.authenticated) {
464
+ throw new Error('Already authenticated');
465
+ }
466
+ // Cannot use recovery key in a new-user flow
467
+ if (this.authenticationFlow === 'new-user') {
468
+ throw new Error('Do not submit recovery key in new-user flow');
469
+ }
470
+ if (this.authenticationMode === 'presentation-key-and-password') {
471
+ throw new Error('No recovery key required in this mode');
472
+ }
473
+ else if (this.authenticationMode === 'recovery-key-and-password') {
474
+ // We will need to wait until the user provides the password as well.
475
+ const hash = sdk_1.Hash.sha256(recoveryKey);
476
+ const token = await this.UMPTokenInteractor.findByRecoveryKeyHash(hash);
477
+ if (!token) {
478
+ throw new Error('No user found with this key');
479
+ }
480
+ this.recoveryKey = recoveryKey;
481
+ this.currentUMPToken = token;
482
+ }
483
+ else {
484
+ // 'presentation-key-and-recovery-key'
485
+ if (!this.presentationKey) {
486
+ throw new Error('Provide the presentation key first');
487
+ }
488
+ if (!this.currentUMPToken) {
489
+ throw new Error('Current UMP token not found');
490
+ }
491
+ // Decrypt the primary key:
492
+ const xorKey = this.XOR(this.presentationKey, recoveryKey);
493
+ const primaryKey = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrimary);
494
+ // Decrypt the privileged key (for account recovery).
495
+ const privilegedKey = new sdk_1.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
496
+ await this.buildUnderlying(primaryKey, privilegedKey);
497
+ }
498
+ }
499
+ /**
500
+ * Saves the current wallet state (including the current UMP token and primary key)
501
+ * into an encrypted snapshot. This snapshot can be stored locally and later passed
502
+ * to `loadSnapshot` to restore the wallet state without re-authenticating manually.
503
+ *
504
+ * @remarks
505
+ * Storing the snapshot provides a fully authenticated state.
506
+ * This **must** be securely stored (e.g. system keychain or encrypted file).
507
+ * If attackers gain access to this snapshot, they can fully control the wallet.
508
+ *
509
+ * @returns An array of bytes representing the encrypted snapshot.
510
+ * @throws {Error} if no primary key or token is currently set.
511
+ */
512
+ saveSnapshot() {
513
+ if (!this.primaryKey || !this.currentUMPToken) {
514
+ throw new Error('No primary key or current UMP token set');
515
+ }
516
+ // Generate a random snapshot encryption key:
517
+ const snapshotKey = (0, sdk_1.Random)(32);
518
+ // Serialize the relevant data to a preimage buffer:
519
+ const snapshotPreimageWriter = new sdk_1.Utils.Writer();
520
+ // Write the primary key (32 bytes):
521
+ snapshotPreimageWriter.write(this.primaryKey);
522
+ // Write the serialized UMP token:
523
+ const serializedToken = this.serializeUMPToken(this.currentUMPToken);
524
+ snapshotPreimageWriter.write(serializedToken);
525
+ // Encrypt the combined data with the snapshotKey:
526
+ const snapshotPreimage = snapshotPreimageWriter.toArray();
527
+ const snapshotPayload = new sdk_1.SymmetricKey(snapshotKey).encrypt(snapshotPreimage);
528
+ // Build the final snapshot structure: [snapshotKey (32 bytes) + encryptedPayload]
529
+ const snapshotWriter = new sdk_1.Utils.Writer();
530
+ snapshotWriter.write(snapshotKey);
531
+ snapshotWriter.write(snapshotPayload);
532
+ return snapshotWriter.toArray();
533
+ }
534
+ /**
535
+ * Loads a previously saved state snapshot (e.g. from `saveSnapshot`).
536
+ * Upon success, the wallet becomes authenticated without needing to re-enter keys.
537
+ *
538
+ * @param snapshot An array of bytes that was previously produced by `saveSnapshot`.
539
+ * @throws {Error} If the snapshot format is invalid or decryption fails.
540
+ */
541
+ async loadSnapshot(snapshot) {
542
+ try {
543
+ const reader = new sdk_1.Utils.Reader(snapshot);
544
+ // First 32 bytes is the snapshotKey:
545
+ const snapshotKey = reader.read(32);
546
+ // The rest is the encrypted payload:
547
+ const encryptedPayload = reader.read();
548
+ // Decrypt the payload:
549
+ const decryptedPayload = new sdk_1.SymmetricKey(snapshotKey).decrypt(encryptedPayload);
550
+ const payloadReader = new sdk_1.Utils.Reader(decryptedPayload);
551
+ // Read the primary key (32 bytes):
552
+ const primaryKey = payloadReader.read(32);
553
+ // Read the remainder as the serialized UMP token:
554
+ const tokenBytes = payloadReader.read();
555
+ const token = this.deserializeUMPToken(tokenBytes);
556
+ // Assign and build:
557
+ this.currentUMPToken = token;
558
+ await this.buildUnderlying(primaryKey);
559
+ }
560
+ catch (error) {
561
+ throw new Error(`Failed to load snapshot: ${error.message}`);
562
+ }
563
+ }
564
+ /**
565
+ * Destroys the underlying wallet, returning to a default state
566
+ */
567
+ destroy() {
568
+ this.underlying = undefined;
569
+ this.underlyingPrivilegedKeyManager = undefined;
570
+ this.authenticated = false;
571
+ this.primaryKey = undefined;
572
+ this.currentUMPToken = undefined;
573
+ this.presentationKey = undefined;
574
+ this.recoveryKey = undefined;
575
+ this.authenticationMode = 'presentation-key-and-password';
576
+ this.authenticationFlow = 'new-user';
577
+ }
578
+ /**
579
+ * Changes the user's password, re-wrapping the primary and privileged keys with the new password factor.
580
+ *
581
+ * @param newPassword The user's new password as a string.
582
+ * @throws {Error} If the user is not authenticated, or if underlying token references are missing.
583
+ */
584
+ async changePassword(newPassword) {
585
+ if (!this.authenticated) {
586
+ throw new Error('Not authenticated.');
587
+ }
588
+ if (!this.currentUMPToken) {
589
+ throw new Error('No UMP token to update.');
590
+ }
591
+ const passwordSalt = (0, sdk_1.Random)(32);
592
+ const passwordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(newPassword, 'utf8'), passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
593
+ // Decrypt existing factors via the privileged key manager:
594
+ const recoveryKey = (await this.underlyingPrivilegedKeyManager.decrypt({
595
+ ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
596
+ protocolID: [2, 'admin key wrapping'],
597
+ keyID: '1'
598
+ })).plaintext;
599
+ const presentationKey = (await this.underlyingPrivilegedKeyManager.decrypt({
600
+ ciphertext: this.currentUMPToken.presentationKeyEncrypted,
601
+ protocolID: [2, 'admin key wrapping'],
602
+ keyID: '1'
603
+ })).plaintext;
604
+ const privilegedKey = new sdk_1.SymmetricKey(this.XOR(presentationKey, recoveryKey)).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
605
+ await this.updateAuthFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
606
+ }
607
+ /**
608
+ * Changes the user's recovery key, prompting the user to save the new key.
609
+ *
610
+ * @throws {Error} If the user is not authenticated, or if underlying token references are missing.
611
+ */
612
+ async changeRecoveryKey() {
613
+ if (!this.authenticated) {
614
+ throw new Error('Not authenticated.');
615
+ }
616
+ if (!this.currentUMPToken) {
617
+ throw new Error('No UMP token to update.');
618
+ }
619
+ const recoveryKey = (0, sdk_1.Random)(32);
620
+ await this.recoveryKeySaver(recoveryKey);
621
+ // Decrypt existing password/presentation keys via the privileged key manager:
622
+ const passwordKey = (await this.underlyingPrivilegedKeyManager.decrypt({
623
+ ciphertext: this.currentUMPToken.passwordKeyEncrypted,
624
+ protocolID: [2, 'admin key wrapping'],
625
+ keyID: '1'
626
+ })).plaintext;
627
+ const presentationKey = (await this.underlyingPrivilegedKeyManager.decrypt({
628
+ ciphertext: this.currentUMPToken.presentationKeyEncrypted,
629
+ protocolID: [2, 'admin key wrapping'],
630
+ keyID: '1'
631
+ })).plaintext;
632
+ const privilegedKey = new sdk_1.SymmetricKey(this.XOR(passwordKey, this.primaryKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
633
+ await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
634
+ }
635
+ /**
636
+ * Changes the user's presentation key.
637
+ *
638
+ * @param presentationKey The new presentation key (32 bytes).
639
+ * @throws {Error} If the user is not authenticated, or if underlying token references are missing.
640
+ */
641
+ async changePresentationKey(presentationKey) {
642
+ if (!this.authenticated) {
643
+ throw new Error('Not authenticated.');
644
+ }
645
+ if (!this.currentUMPToken) {
646
+ throw new Error('No UMP token to update.');
647
+ }
648
+ // Decrypt existing password/recovery keys via the privileged key manager:
649
+ const recoveryKey = (await this.underlyingPrivilegedKeyManager.decrypt({
650
+ ciphertext: this.currentUMPToken.recoveryKeyEncrypted,
651
+ protocolID: [2, 'admin key wrapping'],
652
+ keyID: '1'
653
+ })).plaintext;
654
+ const passwordKey = (await this.underlyingPrivilegedKeyManager.decrypt({
655
+ ciphertext: this.currentUMPToken.passwordKeyEncrypted,
656
+ protocolID: [2, 'admin key wrapping'],
657
+ keyID: '1'
658
+ })).plaintext;
659
+ const privilegedKey = new sdk_1.SymmetricKey(this.XOR(passwordKey, this.primaryKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
660
+ await this.updateAuthFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, recoveryKey, this.primaryKey, privilegedKey);
661
+ }
662
+ /**
663
+ * Internal helper to recompute a UMP token with updated authentication factors and consume the old token on-chain.
664
+ *
665
+ * @param passwordSalt The PBKDF2 salt for the new password factor.
666
+ * @param passwordKey The PBKDF2-derived password key (32 bytes).
667
+ * @param presentationKey The new or existing presentation key (32 bytes).
668
+ * @param recoveryKey The new or existing recovery key (32 bytes).
669
+ * @param primaryKey The user's primary key for re-wrapping.
670
+ * @param privilegedKey The user's privileged key for re-wrapping.
671
+ * @throws {Error} If the user is not authenticated or if keys are unavailable.
672
+ */
673
+ async updateAuthFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, primaryKey, privilegedKey) {
674
+ if (!this.authenticated || !this.primaryKey || !this.currentUMPToken) {
675
+ throw new Error('Wallet is not properly authenticated or missing data.');
676
+ }
677
+ // Derive symmetrical encryption keys via XOR:
678
+ const presentationPassword = new sdk_1.SymmetricKey(this.XOR(presentationKey, passwordKey));
679
+ const presentationRecovery = new sdk_1.SymmetricKey(this.XOR(presentationKey, recoveryKey));
680
+ const recoveryPassword = new sdk_1.SymmetricKey(this.XOR(recoveryKey, passwordKey));
681
+ const primaryPassword = new sdk_1.SymmetricKey(this.XOR(this.primaryKey, passwordKey));
682
+ // Build a temporary privileged key manager just to encrypt the new fields:
683
+ const tempPrivilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => new sdk_1.PrivateKey(privilegedKey));
684
+ // Construct the new UMP token:
685
+ const newToken = {
686
+ passwordSalt,
687
+ passwordPresentationPrimary: presentationPassword.encrypt(this.primaryKey),
688
+ passwordRecoveryPrimary: recoveryPassword.encrypt(this.primaryKey),
689
+ presentationRecoveryPrimary: presentationRecovery.encrypt(this.primaryKey),
690
+ passwordPrimaryPrivileged: primaryPassword.encrypt(privilegedKey),
691
+ presentationRecoveryPrivileged: presentationRecovery.encrypt(privilegedKey),
692
+ presentationHash: sdk_1.Hash.sha256(presentationKey),
693
+ recoveryHash: sdk_1.Hash.sha256(recoveryKey),
694
+ presentationKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
695
+ plaintext: presentationKey,
696
+ protocolID: [2, 'admin key wrapping'],
697
+ keyID: '1'
698
+ })).ciphertext,
699
+ passwordKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
700
+ plaintext: passwordKey,
701
+ protocolID: [2, 'admin key wrapping'],
702
+ keyID: '1'
703
+ })).ciphertext,
704
+ recoveryKeyEncrypted: (await tempPrivilegedKeyManager.encrypt({
705
+ plaintext: recoveryKey,
706
+ protocolID: [2, 'admin key wrapping'],
707
+ keyID: '1'
708
+ })).ciphertext
709
+ };
710
+ // Publish the new token on-chain and consume the old one:
711
+ newToken.currentOutpoint = await this.UMPTokenInteractor.buildAndSend(this.underlying, this.adminOriginator, newToken, this.currentUMPToken);
712
+ this.currentUMPToken = newToken;
713
+ }
714
+ /**
715
+ * A helper function to XOR two equal-length byte arrays.
716
+ *
717
+ * @param n1 The first byte array.
718
+ * @param n2 The second byte array.
719
+ * @returns A new byte array which is the element-wise XOR of the two inputs.
720
+ * @throws {Error} if the two arrays are not the same length.
721
+ */
722
+ XOR(n1, n2) {
723
+ if (n1.length !== n2.length) {
724
+ throw new Error('lengths mismatch');
725
+ }
726
+ const r = new Array(n1.length);
727
+ for (let i = 0; i < n1.length; i++) {
728
+ r[i] = n1[i] ^ n2[i];
729
+ }
730
+ return r;
731
+ }
732
+ /**
733
+ * A helper function to serialize a UMP token to a binary format (version=1).
734
+ * The serialization layout is:
735
+ * - [1 byte version (value=1)]
736
+ * - For each array field in the UMP token, [varint length + bytes]
737
+ * - Then [varint length + outpoint string in UTF-8]
738
+ *
739
+ * @param token The UMP token to serialize.
740
+ * @returns A byte array representing the serialized token.
741
+ * @throws {Error} if the token has no currentOutpoint (required for serialization).
742
+ */
743
+ serializeUMPToken(token) {
744
+ if (!token.currentOutpoint) {
745
+ throw new Error('Token must have outpoint for serialization');
746
+ }
747
+ const writer = new sdk_1.Utils.Writer();
748
+ // Write version byte
749
+ writer.writeUInt8(1);
750
+ // Helper to write array with length prefix
751
+ const writeArray = (arr) => {
752
+ writer.writeVarIntNum(arr.length);
753
+ writer.write(arr);
754
+ };
755
+ // Write each array-based field in the order they appear on UMPToken
756
+ writeArray(token.passwordPresentationPrimary);
757
+ writeArray(token.passwordRecoveryPrimary);
758
+ writeArray(token.presentationRecoveryPrimary);
759
+ writeArray(token.passwordPrimaryPrivileged);
760
+ writeArray(token.presentationRecoveryPrivileged);
761
+ writeArray(token.presentationHash);
762
+ writeArray(token.passwordSalt);
763
+ writeArray(token.recoveryHash);
764
+ writeArray(token.presentationKeyEncrypted);
765
+ writeArray(token.recoveryKeyEncrypted);
766
+ writeArray(token.passwordKeyEncrypted);
767
+ // Finally, write the outpoint string:
768
+ const outpointBytes = sdk_1.Utils.toArray(token.currentOutpoint, 'utf8');
769
+ writer.writeVarIntNum(outpointBytes.length);
770
+ writer.write(outpointBytes);
771
+ return writer.toArray();
772
+ }
773
+ /**
774
+ * A helper function to deserialize a UMP token from the format described in `serializeUMPToken`.
775
+ *
776
+ * @param bin The serialized byte array.
777
+ * @returns The reconstructed UMP token.
778
+ * @throws {Error} if the version byte is unexpected or if parsing fails.
779
+ */
780
+ deserializeUMPToken(bin) {
781
+ const reader = new sdk_1.Utils.Reader(bin);
782
+ // Check version:
783
+ const version = reader.readUInt8();
784
+ if (version !== 1) {
785
+ throw new Error(`Unsupported UMP token version: ${version}`);
786
+ }
787
+ // Helper to read an array with length prefix
788
+ const readArray = () => {
789
+ const length = reader.readVarIntNum();
790
+ return reader.read(length);
791
+ };
792
+ // Read in the correct order:
793
+ const passwordPresentationPrimary = readArray();
794
+ const passwordRecoveryPrimary = readArray();
795
+ const presentationRecoveryPrimary = readArray();
796
+ const passwordPrimaryPrivileged = readArray();
797
+ const presentationRecoveryPrivileged = readArray();
798
+ const presentationHash = readArray();
799
+ const passwordSalt = readArray();
800
+ const recoveryHash = readArray();
801
+ const presentationKeyEncrypted = readArray();
802
+ const recoveryKeyEncrypted = readArray();
803
+ const passwordKeyEncrypted = readArray();
804
+ // Read outpoint string:
805
+ const outpointLen = reader.readVarIntNum();
806
+ const outpointBytes = reader.read(outpointLen);
807
+ const currentOutpoint = sdk_1.Utils.toUTF8(outpointBytes);
808
+ const token = {
809
+ passwordPresentationPrimary,
810
+ passwordRecoveryPrimary,
811
+ presentationRecoveryPrimary,
812
+ passwordPrimaryPrivileged,
813
+ presentationRecoveryPrivileged,
814
+ presentationHash,
815
+ passwordSalt,
816
+ recoveryHash,
817
+ presentationKeyEncrypted,
818
+ recoveryKeyEncrypted,
819
+ passwordKeyEncrypted,
820
+ currentOutpoint
821
+ };
822
+ return token;
823
+ }
824
+ /**
825
+ * Builds the underlying wallet once the user is authenticated.
826
+ *
827
+ * @param primaryKey The user's primary key (32 bytes).
828
+ * @param privilegedKey Optionally, a privileged key (for short-term usage in account recovery).
829
+ */
830
+ async buildUnderlying(primaryKey, privilegedKey) {
831
+ if (!this.currentUMPToken) {
832
+ throw new Error('A UMP token must exist before building underlying wallet!');
833
+ }
834
+ this.primaryKey = primaryKey;
835
+ // Create a privileged manager that either uses the ephemeral privilegedKey if provided,
836
+ // or derives it later from the user's password on demand.
837
+ const privilegedManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async (reason) => {
838
+ if (privilegedKey) {
839
+ // For account recovery: a one-off opportunity to recover.
840
+ const tempKey = new sdk_1.PrivateKey(privilegedKey);
841
+ privilegedKey = undefined;
842
+ return tempKey;
843
+ }
844
+ // Otherwise, ask user for their password to decrypt the privileged key.
845
+ const password = await this.passwordRetriever(reason, (passwordCandidate) => {
846
+ try {
847
+ const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(passwordCandidate, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
848
+ // Decrypt the privileged key with XOR(primaryKey, derivedPasswordKey).
849
+ const privilegedDecryptor = this.XOR(this.primaryKey, derivedPasswordKey);
850
+ const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
851
+ if (decryptedPrivileged) {
852
+ return true;
853
+ }
854
+ return false;
855
+ }
856
+ catch (e) {
857
+ return false;
858
+ }
859
+ });
860
+ const derivedPasswordKey = sdk_1.Hash.pbkdf2(sdk_1.Utils.toArray(password, 'utf8'), this.currentUMPToken.passwordSalt, exports.PBKDF2_NUM_ROUNDS, 32, 'sha512');
861
+ // Decrypt the privileged key with XOR(primaryKey, derivedPasswordKey).
862
+ const privilegedDecryptor = this.XOR(this.primaryKey, derivedPasswordKey);
863
+ const decryptedPrivileged = new sdk_1.SymmetricKey(privilegedDecryptor).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
864
+ return new sdk_1.PrivateKey(decryptedPrivileged);
865
+ });
866
+ this.underlyingPrivilegedKeyManager = privilegedManager;
867
+ // Build the underlying wallet with the primary key and privileged manager.
868
+ this.underlying = await this.walletBuilder(primaryKey, privilegedManager);
869
+ this.authenticated = true;
870
+ }
871
+ /*
872
+ * ---------------------------------------------------------------------------------------
873
+ * Below are the standard WalletInterface methods that simply proxy through to this.underlying,
874
+ * ensuring that the user is authenticated and that the admin originator is not misused.
875
+ * ---------------------------------------------------------------------------------------
876
+ */
877
+ async getPublicKey(args, originator) {
878
+ if (!this.authenticated) {
879
+ throw new Error('User is not authenticated.');
880
+ }
881
+ if (originator === this.adminOriginator) {
882
+ throw new Error('External applications are not allowed to use the admin originator.');
883
+ }
884
+ return this.underlying.getPublicKey(args, originator);
885
+ }
886
+ async revealCounterpartyKeyLinkage(args, originator) {
887
+ if (!this.authenticated) {
888
+ throw new Error('User is not authenticated.');
889
+ }
890
+ if (originator === this.adminOriginator) {
891
+ throw new Error('External applications are not allowed to use the admin originator.');
892
+ }
893
+ return this.underlying.revealCounterpartyKeyLinkage(args, originator);
894
+ }
895
+ async revealSpecificKeyLinkage(args, originator) {
896
+ if (!this.authenticated) {
897
+ throw new Error('User is not authenticated.');
898
+ }
899
+ if (originator === this.adminOriginator) {
900
+ throw new Error('External applications are not allowed to use the admin originator.');
901
+ }
902
+ return this.underlying.revealSpecificKeyLinkage(args, originator);
903
+ }
904
+ async encrypt(args, originator) {
905
+ if (!this.authenticated) {
906
+ throw new Error('User is not authenticated.');
907
+ }
908
+ if (originator === this.adminOriginator) {
909
+ throw new Error('External applications are not allowed to use the admin originator.');
910
+ }
911
+ return this.underlying.encrypt(args, originator);
912
+ }
913
+ async decrypt(args, originator) {
914
+ if (!this.authenticated) {
915
+ throw new Error('User is not authenticated.');
916
+ }
917
+ if (originator === this.adminOriginator) {
918
+ throw new Error('External applications are not allowed to use the admin originator.');
919
+ }
920
+ return this.underlying.decrypt(args, originator);
921
+ }
922
+ async createHmac(args, originator) {
923
+ if (!this.authenticated) {
924
+ throw new Error('User is not authenticated.');
925
+ }
926
+ if (originator === this.adminOriginator) {
927
+ throw new Error('External applications are not allowed to use the admin originator.');
928
+ }
929
+ return this.underlying.createHmac(args, originator);
930
+ }
931
+ async verifyHmac(args, originator) {
932
+ if (!this.authenticated) {
933
+ throw new Error('User is not authenticated.');
934
+ }
935
+ if (originator === this.adminOriginator) {
936
+ throw new Error('External applications are not allowed to use the admin originator.');
937
+ }
938
+ return this.underlying.verifyHmac(args, originator);
939
+ }
940
+ async createSignature(args, originator) {
941
+ if (!this.authenticated) {
942
+ throw new Error('User is not authenticated.');
943
+ }
944
+ if (originator === this.adminOriginator) {
945
+ throw new Error('External applications are not allowed to use the admin originator.');
946
+ }
947
+ return this.underlying.createSignature(args, originator);
948
+ }
949
+ async verifySignature(args, originator) {
950
+ if (!this.authenticated) {
951
+ throw new Error('User is not authenticated.');
952
+ }
953
+ if (originator === this.adminOriginator) {
954
+ throw new Error('External applications are not allowed to use the admin originator.');
955
+ }
956
+ return this.underlying.verifySignature(args, originator);
957
+ }
958
+ async createAction(args, originator) {
959
+ if (!this.authenticated) {
960
+ throw new Error('User is not authenticated.');
961
+ }
962
+ if (originator === this.adminOriginator) {
963
+ throw new Error('External applications are not allowed to use the admin originator.');
964
+ }
965
+ return this.underlying.createAction(args, originator);
966
+ }
967
+ async signAction(args, originator) {
968
+ if (!this.authenticated) {
969
+ throw new Error('User is not authenticated.');
970
+ }
971
+ if (originator === this.adminOriginator) {
972
+ throw new Error('External applications are not allowed to use the admin originator.');
973
+ }
974
+ return this.underlying.signAction(args, originator);
975
+ }
976
+ async abortAction(args, originator) {
977
+ if (!this.authenticated) {
978
+ throw new Error('User is not authenticated.');
979
+ }
980
+ if (originator === this.adminOriginator) {
981
+ throw new Error('External applications are not allowed to use the admin originator.');
982
+ }
983
+ return this.underlying.abortAction(args, originator);
984
+ }
985
+ async listActions(args, originator) {
986
+ if (!this.authenticated) {
987
+ throw new Error('User is not authenticated.');
988
+ }
989
+ if (originator === this.adminOriginator) {
990
+ throw new Error('External applications are not allowed to use the admin originator.');
991
+ }
992
+ return this.underlying.listActions(args, originator);
993
+ }
994
+ async internalizeAction(args, originator) {
995
+ if (!this.authenticated) {
996
+ throw new Error('User is not authenticated.');
997
+ }
998
+ if (originator === this.adminOriginator) {
999
+ throw new Error('External applications are not allowed to use the admin originator.');
1000
+ }
1001
+ return this.underlying.internalizeAction(args, originator);
1002
+ }
1003
+ async listOutputs(args, originator) {
1004
+ if (!this.authenticated) {
1005
+ throw new Error('User is not authenticated.');
1006
+ }
1007
+ if (originator === this.adminOriginator) {
1008
+ throw new Error('External applications are not allowed to use the admin originator.');
1009
+ }
1010
+ return this.underlying.listOutputs(args, originator);
1011
+ }
1012
+ async relinquishOutput(args, originator) {
1013
+ if (!this.authenticated) {
1014
+ throw new Error('User is not authenticated.');
1015
+ }
1016
+ if (originator === this.adminOriginator) {
1017
+ throw new Error('External applications are not allowed to use the admin originator.');
1018
+ }
1019
+ return this.underlying.relinquishOutput(args, originator);
1020
+ }
1021
+ async acquireCertificate(args, originator) {
1022
+ if (!this.authenticated) {
1023
+ throw new Error('User is not authenticated.');
1024
+ }
1025
+ if (originator === this.adminOriginator) {
1026
+ throw new Error('External applications are not allowed to use the admin originator.');
1027
+ }
1028
+ return this.underlying.acquireCertificate(args, originator);
1029
+ }
1030
+ async listCertificates(args, originator) {
1031
+ if (!this.authenticated) {
1032
+ throw new Error('User is not authenticated.');
1033
+ }
1034
+ if (originator === this.adminOriginator) {
1035
+ throw new Error('External applications are not allowed to use the admin originator.');
1036
+ }
1037
+ return this.underlying.listCertificates(args, originator);
1038
+ }
1039
+ async proveCertificate(args, originator) {
1040
+ if (!this.authenticated) {
1041
+ throw new Error('User is not authenticated.');
1042
+ }
1043
+ if (originator === this.adminOriginator) {
1044
+ throw new Error('External applications are not allowed to use the admin originator.');
1045
+ }
1046
+ return this.underlying.proveCertificate(args, originator);
1047
+ }
1048
+ async relinquishCertificate(args, originator) {
1049
+ if (!this.authenticated) {
1050
+ throw new Error('User is not authenticated.');
1051
+ }
1052
+ if (originator === this.adminOriginator) {
1053
+ throw new Error('External applications are not allowed to use the admin originator.');
1054
+ }
1055
+ return this.underlying.relinquishCertificate(args, originator);
1056
+ }
1057
+ async discoverByIdentityKey(args, originator) {
1058
+ if (!this.authenticated) {
1059
+ throw new Error('User is not authenticated.');
1060
+ }
1061
+ if (originator === this.adminOriginator) {
1062
+ throw new Error('External applications are not allowed to use the admin originator.');
1063
+ }
1064
+ return this.underlying.discoverByIdentityKey(args, originator);
1065
+ }
1066
+ async discoverByAttributes(args, originator) {
1067
+ if (!this.authenticated) {
1068
+ throw new Error('User is not authenticated.');
1069
+ }
1070
+ if (originator === this.adminOriginator) {
1071
+ throw new Error('External applications are not allowed to use the admin originator.');
1072
+ }
1073
+ return this.underlying.discoverByAttributes(args, originator);
1074
+ }
1075
+ async isAuthenticated(_, originator) {
1076
+ if (!this.authenticated) {
1077
+ throw new Error('User is not authenticated.');
1078
+ }
1079
+ if (originator === this.adminOriginator) {
1080
+ throw new Error('External applications are not allowed to use the admin originator.');
1081
+ }
1082
+ return { authenticated: true };
1083
+ }
1084
+ async waitForAuthentication(_, originator) {
1085
+ if (originator === this.adminOriginator) {
1086
+ throw new Error('External applications are not allowed to use the admin originator.');
1087
+ }
1088
+ while (!this.authenticated) {
1089
+ await new Promise(resolve => setTimeout(resolve, 100));
1090
+ }
1091
+ return { authenticated: true };
1092
+ }
1093
+ async getHeight(_, originator) {
1094
+ if (!this.authenticated) {
1095
+ throw new Error('User is not authenticated.');
1096
+ }
1097
+ if (originator === this.adminOriginator) {
1098
+ throw new Error('External applications are not allowed to use the admin originator.');
1099
+ }
1100
+ return this.underlying.getHeight({}, originator);
1101
+ }
1102
+ async getHeaderForHeight(args, originator) {
1103
+ if (!this.authenticated) {
1104
+ throw new Error('User is not authenticated.');
1105
+ }
1106
+ if (originator === this.adminOriginator) {
1107
+ throw new Error('External applications are not allowed to use the admin originator.');
1108
+ }
1109
+ return this.underlying.getHeaderForHeight(args, originator);
1110
+ }
1111
+ async getNetwork(_, originator) {
1112
+ if (!this.authenticated) {
1113
+ throw new Error('User is not authenticated.');
1114
+ }
1115
+ if (originator === this.adminOriginator) {
1116
+ throw new Error('External applications are not allowed to use the admin originator.');
1117
+ }
1118
+ return this.underlying.getNetwork({}, originator);
1119
+ }
1120
+ async getVersion(_, originator) {
1121
+ if (!this.authenticated) {
1122
+ throw new Error('User is not authenticated.');
1123
+ }
1124
+ if (originator === this.adminOriginator) {
1125
+ throw new Error('External applications are not allowed to use the admin originator.');
1126
+ }
1127
+ return this.underlying.getVersion({}, originator);
1128
+ }
1129
+ }
1130
+ exports.CWIStyleWalletManager = CWIStyleWalletManager;
1131
+ //# sourceMappingURL=CWIStyleWalletManager.js.map