@bsv/sdk 2.1.0 → 2.1.1

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 (383) hide show
  1. package/README.md +7 -7
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/src/auth/Peer.js +8 -13
  4. package/dist/cjs/src/auth/Peer.js.map +1 -1
  5. package/dist/cjs/src/auth/SessionManager.js +4 -7
  6. package/dist/cjs/src/auth/SessionManager.js.map +1 -1
  7. package/dist/cjs/src/auth/certificates/MasterCertificate.js +1 -1
  8. package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
  9. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js +1 -1
  10. package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  11. package/dist/cjs/src/auth/clients/AuthFetch.js +32 -32
  12. package/dist/cjs/src/auth/clients/AuthFetch.js.map +1 -1
  13. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js +4 -4
  14. package/dist/cjs/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  15. package/dist/cjs/src/compat/ECIES.js +29 -34
  16. package/dist/cjs/src/compat/ECIES.js.map +1 -1
  17. package/dist/cjs/src/compat/HD.js +9 -4
  18. package/dist/cjs/src/compat/HD.js.map +1 -1
  19. package/dist/cjs/src/compat/Mnemonic.js +12 -12
  20. package/dist/cjs/src/compat/Mnemonic.js.map +1 -1
  21. package/dist/cjs/src/identity/ContactsManager.js +172 -232
  22. package/dist/cjs/src/identity/ContactsManager.js.map +1 -1
  23. package/dist/cjs/src/identity/IdentityClient.js +122 -55
  24. package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
  25. package/dist/cjs/src/kvstore/GlobalKVStore.js +30 -31
  26. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
  27. package/dist/cjs/src/kvstore/LocalKVStore.js +9 -9
  28. package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
  29. package/dist/cjs/src/kvstore/kvStoreInterpreter.js +2 -2
  30. package/dist/cjs/src/kvstore/kvStoreInterpreter.js.map +1 -1
  31. package/dist/cjs/src/messages/SignedMessage.js +1 -1
  32. package/dist/cjs/src/messages/SignedMessage.js.map +1 -1
  33. package/dist/cjs/src/overlay-tools/Historian.js +1 -1
  34. package/dist/cjs/src/overlay-tools/Historian.js.map +1 -1
  35. package/dist/cjs/src/overlay-tools/LookupResolver.js +139 -46
  36. package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
  37. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js +75 -146
  38. package/dist/cjs/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  39. package/dist/cjs/src/primitives/AESGCM.js +2 -2
  40. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  41. package/dist/cjs/src/primitives/BigNumber.js +164 -148
  42. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  43. package/dist/cjs/src/primitives/Curve.js +17 -15
  44. package/dist/cjs/src/primitives/Curve.js.map +1 -1
  45. package/dist/cjs/src/primitives/ECDSA.js +12 -7
  46. package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
  47. package/dist/cjs/src/primitives/Hash.js +140 -56
  48. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  49. package/dist/cjs/src/primitives/JacobianPoint.js +8 -8
  50. package/dist/cjs/src/primitives/JacobianPoint.js.map +1 -1
  51. package/dist/cjs/src/primitives/K256.js +3 -3
  52. package/dist/cjs/src/primitives/K256.js.map +1 -1
  53. package/dist/cjs/src/primitives/Point.js +36 -40
  54. package/dist/cjs/src/primitives/Point.js.map +1 -1
  55. package/dist/cjs/src/primitives/PrivateKey.js +4 -4
  56. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  57. package/dist/cjs/src/primitives/PublicKey.js +4 -4
  58. package/dist/cjs/src/primitives/PublicKey.js.map +1 -1
  59. package/dist/cjs/src/primitives/Random.js +10 -14
  60. package/dist/cjs/src/primitives/Random.js.map +1 -1
  61. package/dist/cjs/src/primitives/ReaderUint8Array.js +6 -6
  62. package/dist/cjs/src/primitives/ReaderUint8Array.js.map +1 -1
  63. package/dist/cjs/src/primitives/Schnorr.js +2 -2
  64. package/dist/cjs/src/primitives/Schnorr.js.map +1 -1
  65. package/dist/cjs/src/primitives/Secp256r1.js +2 -1
  66. package/dist/cjs/src/primitives/Secp256r1.js.map +1 -1
  67. package/dist/cjs/src/primitives/Signature.js +8 -8
  68. package/dist/cjs/src/primitives/Signature.js.map +1 -1
  69. package/dist/cjs/src/primitives/TransactionSignature.js +20 -21
  70. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  71. package/dist/cjs/src/primitives/utils.js +39 -46
  72. package/dist/cjs/src/primitives/utils.js.map +1 -1
  73. package/dist/cjs/src/registry/RegistryClient.js +31 -23
  74. package/dist/cjs/src/registry/RegistryClient.js.map +1 -1
  75. package/dist/cjs/src/remittance/RemittanceManager.js +19 -18
  76. package/dist/cjs/src/remittance/RemittanceManager.js.map +1 -1
  77. package/dist/cjs/src/remittance/modules/BasicBRC29.js.map +1 -1
  78. package/dist/cjs/src/script/Script.js +93 -170
  79. package/dist/cjs/src/script/Script.js.map +1 -1
  80. package/dist/cjs/src/script/ScriptEvaluationError.js +2 -2
  81. package/dist/cjs/src/script/ScriptEvaluationError.js.map +1 -1
  82. package/dist/cjs/src/script/Spend.js +14 -12
  83. package/dist/cjs/src/script/Spend.js.map +1 -1
  84. package/dist/cjs/src/script/templates/PushDrop.js +22 -18
  85. package/dist/cjs/src/script/templates/PushDrop.js.map +1 -1
  86. package/dist/cjs/src/script/templates/RPuzzle.js +2 -4
  87. package/dist/cjs/src/script/templates/RPuzzle.js.map +1 -1
  88. package/dist/cjs/src/storage/StorageDownloader.js +42 -9
  89. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  90. package/dist/cjs/src/totp/totp.js +1 -1
  91. package/dist/cjs/src/totp/totp.js.map +1 -1
  92. package/dist/cjs/src/transaction/Beef.js +239 -192
  93. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  94. package/dist/cjs/src/transaction/BeefConstants.js +19 -0
  95. package/dist/cjs/src/transaction/BeefConstants.js.map +1 -0
  96. package/dist/cjs/src/transaction/BeefTx.js +12 -12
  97. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  98. package/dist/cjs/src/transaction/MerklePath.js +4 -4
  99. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  100. package/dist/cjs/src/transaction/Transaction.js +49 -52
  101. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  102. package/dist/cjs/src/transaction/fee-models/SatoshisPerKilobyte.js +1 -1
  103. package/dist/cjs/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  104. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +9 -9
  105. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  106. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +9 -9
  107. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  108. package/dist/cjs/src/wallet/CachedKeyDeriver.js +1 -1
  109. package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
  110. package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
  111. package/dist/cjs/src/wallet/WalletError.js.map +1 -1
  112. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js +5 -4
  113. package/dist/cjs/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  114. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js +9 -9
  115. package/dist/cjs/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  116. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js +92 -92
  117. package/dist/cjs/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  118. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js +387 -711
  119. package/dist/cjs/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  120. package/dist/cjs/src/wallet/substrates/XDM.js +4 -4
  121. package/dist/cjs/src/wallet/substrates/XDM.js.map +1 -1
  122. package/dist/cjs/src/wallet/substrates/window.CWI.js +2 -2
  123. package/dist/cjs/src/wallet/substrates/window.CWI.js.map +1 -1
  124. package/dist/cjs/src/wallet/validationHelpers.js +9 -9
  125. package/dist/cjs/src/wallet/validationHelpers.js.map +1 -1
  126. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  127. package/dist/esm/src/auth/Peer.js +25 -13
  128. package/dist/esm/src/auth/Peer.js.map +1 -1
  129. package/dist/esm/src/auth/SessionManager.js +4 -7
  130. package/dist/esm/src/auth/SessionManager.js.map +1 -1
  131. package/dist/esm/src/auth/certificates/MasterCertificate.js +1 -1
  132. package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
  133. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js +1 -1
  134. package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -1
  135. package/dist/esm/src/auth/clients/AuthFetch.js +32 -32
  136. package/dist/esm/src/auth/clients/AuthFetch.js.map +1 -1
  137. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js +4 -4
  138. package/dist/esm/src/auth/transports/SimplifiedFetchTransport.js.map +1 -1
  139. package/dist/esm/src/compat/ECIES.js +29 -34
  140. package/dist/esm/src/compat/ECIES.js.map +1 -1
  141. package/dist/esm/src/compat/HD.js +9 -4
  142. package/dist/esm/src/compat/HD.js.map +1 -1
  143. package/dist/esm/src/compat/Mnemonic.js +12 -12
  144. package/dist/esm/src/compat/Mnemonic.js.map +1 -1
  145. package/dist/esm/src/identity/ContactsManager.js +172 -232
  146. package/dist/esm/src/identity/ContactsManager.js.map +1 -1
  147. package/dist/esm/src/identity/IdentityClient.js +122 -55
  148. package/dist/esm/src/identity/IdentityClient.js.map +1 -1
  149. package/dist/esm/src/kvstore/GlobalKVStore.js +30 -31
  150. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
  151. package/dist/esm/src/kvstore/LocalKVStore.js +9 -9
  152. package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
  153. package/dist/esm/src/kvstore/kvStoreInterpreter.js +2 -2
  154. package/dist/esm/src/kvstore/kvStoreInterpreter.js.map +1 -1
  155. package/dist/esm/src/messages/SignedMessage.js +1 -1
  156. package/dist/esm/src/messages/SignedMessage.js.map +1 -1
  157. package/dist/esm/src/overlay-tools/Historian.js +1 -1
  158. package/dist/esm/src/overlay-tools/Historian.js.map +1 -1
  159. package/dist/esm/src/overlay-tools/LookupResolver.js +139 -46
  160. package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
  161. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js +74 -146
  162. package/dist/esm/src/overlay-tools/SHIPBroadcaster.js.map +1 -1
  163. package/dist/esm/src/primitives/AESGCM.js +2 -2
  164. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  165. package/dist/esm/src/primitives/BigNumber.js +167 -154
  166. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  167. package/dist/esm/src/primitives/Curve.js +17 -15
  168. package/dist/esm/src/primitives/Curve.js.map +1 -1
  169. package/dist/esm/src/primitives/ECDSA.js +12 -7
  170. package/dist/esm/src/primitives/ECDSA.js.map +1 -1
  171. package/dist/esm/src/primitives/Hash.js +140 -56
  172. package/dist/esm/src/primitives/Hash.js.map +1 -1
  173. package/dist/esm/src/primitives/JacobianPoint.js +8 -8
  174. package/dist/esm/src/primitives/JacobianPoint.js.map +1 -1
  175. package/dist/esm/src/primitives/K256.js +3 -3
  176. package/dist/esm/src/primitives/K256.js.map +1 -1
  177. package/dist/esm/src/primitives/Point.js +36 -40
  178. package/dist/esm/src/primitives/Point.js.map +1 -1
  179. package/dist/esm/src/primitives/PrivateKey.js +4 -4
  180. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  181. package/dist/esm/src/primitives/PublicKey.js +4 -4
  182. package/dist/esm/src/primitives/PublicKey.js.map +1 -1
  183. package/dist/esm/src/primitives/Random.js +10 -14
  184. package/dist/esm/src/primitives/Random.js.map +1 -1
  185. package/dist/esm/src/primitives/ReaderUint8Array.js +6 -6
  186. package/dist/esm/src/primitives/ReaderUint8Array.js.map +1 -1
  187. package/dist/esm/src/primitives/Schnorr.js +1 -1
  188. package/dist/esm/src/primitives/Schnorr.js.map +1 -1
  189. package/dist/esm/src/primitives/Secp256r1.js +2 -1
  190. package/dist/esm/src/primitives/Secp256r1.js.map +1 -1
  191. package/dist/esm/src/primitives/Signature.js +8 -8
  192. package/dist/esm/src/primitives/Signature.js.map +1 -1
  193. package/dist/esm/src/primitives/TransactionSignature.js +20 -21
  194. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  195. package/dist/esm/src/primitives/utils.js +39 -48
  196. package/dist/esm/src/primitives/utils.js.map +1 -1
  197. package/dist/esm/src/registry/RegistryClient.js +31 -23
  198. package/dist/esm/src/registry/RegistryClient.js.map +1 -1
  199. package/dist/esm/src/remittance/RemittanceManager.js +19 -18
  200. package/dist/esm/src/remittance/RemittanceManager.js.map +1 -1
  201. package/dist/esm/src/remittance/modules/BasicBRC29.js.map +1 -1
  202. package/dist/esm/src/script/Script.js +93 -170
  203. package/dist/esm/src/script/Script.js.map +1 -1
  204. package/dist/esm/src/script/ScriptEvaluationError.js +2 -2
  205. package/dist/esm/src/script/ScriptEvaluationError.js.map +1 -1
  206. package/dist/esm/src/script/Spend.js +14 -12
  207. package/dist/esm/src/script/Spend.js.map +1 -1
  208. package/dist/esm/src/script/templates/PushDrop.js +4 -3
  209. package/dist/esm/src/script/templates/PushDrop.js.map +1 -1
  210. package/dist/esm/src/script/templates/RPuzzle.js +2 -4
  211. package/dist/esm/src/script/templates/RPuzzle.js.map +1 -1
  212. package/dist/esm/src/storage/StorageDownloader.js +1 -1
  213. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  214. package/dist/esm/src/totp/totp.js +1 -1
  215. package/dist/esm/src/totp/totp.js.map +1 -1
  216. package/dist/esm/src/transaction/Beef.js +229 -186
  217. package/dist/esm/src/transaction/Beef.js.map +1 -1
  218. package/dist/esm/src/transaction/BeefConstants.js +16 -0
  219. package/dist/esm/src/transaction/BeefConstants.js.map +1 -0
  220. package/dist/esm/src/transaction/BeefTx.js +3 -3
  221. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  222. package/dist/esm/src/transaction/MerklePath.js +4 -4
  223. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  224. package/dist/esm/src/transaction/Transaction.js +49 -52
  225. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  226. package/dist/esm/src/transaction/fee-models/SatoshisPerKilobyte.js +1 -1
  227. package/dist/esm/src/transaction/fee-models/SatoshisPerKilobyte.js.map +1 -1
  228. package/dist/esm/src/transaction/http/BinaryFetchClient.js +9 -9
  229. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  230. package/dist/esm/src/transaction/http/DefaultHttpClient.js +9 -9
  231. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  232. package/dist/esm/src/wallet/CachedKeyDeriver.js +1 -1
  233. package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
  234. package/dist/esm/src/wallet/WalletClient.js.map +1 -1
  235. package/dist/esm/src/wallet/WalletError.js.map +1 -1
  236. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js +5 -4
  237. package/dist/esm/src/wallet/substrates/HTTPWalletJSON.js.map +1 -1
  238. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js +9 -9
  239. package/dist/esm/src/wallet/substrates/ReactNativeWebView.js.map +1 -1
  240. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js +92 -92
  241. package/dist/esm/src/wallet/substrates/WalletWireProcessor.js.map +1 -1
  242. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js +387 -711
  243. package/dist/esm/src/wallet/substrates/WalletWireTransceiver.js.map +1 -1
  244. package/dist/esm/src/wallet/substrates/XDM.js +4 -4
  245. package/dist/esm/src/wallet/substrates/XDM.js.map +1 -1
  246. package/dist/esm/src/wallet/substrates/window.CWI.js +2 -2
  247. package/dist/esm/src/wallet/substrates/window.CWI.js.map +1 -1
  248. package/dist/esm/src/wallet/validationHelpers.js +9 -9
  249. package/dist/esm/src/wallet/validationHelpers.js.map +1 -1
  250. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  251. package/dist/types/src/auth/Peer.d.ts +13 -0
  252. package/dist/types/src/auth/Peer.d.ts.map +1 -1
  253. package/dist/types/src/auth/SessionManager.d.ts.map +1 -1
  254. package/dist/types/src/auth/clients/AuthFetch.d.ts.map +1 -1
  255. package/dist/types/src/compat/ECIES.d.ts.map +1 -1
  256. package/dist/types/src/compat/HD.d.ts.map +1 -1
  257. package/dist/types/src/identity/ContactsManager.d.ts +18 -0
  258. package/dist/types/src/identity/ContactsManager.d.ts.map +1 -1
  259. package/dist/types/src/identity/IdentityClient.d.ts +47 -8
  260. package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
  261. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -1
  262. package/dist/types/src/overlay-tools/LookupResolver.d.ts +59 -1
  263. package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
  264. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts +18 -3
  265. package/dist/types/src/overlay-tools/SHIPBroadcaster.d.ts.map +1 -1
  266. package/dist/types/src/primitives/BigNumber.d.ts +13 -3
  267. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  268. package/dist/types/src/primitives/Curve.d.ts.map +1 -1
  269. package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
  270. package/dist/types/src/primitives/Hash.d.ts +3 -3
  271. package/dist/types/src/primitives/Hash.d.ts.map +1 -1
  272. package/dist/types/src/primitives/JacobianPoint.d.ts +3 -1
  273. package/dist/types/src/primitives/JacobianPoint.d.ts.map +1 -1
  274. package/dist/types/src/primitives/Point.d.ts.map +1 -1
  275. package/dist/types/src/primitives/Random.d.ts +2 -2
  276. package/dist/types/src/primitives/Random.d.ts.map +1 -1
  277. package/dist/types/src/primitives/ReaderUint8Array.d.ts.map +1 -1
  278. package/dist/types/src/primitives/Schnorr.d.ts +2 -1
  279. package/dist/types/src/primitives/Schnorr.d.ts.map +1 -1
  280. package/dist/types/src/primitives/Secp256r1.d.ts.map +1 -1
  281. package/dist/types/src/primitives/utils.d.ts +2 -4
  282. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  283. package/dist/types/src/registry/RegistryClient.d.ts.map +1 -1
  284. package/dist/types/src/remittance/RemittanceManager.d.ts.map +1 -1
  285. package/dist/types/src/remittance/modules/BasicBRC29.d.ts.map +1 -1
  286. package/dist/types/src/script/Script.d.ts +15 -8
  287. package/dist/types/src/script/Script.d.ts.map +1 -1
  288. package/dist/types/src/script/Spend.d.ts.map +1 -1
  289. package/dist/types/src/script/templates/PushDrop.d.ts +3 -1
  290. package/dist/types/src/script/templates/PushDrop.d.ts.map +1 -1
  291. package/dist/types/src/script/templates/RPuzzle.d.ts.map +1 -1
  292. package/dist/types/src/transaction/Beef.d.ts +46 -8
  293. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  294. package/dist/types/src/transaction/BeefConstants.d.ts +15 -0
  295. package/dist/types/src/transaction/BeefConstants.d.ts.map +1 -0
  296. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  297. package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -1
  298. package/dist/types/src/wallet/KeyDeriver.d.ts +1 -1
  299. package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -1
  300. package/dist/types/src/wallet/Wallet.interfaces.d.ts +2 -2
  301. package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
  302. package/dist/types/src/wallet/WalletClient.d.ts +7 -7
  303. package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
  304. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts +7 -7
  305. package/dist/types/src/wallet/substrates/HTTPWalletJSON.d.ts.map +1 -1
  306. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts +36 -7
  307. package/dist/types/src/wallet/substrates/WalletWireTransceiver.d.ts.map +1 -1
  308. package/dist/types/src/wallet/substrates/window.CWI.d.ts +8 -8
  309. package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -1
  310. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  311. package/dist/umd/bundle.js +3 -3
  312. package/package.json +1 -1
  313. package/src/auth/Peer.ts +26 -13
  314. package/src/auth/SessionManager.ts +4 -7
  315. package/src/auth/certificates/MasterCertificate.ts +1 -1
  316. package/src/auth/certificates/__tests/CompletedProtoWallet.ts +1 -1
  317. package/src/auth/clients/AuthFetch.ts +41 -41
  318. package/src/auth/transports/SimplifiedFetchTransport.ts +4 -4
  319. package/src/compat/ECIES.ts +29 -34
  320. package/src/compat/HD.ts +10 -5
  321. package/src/compat/Mnemonic.ts +11 -11
  322. package/src/compat/__tests/HD.test.ts +19 -0
  323. package/src/identity/ContactsManager.ts +194 -257
  324. package/src/identity/IdentityClient.ts +155 -66
  325. package/src/identity/__tests/IdentityClient.test.ts +25 -1
  326. package/src/kvstore/GlobalKVStore.ts +31 -32
  327. package/src/kvstore/LocalKVStore.ts +8 -8
  328. package/src/kvstore/kvStoreInterpreter.ts +2 -2
  329. package/src/messages/SignedMessage.ts +1 -1
  330. package/src/overlay-tools/Historian.ts +1 -1
  331. package/src/overlay-tools/LookupResolver.ts +182 -45
  332. package/src/overlay-tools/SHIPBroadcaster.ts +92 -168
  333. package/src/primitives/AESGCM.ts +2 -2
  334. package/src/primitives/BigNumber.ts +122 -113
  335. package/src/primitives/Curve.ts +16 -15
  336. package/src/primitives/ECDSA.ts +10 -8
  337. package/src/primitives/Hash.ts +152 -53
  338. package/src/primitives/JacobianPoint.ts +13 -11
  339. package/src/primitives/K256.ts +3 -3
  340. package/src/primitives/Point.ts +35 -38
  341. package/src/primitives/PrivateKey.ts +3 -3
  342. package/src/primitives/PublicKey.ts +3 -3
  343. package/src/primitives/Random.ts +11 -14
  344. package/src/primitives/ReaderUint8Array.ts +7 -7
  345. package/src/primitives/Schnorr.ts +2 -1
  346. package/src/primitives/Secp256r1.ts +2 -1
  347. package/src/primitives/Signature.ts +8 -8
  348. package/src/primitives/TransactionSignature.ts +16 -16
  349. package/src/primitives/utils.ts +37 -47
  350. package/src/registry/RegistryClient.ts +25 -25
  351. package/src/remittance/RemittanceManager.ts +17 -18
  352. package/src/remittance/modules/BasicBRC29.ts +2 -5
  353. package/src/script/Script.ts +114 -170
  354. package/src/script/ScriptEvaluationError.ts +2 -2
  355. package/src/script/Spend.ts +14 -15
  356. package/src/script/templates/PushDrop.ts +5 -3
  357. package/src/script/templates/RPuzzle.ts +2 -4
  358. package/src/storage/StorageDownloader.ts +1 -1
  359. package/src/totp/totp.ts +1 -1
  360. package/src/transaction/Beef.ts +241 -203
  361. package/src/transaction/BeefConstants.ts +16 -0
  362. package/src/transaction/BeefTx.ts +3 -3
  363. package/src/transaction/MerklePath.ts +4 -4
  364. package/src/transaction/Transaction.ts +48 -51
  365. package/src/transaction/fee-models/SatoshisPerKilobyte.ts +1 -1
  366. package/src/transaction/http/BinaryFetchClient.ts +8 -8
  367. package/src/transaction/http/DefaultHttpClient.ts +8 -8
  368. package/src/wallet/CachedKeyDeriver.ts +8 -6
  369. package/src/wallet/KeyDeriver.ts +1 -1
  370. package/src/wallet/Wallet.interfaces.ts +2 -4
  371. package/src/wallet/WalletClient.ts +8 -8
  372. package/src/wallet/WalletError.ts +1 -1
  373. package/src/wallet/__tests/WalletClient.substrate.test.ts +10 -6
  374. package/src/wallet/substrates/HTTPWalletJSON.ts +22 -21
  375. package/src/wallet/substrates/ReactNativeWebView.ts +9 -9
  376. package/src/wallet/substrates/WalletWireProcessor.ts +83 -83
  377. package/src/wallet/substrates/WalletWireTransceiver.ts +528 -938
  378. package/src/wallet/substrates/XDM.ts +4 -4
  379. package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +38 -25
  380. package/src/wallet/substrates/__tests/ReactNativeWebView.test.ts +174 -0
  381. package/src/wallet/substrates/__tests/window.CWI.test.ts +256 -0
  382. package/src/wallet/substrates/window.CWI.ts +10 -10
  383. package/src/wallet/validationHelpers.ts +9 -9
@@ -394,6 +394,25 @@ describe('HD', () => {
394
394
  })
395
395
  })
396
396
 
397
+ describe('#derive path validation', () => {
398
+ it('should derive mixed hardened and non-hardened path segments', () => {
399
+ const bip32 = HD.fromString(vector1mPrivate)
400
+ expect(() => bip32.derive("m/0'/0'/2/3/4'/4")).not.toThrow()
401
+ })
402
+
403
+ it('should reject apostrophes that are not trailing hardening markers', () => {
404
+ const bip32 = HD.fromString(vector1mPrivate)
405
+ expect(() => bip32.derive("m/1'2")).toThrow('invalid path')
406
+ expect(() => bip32.derive("m/1''")).toThrow('invalid path')
407
+ })
408
+
409
+ it('should reject child indexes outside the non-hardened range', () => {
410
+ const bip32 = HD.fromString(vector1mPrivate)
411
+ expect(() => bip32.derive('m/2147483648')).toThrow('invalid path')
412
+ expect(() => bip32.derive("m/2147483648'")).toThrow('invalid path')
413
+ })
414
+ })
415
+
397
416
  describe('#toString', () => {
398
417
  const bip32 = new HD()
399
418
  bip32.fromRandom()
@@ -47,55 +47,61 @@ export class ContactsManager {
47
47
  * @returns A promise that resolves with an array of contacts
48
48
  */
49
49
  async getContacts (identityKey?: PubKeyHex, forceRefresh = false, limit = 1000): Promise<Contact[]> {
50
- // Check in-memory cache first unless forcing refresh
51
50
  if (!forceRefresh) {
52
- const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
53
- if (cached != null && cached !== '') {
54
- try {
55
- const cachedContacts: Contact[] = JSON.parse(cached)
56
- return identityKey != null
57
- ? cachedContacts.filter(c => c.identityKey === identityKey)
58
- : cachedContacts
59
- } catch (e) {
60
- console.warn('Invalid cached contacts JSON; will reload from chain', e)
61
- }
62
- }
51
+ const fromCache = this.loadCachedContacts(identityKey)
52
+ if (fromCache !== null) return fromCache
63
53
  }
64
54
 
65
- const tags: string[] = []
66
- if (identityKey != null) {
67
- // Hash the identity key to use as a tag for quick lookup
68
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
69
- protocolID: CONTACT_PROTOCOL_ID,
70
- keyID: identityKey,
71
- counterparty: 'self',
72
- data: Utils.toArray(identityKey, 'utf8')
73
- }, this.originator)
74
- tags.push(`identityKey ${Utils.toHex(hashedIdentityKey)}`)
75
- }
76
-
77
- // Get all contact outputs from the contacts basket
78
- const outputs = await this.wallet.listOutputs({
79
- basket: 'contacts',
80
- include: 'locking scripts',
81
- includeCustomInstructions: true,
82
- tags,
83
- limit
84
- }, this.originator)
55
+ const tags = await this.buildIdentityKeyTags(identityKey)
56
+ const outputs = await this.wallet.listOutputs(
57
+ { basket: 'contacts', include: 'locking scripts', includeCustomInstructions: true, tags, limit },
58
+ this.originator
59
+ )
85
60
 
86
61
  if (outputs.outputs == null || outputs.outputs.length === 0) {
87
62
  this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify([]))
88
63
  return []
89
64
  }
90
65
 
91
- // Pre-process outputs synchronously to extract decode data, then decrypt in parallel
92
- const decryptTasks: Array<{ keyID: string, ciphertext: number[] }> = []
66
+ const contacts = await this.decryptContactOutputs(outputs.outputs)
67
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
68
+ return identityKey != null ? contacts.filter(c => c.identityKey === identityKey) : contacts
69
+ }
93
70
 
94
- for (const output of outputs.outputs) {
71
+ /** Returns cached contacts (optionally filtered) or null if cache is missing/invalid. */
72
+ private loadCachedContacts (identityKey?: PubKeyHex): Contact[] | null {
73
+ const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
74
+ if (cached == null || cached === '') return null
75
+ try {
76
+ const cachedContacts: Contact[] = JSON.parse(cached)
77
+ return identityKey != null ? cachedContacts.filter(c => c.identityKey === identityKey) : cachedContacts
78
+ } catch (e) {
79
+ console.warn('Invalid cached contacts JSON; will reload from chain', e)
80
+ return null
81
+ }
82
+ }
83
+
84
+ /** Builds the HMAC-based identity-key tag array; empty array if no identity key is given. */
85
+ private async buildIdentityKeyTags (identityKey?: PubKeyHex): Promise<string[]> {
86
+ if (identityKey == null) return []
87
+ const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
88
+ protocolID: CONTACT_PROTOCOL_ID,
89
+ keyID: identityKey,
90
+ counterparty: 'self',
91
+ data: Utils.toArray(identityKey, 'utf8')
92
+ }, this.originator)
93
+ return [`identityKey ${Utils.toHex(hashedIdentityKey)}`]
94
+ }
95
+
96
+ /** Decodes and decrypts all contact outputs in parallel, returning valid Contact objects. */
97
+ private async decryptContactOutputs (
98
+ rawOutputs: Awaited<ReturnType<WalletInterface['listOutputs']>>['outputs']
99
+ ): Promise<Contact[]> {
100
+ const decryptTasks: Array<{ keyID: string, ciphertext: number[] }> = []
101
+ for (const output of rawOutputs) {
95
102
  try {
96
- if (output.lockingScript == null) continue
103
+ if (output.lockingScript == null || output.customInstructions == null) continue
97
104
  const decoded = PushDrop.decode(LockingScript.fromHex(output.lockingScript))
98
- if (output.customInstructions == null) continue
99
105
  const keyID = JSON.parse(output.customInstructions).keyID
100
106
  decryptTasks.push({ keyID, ciphertext: decoded.fields[0] })
101
107
  } catch (error) {
@@ -103,25 +109,17 @@ export class ContactsManager {
103
109
  }
104
110
  }
105
111
 
106
- // Decrypt all contacts in parallel — each call is a network round-trip over localhost
107
112
  const decryptResults = await Promise.allSettled(
108
113
  decryptTasks.map(async task =>
109
- await this.wallet.decrypt({
110
- ciphertext: task.ciphertext,
111
- protocolID: CONTACT_PROTOCOL_ID,
112
- keyID: task.keyID,
113
- counterparty: 'self'
114
- }, this.originator)
114
+ await this.wallet.decrypt({ ciphertext: task.ciphertext, protocolID: CONTACT_PROTOCOL_ID, keyID: task.keyID, counterparty: 'self' }, this.originator)
115
115
  )
116
116
  )
117
117
 
118
118
  const contacts: Contact[] = []
119
- for (let i = 0; i < decryptResults.length; i++) {
120
- const result = decryptResults[i]
119
+ for (const result of decryptResults) {
121
120
  if (result.status === 'fulfilled') {
122
121
  try {
123
- const contactData: Contact = JSON.parse(Utils.toUTF8(result.value.plaintext))
124
- contacts.push(contactData)
122
+ contacts.push(JSON.parse(Utils.toUTF8(result.value.plaintext)) as Contact)
125
123
  } catch (error) {
126
124
  console.warn('ContactsManager: Failed to parse contact data:', error)
127
125
  }
@@ -129,13 +127,7 @@ export class ContactsManager {
129
127
  console.warn('ContactsManager: Failed to decrypt contact output:', result.reason)
130
128
  }
131
129
  }
132
-
133
- // Cache the loaded contacts
134
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
135
- const filteredContacts = identityKey != null
136
- ? contacts.filter(c => c.identityKey === identityKey)
137
- : contacts
138
- return filteredContacts
130
+ return contacts
139
131
  }
140
132
 
141
133
  /**
@@ -144,152 +136,123 @@ export class ContactsManager {
144
136
  * @param metadata Optional metadata to store with the contact (ex. notes, aliases, etc)
145
137
  */
146
138
  async saveContact (contact: DisplayableIdentity, metadata?: Record<string, any>): Promise<void> {
147
- // Get current contacts from cache or blockchain
148
139
  const cached = this.cache.getItem(this.CONTACTS_CACHE_KEY)
149
- let contacts: Contact[]
150
- if (cached != null && cached !== '') {
151
- contacts = JSON.parse(cached)
152
- } else {
153
- // If cache is empty, get current data from blockchain
154
- contacts = await this.getContacts()
155
- }
156
-
140
+ const contacts: Contact[] = (cached != null && cached !== '') ? JSON.parse(cached) : await this.getContacts()
141
+ const contactToStore: Contact = { ...contact, metadata }
157
142
  const existingIndex = contacts.findIndex(c => c.identityKey === contact.identityKey)
158
- const contactToStore: Contact = {
159
- ...contact,
160
- metadata
161
- }
143
+ if (existingIndex >= 0) contacts[existingIndex] = contactToStore
144
+ else contacts.push(contactToStore)
145
+
146
+ const hashedIdentityKey = await this.hashIdentityKey(contact.identityKey)
147
+ const outputs = await this.wallet.listOutputs({
148
+ basket: 'contacts', include: 'entire transactions', includeCustomInstructions: true,
149
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], limit: 100
150
+ }, this.originator)
151
+
152
+ const { existingOutput, keyID } = await this.findExistingOutput(outputs, contact.identityKey)
153
+ const lockingScript = await this.encryptAndLock(contactToStore, keyID)
162
154
 
163
- if (existingIndex >= 0) {
164
- contacts[existingIndex] = contactToStore
155
+ if (existingOutput != null) {
156
+ await this.updateContactOutput(outputs, existingOutput, lockingScript, keyID, hashedIdentityKey, contact)
165
157
  } else {
166
- contacts.push(contactToStore)
158
+ await this.createContactOutput(lockingScript, keyID, hashedIdentityKey, contact)
167
159
  }
160
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
161
+ }
168
162
 
169
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
170
- protocolID: CONTACT_PROTOCOL_ID,
171
- keyID: contact.identityKey,
172
- counterparty: 'self',
173
- data: Utils.toArray(contact.identityKey, 'utf8')
174
- }, this.originator)
175
-
176
- // Check if this contact already exists (to update it)
177
- const outputs = await this.wallet.listOutputs({
178
- basket: 'contacts',
179
- include: 'entire transactions',
180
- includeCustomInstructions: true,
181
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
182
- limit: 100 // Should only be one contact!
163
+ /** Computes the HMAC-based hash of an identity key for tag indexing. */
164
+ private async hashIdentityKey (identityKey: string): Promise<number[]> {
165
+ const { hmac } = await this.wallet.createHmac({
166
+ protocolID: CONTACT_PROTOCOL_ID, keyID: identityKey, counterparty: 'self',
167
+ data: Utils.toArray(identityKey, 'utf8')
183
168
  }, this.originator)
169
+ return hmac
170
+ }
184
171
 
172
+ /** Scans existing outputs to find the one matching the given identity key; returns output + keyID. */
173
+ private async findExistingOutput (
174
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
175
+ identityKey: string
176
+ ): Promise<{ existingOutput: any, keyID: string }> {
185
177
  let existingOutput: any = null
186
178
  let keyID = Utils.toBase64(Random(32))
187
- if (outputs.outputs != null) {
188
- // Find output by trying to decrypt and checking identityKey in payload
189
- for (const output of outputs.outputs) {
190
- try {
191
- const [txid, outputIndex] = output.outpoint.split('.')
192
- const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
193
- const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
194
- if (output.customInstructions == null) continue
195
- keyID = JSON.parse(output.customInstructions).keyID
196
-
197
- const { plaintext } = await this.wallet.decrypt({
198
- ciphertext: decoded.fields[0],
199
- protocolID: CONTACT_PROTOCOL_ID,
200
- keyID,
201
- counterparty: 'self'
202
- }, this.originator)
203
-
204
- const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
205
- if (storedContact.identityKey === contact.identityKey) {
206
- // Found the right output
207
- existingOutput = output
208
- break
209
- }
210
- } catch (e) {
211
- // Skip malformed or undecryptable outputs
212
- }
213
- }
179
+ if (outputs.outputs == null) return { existingOutput, keyID }
180
+ for (const output of outputs.outputs) {
181
+ try {
182
+ const [txid, outputIndex] = output.outpoint.split('.')
183
+ const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
184
+ const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
185
+ if (output.customInstructions == null) continue
186
+ keyID = JSON.parse(output.customInstructions).keyID
187
+ const { plaintext } = await this.wallet.decrypt(
188
+ { ciphertext: decoded.fields[0], protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self' }, this.originator
189
+ )
190
+ const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
191
+ if (storedContact.identityKey === identityKey) { existingOutput = output; break }
192
+ } catch (_malformedOrUndecryptableOutput) { /* skip */ }
214
193
  }
194
+ return { existingOutput, keyID }
195
+ }
215
196
 
216
- // Encrypt the contact data directly
217
- const contactWithMetadata: Contact = {
218
- ...contact,
219
- metadata
220
- }
197
+ /** Encrypts a contact and produces its PushDrop locking script. */
198
+ private async encryptAndLock (contactData: Contact, keyID: string): Promise<LockingScript> {
221
199
  const { ciphertext } = await this.wallet.encrypt({
222
- plaintext: Utils.toArray(JSON.stringify(contactWithMetadata), 'utf8'),
223
- protocolID: CONTACT_PROTOCOL_ID,
224
- keyID,
225
- counterparty: 'self'
200
+ plaintext: Utils.toArray(JSON.stringify(contactData), 'utf8'),
201
+ protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self'
226
202
  }, this.originator)
203
+ return await new PushDrop(this.wallet, this.originator).lock([ciphertext], CONTACT_PROTOCOL_ID, keyID, 'self')
204
+ }
227
205
 
228
- // Create locking script for the new contact token
229
- const lockingScript = await new PushDrop(this.wallet, this.originator).lock(
230
- [ciphertext],
231
- CONTACT_PROTOCOL_ID,
232
- keyID,
233
- 'self'
234
- )
235
-
236
- if (existingOutput != null) {
237
- // Update existing contact by spending its output
238
- const [txid, outputIndex] = String(existingOutput.outpoint).split('.')
239
- const prevOutpoint = `${txid}.${outputIndex}` as const
240
-
241
- const pushdrop = new PushDrop(this.wallet, this.originator)
242
- const { signableTransaction } = await this.wallet.createAction({
243
- description: 'Update Contact',
244
- inputBEEF: outputs.BEEF as number[],
245
- inputs: [{
246
- outpoint: prevOutpoint,
247
- unlockingScriptLength: 74,
248
- inputDescription: 'Spend previous contact output'
249
- }],
250
- outputs: [{
251
- basket: 'contacts',
252
- satoshis: 1,
253
- lockingScript: lockingScript.toHex(),
254
- outputDescription: `Updated Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
255
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
256
- customInstructions: JSON.stringify({ keyID })
257
- }],
258
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
259
- }, this.originator)
260
-
261
- if (signableTransaction == null) throw new Error('Unable to update contact')
262
-
263
- const unlocker = pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
264
- const unlockingScript = await unlocker.sign(
265
- Transaction.fromBEEF(signableTransaction.tx),
266
- 0
267
- )
268
-
269
- const { tx } = await this.wallet.signAction({
270
- reference: signableTransaction.reference,
271
- spends: { 0: { unlockingScript: unlockingScript.toHex() } }
272
- }, this.originator)
273
-
274
- if (tx == null) throw new Error('Failed to update contact output')
275
- } else {
276
- // Create new contact output
277
- const { tx } = await this.wallet.createAction({
278
- description: 'Add Contact',
279
- outputs: [{
280
- basket: 'contacts',
281
- satoshis: 1,
282
- lockingScript: lockingScript.toHex(),
283
- outputDescription: `Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
284
- tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`],
285
- customInstructions: JSON.stringify({ keyID })
286
- }],
287
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
288
- }, this.originator)
206
+ /** Spends an existing contact output and creates a replacement with updated data. */
207
+ private async updateContactOutput (
208
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
209
+ existingOutput: any,
210
+ lockingScript: LockingScript,
211
+ keyID: string,
212
+ hashedIdentityKey: number[],
213
+ contact: DisplayableIdentity
214
+ ): Promise<void> {
215
+ const [txid, outputIndex] = String(existingOutput.outpoint).split('.')
216
+ const prevOutpoint = `${txid}.${outputIndex}` as const
217
+ const pushdrop = new PushDrop(this.wallet, this.originator)
218
+ const { signableTransaction } = await this.wallet.createAction({
219
+ description: 'Update Contact',
220
+ inputBEEF: outputs.BEEF as number[],
221
+ inputs: [{ outpoint: prevOutpoint, unlockingScriptLength: 74, inputDescription: 'Spend previous contact output' }],
222
+ outputs: [{
223
+ basket: 'contacts', satoshis: 1, lockingScript: lockingScript.toHex(),
224
+ outputDescription: `Updated Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
225
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], customInstructions: JSON.stringify({ keyID })
226
+ }],
227
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
228
+ }, this.originator)
229
+ if (signableTransaction == null) throw new Error('Unable to update contact')
230
+ const unlockingScript = await pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
231
+ .sign(Transaction.fromBEEF(signableTransaction.tx), 0)
232
+ const { tx } = await this.wallet.signAction({
233
+ reference: signableTransaction.reference,
234
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
235
+ }, this.originator)
236
+ if (tx == null) throw new Error('Failed to update contact output')
237
+ }
289
238
 
290
- if (tx == null) throw new Error('Failed to create contact output')
291
- }
292
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts))
239
+ /** Creates a new on-chain contact output. */
240
+ private async createContactOutput (
241
+ lockingScript: LockingScript,
242
+ keyID: string,
243
+ hashedIdentityKey: number[],
244
+ contact: DisplayableIdentity
245
+ ): Promise<void> {
246
+ const { tx } = await this.wallet.createAction({
247
+ description: 'Add Contact',
248
+ outputs: [{
249
+ basket: 'contacts', satoshis: 1, lockingScript: lockingScript.toHex(),
250
+ outputDescription: `Contact: ${contact.name ?? contact.identityKey.slice(0, 10)}`,
251
+ tags: [`identityKey ${Utils.toHex(hashedIdentityKey)}`], customInstructions: JSON.stringify({ keyID })
252
+ }],
253
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
254
+ }, this.originator)
255
+ if (tx == null) throw new Error('Failed to create contact output')
293
256
  }
294
257
 
295
258
  /**
@@ -302,87 +265,61 @@ export class ContactsManager {
302
265
  if (cached != null && cached !== '') {
303
266
  try {
304
267
  const contacts: Contact[] = JSON.parse(cached)
305
- const filteredContacts = contacts.filter(c => c.identityKey !== identityKey)
306
- this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(filteredContacts))
268
+ this.cache.setItem(this.CONTACTS_CACHE_KEY, JSON.stringify(contacts.filter(c => c.identityKey !== identityKey)))
307
269
  } catch (e) {
308
270
  console.warn('Failed to update cache after contact removal:', e)
309
271
  }
310
272
  }
311
273
 
312
- // Hash the identity key to use as a tag for quick lookup
313
- const tags: string[] = []
314
- const { hmac: hashedIdentityKey } = await this.wallet.createHmac({
315
- protocolID: CONTACT_PROTOCOL_ID,
316
- keyID: identityKey,
317
- counterparty: 'self',
318
- data: Utils.toArray(identityKey, 'utf8')
319
- }, this.originator)
320
- tags.push(`identityKey ${Utils.toHex(hashedIdentityKey)}`)
321
-
322
- // Find and spend the contact's output
323
- const outputs = await this.wallet.listOutputs({
324
- basket: 'contacts',
325
- include: 'entire transactions',
326
- includeCustomInstructions: true,
327
- tags,
328
- limit: 100 // Should only be one contact!
329
- }, this.originator)
330
-
274
+ const tags = await this.buildIdentityKeyTags(identityKey)
275
+ const outputs = await this.wallet.listOutputs(
276
+ { basket: 'contacts', include: 'entire transactions', includeCustomInstructions: true, tags, limit: 100 },
277
+ this.originator
278
+ )
331
279
  if (outputs.outputs == null) return
332
280
 
333
- // Find the output for this specific contact by decrypting and checking identityKey
334
281
  for (const output of outputs.outputs) {
335
282
  try {
336
- const [txid, outputIndex] = String(output.outpoint).split('.')
337
- const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
338
- const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
339
- if (output.customInstructions == null) continue
340
- const keyID = JSON.parse(output.customInstructions).keyID
341
-
342
- const { plaintext } = await this.wallet.decrypt({
343
- ciphertext: decoded.fields[0],
344
- protocolID: CONTACT_PROTOCOL_ID,
345
- keyID,
346
- counterparty: 'self'
347
- }, this.originator)
348
-
349
- const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
350
- if (storedContact.identityKey === identityKey) {
351
- // Found the contact's output, spend it without creating a new one
352
- const prevOutpoint = `${txid}.${outputIndex}` as const
353
-
354
- const pushdrop = new PushDrop(this.wallet, this.originator)
355
- const { signableTransaction } = await this.wallet.createAction({
356
- description: 'Delete Contact',
357
- inputBEEF: outputs.BEEF as number[],
358
- inputs: [{
359
- outpoint: prevOutpoint,
360
- unlockingScriptLength: 74,
361
- inputDescription: 'Spend contact output to delete'
362
- }],
363
- outputs: [], // No outputs = deletion
364
- options: { acceptDelayedBroadcast: false, randomizeOutputs: false } // TODO: Support custom config as needed.
365
- }, this.originator)
366
-
367
- if (signableTransaction == null) throw new Error('Unable to delete contact')
368
-
369
- const unlocker = pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
370
- const unlockingScript = await unlocker.sign(
371
- Transaction.fromBEEF(signableTransaction.tx),
372
- 0
373
- )
374
-
375
- const { tx: deleteTx } = await this.wallet.signAction({
376
- reference: signableTransaction.reference,
377
- spends: { 0: { unlockingScript: unlockingScript.toHex() } }
378
- }, this.originator)
379
-
380
- if (deleteTx == null) throw new Error('Failed to delete contact output')
381
- return
382
- }
383
- } catch (e) {
384
- // Skip malformed or undecryptable outputs
385
- }
283
+ const spent = await this.trySpendContactOutput(output, outputs, identityKey)
284
+ if (spent) return
285
+ } catch (_malformedOrUndecryptableOutput) { /* skip */ }
386
286
  }
387
287
  }
288
+
289
+ /** Attempts to decrypt and spend a single output if it matches the given identity key. Returns true if spent. */
290
+ private async trySpendContactOutput (
291
+ output: Awaited<ReturnType<WalletInterface['listOutputs']>>['outputs'][number],
292
+ outputs: Awaited<ReturnType<WalletInterface['listOutputs']>>,
293
+ identityKey: string
294
+ ): Promise<boolean> {
295
+ const [txid, outputIndex] = String(output.outpoint).split('.')
296
+ const tx = Transaction.fromBEEF(outputs.BEEF as number[], txid)
297
+ const decoded = PushDrop.decode(tx.outputs[Number(outputIndex)].lockingScript)
298
+ if (output.customInstructions == null) return false
299
+ const keyID = JSON.parse(output.customInstructions).keyID
300
+ const { plaintext } = await this.wallet.decrypt(
301
+ { ciphertext: decoded.fields[0], protocolID: CONTACT_PROTOCOL_ID, keyID, counterparty: 'self' }, this.originator
302
+ )
303
+ const storedContact: Contact = JSON.parse(Utils.toUTF8(plaintext))
304
+ if (storedContact.identityKey !== identityKey) return false
305
+
306
+ const prevOutpoint = `${txid}.${outputIndex}` as const
307
+ const pushdrop = new PushDrop(this.wallet, this.originator)
308
+ const { signableTransaction } = await this.wallet.createAction({
309
+ description: 'Delete Contact',
310
+ inputBEEF: outputs.BEEF as number[],
311
+ inputs: [{ outpoint: prevOutpoint, unlockingScriptLength: 74, inputDescription: 'Spend contact output to delete' }],
312
+ outputs: [],
313
+ options: { acceptDelayedBroadcast: false, randomizeOutputs: false }
314
+ }, this.originator)
315
+ if (signableTransaction == null) throw new Error('Unable to delete contact')
316
+ const unlockingScript = await pushdrop.unlock(CONTACT_PROTOCOL_ID, keyID, 'self')
317
+ .sign(Transaction.fromBEEF(signableTransaction.tx), 0)
318
+ const { tx: deleteTx } = await this.wallet.signAction({
319
+ reference: signableTransaction.reference,
320
+ spends: { 0: { unlockingScript: unlockingScript.toHex() } }
321
+ }, this.originator)
322
+ if (deleteTx == null) throw new Error('Failed to delete contact output')
323
+ return true
324
+ }
388
325
  }