@did-btcr2/method 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +86 -233
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/browser.js +24111 -20342
  4. package/dist/browser.mjs +24111 -20342
  5. package/dist/cjs/index.js +2463 -2174
  6. package/dist/esm/core/aggregation/cohort.js +178 -0
  7. package/dist/esm/core/aggregation/cohort.js.map +1 -0
  8. package/dist/esm/core/aggregation/errors.js +22 -0
  9. package/dist/esm/core/aggregation/errors.js.map +1 -0
  10. package/dist/esm/core/{beacon/aggregation/cohort → aggregation}/messages/base.js +0 -1
  11. package/dist/esm/core/aggregation/messages/base.js.map +1 -0
  12. package/dist/esm/core/aggregation/messages/constants.js +26 -0
  13. package/dist/esm/core/aggregation/messages/constants.js.map +1 -0
  14. package/dist/esm/core/aggregation/messages/factories.js +113 -0
  15. package/dist/esm/core/aggregation/messages/factories.js.map +1 -0
  16. package/dist/esm/core/aggregation/messages/guards.js +37 -0
  17. package/dist/esm/core/aggregation/messages/guards.js.map +1 -0
  18. package/dist/esm/core/aggregation/messages/index.js +5 -0
  19. package/dist/esm/core/aggregation/messages/index.js.map +1 -0
  20. package/dist/esm/core/aggregation/participant.js +376 -0
  21. package/dist/esm/core/aggregation/participant.js.map +1 -0
  22. package/dist/esm/core/aggregation/phases.js +39 -0
  23. package/dist/esm/core/aggregation/phases.js.map +1 -0
  24. package/dist/esm/core/aggregation/runner/events.js +2 -0
  25. package/dist/esm/core/aggregation/runner/events.js.map +1 -0
  26. package/dist/esm/core/aggregation/runner/index.js +5 -0
  27. package/dist/esm/core/aggregation/runner/index.js.map +1 -0
  28. package/dist/esm/core/aggregation/runner/participant-runner.js +282 -0
  29. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -0
  30. package/dist/esm/core/aggregation/runner/service-runner.js +290 -0
  31. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -0
  32. package/dist/esm/core/aggregation/runner/typed-emitter.js +80 -0
  33. package/dist/esm/core/aggregation/runner/typed-emitter.js.map +1 -0
  34. package/dist/esm/core/aggregation/service.js +416 -0
  35. package/dist/esm/core/aggregation/service.js.map +1 -0
  36. package/dist/esm/core/aggregation/signing-session.js +133 -0
  37. package/dist/esm/core/aggregation/signing-session.js.map +1 -0
  38. package/dist/esm/core/aggregation/transport/didcomm.js +32 -0
  39. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -0
  40. package/dist/esm/core/aggregation/transport/error.js +12 -0
  41. package/dist/esm/core/aggregation/transport/error.js.map +1 -0
  42. package/dist/esm/core/aggregation/transport/factory.js +20 -0
  43. package/dist/esm/core/aggregation/transport/factory.js.map +1 -0
  44. package/dist/esm/core/aggregation/transport/index.js +6 -0
  45. package/dist/esm/core/aggregation/transport/index.js.map +1 -0
  46. package/dist/esm/core/aggregation/transport/nostr.js +262 -0
  47. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -0
  48. package/dist/esm/core/aggregation/transport/transport.js +2 -0
  49. package/dist/esm/core/aggregation/transport/transport.js.map +1 -0
  50. package/dist/esm/core/beacon/beacon.js +80 -0
  51. package/dist/esm/core/beacon/beacon.js.map +1 -1
  52. package/dist/esm/core/beacon/cas-beacon.js +15 -56
  53. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  54. package/dist/esm/core/beacon/error.js +0 -10
  55. package/dist/esm/core/beacon/error.js.map +1 -1
  56. package/dist/esm/core/beacon/fee-estimator.js +30 -0
  57. package/dist/esm/core/beacon/fee-estimator.js.map +1 -0
  58. package/dist/esm/core/beacon/singleton-beacon.js +10 -53
  59. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  60. package/dist/esm/core/beacon/smt-beacon.js +85 -9
  61. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  62. package/dist/esm/core/identifier.js +13 -0
  63. package/dist/esm/core/identifier.js.map +1 -1
  64. package/dist/esm/core/resolver.js +9 -0
  65. package/dist/esm/core/resolver.js.map +1 -1
  66. package/dist/esm/index.js +14 -24
  67. package/dist/esm/index.js.map +1 -1
  68. package/dist/types/core/aggregation/cohort.d.ts +94 -0
  69. package/dist/types/core/aggregation/cohort.d.ts.map +1 -0
  70. package/dist/types/core/aggregation/errors.d.ts +14 -0
  71. package/dist/types/core/aggregation/errors.d.ts.map +1 -0
  72. package/dist/types/core/{beacon/aggregation/cohort → aggregation}/messages/base.d.ts +7 -1
  73. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -0
  74. package/dist/types/core/aggregation/messages/constants.d.ts +23 -0
  75. package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -0
  76. package/dist/types/core/aggregation/messages/factories.d.ts +177 -0
  77. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -0
  78. package/dist/types/core/aggregation/messages/guards.d.ts +11 -0
  79. package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -0
  80. package/dist/types/core/aggregation/messages/index.d.ts +5 -0
  81. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -0
  82. package/dist/types/core/aggregation/participant.d.ts +101 -0
  83. package/dist/types/core/aggregation/participant.d.ts.map +1 -0
  84. package/dist/types/core/aggregation/phases.d.ts +49 -0
  85. package/dist/types/core/aggregation/phases.d.ts.map +1 -0
  86. package/dist/types/core/aggregation/runner/events.d.ts +89 -0
  87. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -0
  88. package/dist/types/core/aggregation/runner/index.d.ts +5 -0
  89. package/dist/types/core/aggregation/runner/index.d.ts.map +1 -0
  90. package/dist/types/core/aggregation/runner/participant-runner.d.ts +107 -0
  91. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -0
  92. package/dist/types/core/aggregation/runner/service-runner.d.ts +102 -0
  93. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -0
  94. package/dist/types/core/aggregation/runner/typed-emitter.d.ts +41 -0
  95. package/dist/types/core/aggregation/runner/typed-emitter.d.ts.map +1 -0
  96. package/dist/types/core/aggregation/service.d.ts +112 -0
  97. package/dist/types/core/aggregation/service.d.ts.map +1 -0
  98. package/dist/types/core/aggregation/signing-session.d.ts +69 -0
  99. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -0
  100. package/dist/types/core/aggregation/transport/didcomm.d.ts +20 -0
  101. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -0
  102. package/dist/types/core/{beacon/aggregation/communication → aggregation/transport}/error.d.ts +2 -2
  103. package/dist/types/core/aggregation/transport/error.d.ts.map +1 -0
  104. package/dist/types/core/aggregation/transport/factory.d.ts +13 -0
  105. package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -0
  106. package/dist/types/core/aggregation/transport/index.d.ts +6 -0
  107. package/dist/types/core/aggregation/transport/index.d.ts.map +1 -0
  108. package/dist/types/core/aggregation/transport/nostr.d.ts +55 -0
  109. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -0
  110. package/dist/types/core/aggregation/transport/transport.d.ts +37 -0
  111. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -0
  112. package/dist/types/core/beacon/beacon.d.ts +37 -2
  113. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  114. package/dist/types/core/beacon/cas-beacon.d.ts +19 -7
  115. package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
  116. package/dist/types/core/beacon/error.d.ts +0 -6
  117. package/dist/types/core/beacon/error.d.ts.map +1 -1
  118. package/dist/types/core/beacon/fee-estimator.d.ts +40 -0
  119. package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -0
  120. package/dist/types/core/beacon/interfaces.d.ts +8 -0
  121. package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
  122. package/dist/types/core/beacon/singleton-beacon.d.ts +9 -2
  123. package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
  124. package/dist/types/core/beacon/smt-beacon.d.ts +27 -7
  125. package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
  126. package/dist/types/core/identifier.d.ts +8 -0
  127. package/dist/types/core/identifier.d.ts.map +1 -1
  128. package/dist/types/core/interfaces.d.ts +2 -2
  129. package/dist/types/core/resolver.d.ts +11 -1
  130. package/dist/types/core/resolver.d.ts.map +1 -1
  131. package/dist/types/index.d.ts +9 -24
  132. package/dist/types/index.d.ts.map +1 -1
  133. package/package.json +31 -30
  134. package/src/core/aggregation/cohort.ts +247 -0
  135. package/src/core/aggregation/errors.ts +25 -0
  136. package/src/core/{beacon/aggregation/cohort → aggregation}/messages/base.ts +8 -3
  137. package/src/core/aggregation/messages/constants.ts +28 -0
  138. package/src/core/aggregation/messages/factories.ts +240 -0
  139. package/src/core/aggregation/messages/guards.ts +55 -0
  140. package/src/core/aggregation/messages/index.ts +4 -0
  141. package/src/core/aggregation/participant.ts +510 -0
  142. package/src/core/aggregation/phases.ts +82 -0
  143. package/src/core/aggregation/runner/events.ts +77 -0
  144. package/src/core/aggregation/runner/index.ts +4 -0
  145. package/src/core/aggregation/runner/participant-runner.ts +360 -0
  146. package/src/core/aggregation/runner/service-runner.ts +365 -0
  147. package/src/core/aggregation/runner/typed-emitter.ts +87 -0
  148. package/src/core/aggregation/service.ts +547 -0
  149. package/src/core/aggregation/signing-session.ts +209 -0
  150. package/src/core/aggregation/transport/didcomm.ts +42 -0
  151. package/src/core/aggregation/transport/error.ts +13 -0
  152. package/src/core/aggregation/transport/factory.ts +29 -0
  153. package/src/core/aggregation/transport/index.ts +5 -0
  154. package/src/core/aggregation/transport/nostr.ts +333 -0
  155. package/src/core/aggregation/transport/transport.ts +46 -0
  156. package/src/core/beacon/beacon.ts +122 -2
  157. package/src/core/beacon/cas-beacon.ts +28 -76
  158. package/src/core/beacon/error.ts +0 -12
  159. package/src/core/beacon/fee-estimator.ts +52 -0
  160. package/src/core/beacon/interfaces.ts +10 -1
  161. package/src/core/beacon/singleton-beacon.ts +14 -75
  162. package/src/core/beacon/smt-beacon.ts +109 -11
  163. package/src/core/identifier.ts +17 -0
  164. package/src/core/interfaces.ts +2 -2
  165. package/src/core/resolver.ts +25 -2
  166. package/src/index.ts +15 -29
  167. package/dist/esm/core/beacon/aggregation/cohort/index.js +0 -237
  168. package/dist/esm/core/beacon/aggregation/cohort/index.js.map +0 -1
  169. package/dist/esm/core/beacon/aggregation/cohort/messages/base.js.map +0 -1
  170. package/dist/esm/core/beacon/aggregation/cohort/messages/constants.js +0 -11
  171. package/dist/esm/core/beacon/aggregation/cohort/messages/constants.js.map +0 -1
  172. package/dist/esm/core/beacon/aggregation/cohort/messages/index.js +0 -98
  173. package/dist/esm/core/beacon/aggregation/cohort/messages/index.js.map +0 -1
  174. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.js +0 -31
  175. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.js.map +0 -1
  176. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.js +0 -29
  177. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.js.map +0 -1
  178. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.js +0 -27
  179. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.js.map +0 -1
  180. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in.js +0 -23
  181. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in.js.map +0 -1
  182. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/subscribe.js +0 -28
  183. package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/subscribe.js.map +0 -1
  184. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.js +0 -29
  185. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.js.map +0 -1
  186. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/authorization-request.js +0 -30
  187. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/authorization-request.js.map +0 -1
  188. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.js +0 -30
  189. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.js.map +0 -1
  190. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/request-signature.js +0 -30
  191. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/request-signature.js.map +0 -1
  192. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/signature-authorization.js +0 -31
  193. package/dist/esm/core/beacon/aggregation/cohort/messages/sign/signature-authorization.js.map +0 -1
  194. package/dist/esm/core/beacon/aggregation/cohort/status.js +0 -8
  195. package/dist/esm/core/beacon/aggregation/cohort/status.js.map +0 -1
  196. package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js +0 -121
  197. package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js.map +0 -1
  198. package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js +0 -245
  199. package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js.map +0 -1
  200. package/dist/esm/core/beacon/aggregation/communication/error.js +0 -12
  201. package/dist/esm/core/beacon/aggregation/communication/error.js.map +0 -1
  202. package/dist/esm/core/beacon/aggregation/communication/factory.js +0 -21
  203. package/dist/esm/core/beacon/aggregation/communication/factory.js.map +0 -1
  204. package/dist/esm/core/beacon/aggregation/communication/service.js +0 -2
  205. package/dist/esm/core/beacon/aggregation/communication/service.js.map +0 -1
  206. package/dist/esm/core/beacon/aggregation/coordinator.js +0 -343
  207. package/dist/esm/core/beacon/aggregation/coordinator.js.map +0 -1
  208. package/dist/esm/core/beacon/aggregation/participant.js +0 -435
  209. package/dist/esm/core/beacon/aggregation/participant.js.map +0 -1
  210. package/dist/esm/core/beacon/aggregation/session/index.js +0 -244
  211. package/dist/esm/core/beacon/aggregation/session/index.js.map +0 -1
  212. package/dist/esm/core/beacon/aggregation/session/status.js +0 -11
  213. package/dist/esm/core/beacon/aggregation/session/status.js.map +0 -1
  214. package/dist/types/core/beacon/aggregation/cohort/index.d.ts +0 -136
  215. package/dist/types/core/beacon/aggregation/cohort/index.d.ts.map +0 -1
  216. package/dist/types/core/beacon/aggregation/cohort/messages/base.d.ts.map +0 -1
  217. package/dist/types/core/beacon/aggregation/cohort/messages/constants.d.ts +0 -11
  218. package/dist/types/core/beacon/aggregation/cohort/messages/constants.d.ts.map +0 -1
  219. package/dist/types/core/beacon/aggregation/cohort/messages/index.d.ts +0 -65
  220. package/dist/types/core/beacon/aggregation/cohort/messages/index.d.ts.map +0 -1
  221. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.d.ts +0 -29
  222. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.d.ts.map +0 -1
  223. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts +0 -26
  224. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts.map +0 -1
  225. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.d.ts +0 -24
  226. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.d.ts.map +0 -1
  227. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in.d.ts +0 -20
  228. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in.d.ts.map +0 -1
  229. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/subscribe.d.ts +0 -25
  230. package/dist/types/core/beacon/aggregation/cohort/messages/keygen/subscribe.d.ts.map +0 -1
  231. package/dist/types/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.d.ts +0 -25
  232. package/dist/types/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.d.ts.map +0 -1
  233. package/dist/types/core/beacon/aggregation/cohort/messages/sign/authorization-request.d.ts +0 -26
  234. package/dist/types/core/beacon/aggregation/cohort/messages/sign/authorization-request.d.ts.map +0 -1
  235. package/dist/types/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.d.ts +0 -26
  236. package/dist/types/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.d.ts.map +0 -1
  237. package/dist/types/core/beacon/aggregation/cohort/messages/sign/request-signature.d.ts +0 -26
  238. package/dist/types/core/beacon/aggregation/cohort/messages/sign/request-signature.d.ts.map +0 -1
  239. package/dist/types/core/beacon/aggregation/cohort/messages/sign/signature-authorization.d.ts +0 -27
  240. package/dist/types/core/beacon/aggregation/cohort/messages/sign/signature-authorization.d.ts.map +0 -1
  241. package/dist/types/core/beacon/aggregation/cohort/status.d.ts +0 -8
  242. package/dist/types/core/beacon/aggregation/cohort/status.d.ts.map +0 -1
  243. package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts +0 -89
  244. package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts.map +0 -1
  245. package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts +0 -103
  246. package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts.map +0 -1
  247. package/dist/types/core/beacon/aggregation/communication/error.d.ts.map +0 -1
  248. package/dist/types/core/beacon/aggregation/communication/factory.d.ts +0 -10
  249. package/dist/types/core/beacon/aggregation/communication/factory.d.ts.map +0 -1
  250. package/dist/types/core/beacon/aggregation/communication/service.d.ts +0 -36
  251. package/dist/types/core/beacon/aggregation/communication/service.d.ts.map +0 -1
  252. package/dist/types/core/beacon/aggregation/coordinator.d.ts +0 -116
  253. package/dist/types/core/beacon/aggregation/coordinator.d.ts.map +0 -1
  254. package/dist/types/core/beacon/aggregation/participant.d.ts +0 -192
  255. package/dist/types/core/beacon/aggregation/participant.d.ts.map +0 -1
  256. package/dist/types/core/beacon/aggregation/session/index.d.ts +0 -156
  257. package/dist/types/core/beacon/aggregation/session/index.d.ts.map +0 -1
  258. package/dist/types/core/beacon/aggregation/session/status.d.ts +0 -11
  259. package/dist/types/core/beacon/aggregation/session/status.d.ts.map +0 -1
  260. package/src/core/beacon/aggregation/cohort/index.ts +0 -305
  261. package/src/core/beacon/aggregation/cohort/messages/constants.ts +0 -12
  262. package/src/core/beacon/aggregation/cohort/messages/index.ts +0 -143
  263. package/src/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.ts +0 -44
  264. package/src/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.ts +0 -40
  265. package/src/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.ts +0 -35
  266. package/src/core/beacon/aggregation/cohort/messages/keygen/opt-in.ts +0 -34
  267. package/src/core/beacon/aggregation/cohort/messages/keygen/subscribe.ts +0 -36
  268. package/src/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.ts +0 -39
  269. package/src/core/beacon/aggregation/cohort/messages/sign/authorization-request.ts +0 -40
  270. package/src/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.ts +0 -40
  271. package/src/core/beacon/aggregation/cohort/messages/sign/request-signature.ts +0 -40
  272. package/src/core/beacon/aggregation/cohort/messages/sign/signature-authorization.ts +0 -41
  273. package/src/core/beacon/aggregation/cohort/status.ts +0 -7
  274. package/src/core/beacon/aggregation/communication/adapter/did-comm.ts +0 -148
  275. package/src/core/beacon/aggregation/communication/adapter/nostr.ts +0 -323
  276. package/src/core/beacon/aggregation/communication/error.ts +0 -13
  277. package/src/core/beacon/aggregation/communication/factory.ts +0 -25
  278. package/src/core/beacon/aggregation/communication/service.ts +0 -42
  279. package/src/core/beacon/aggregation/coordinator.ts +0 -419
  280. package/src/core/beacon/aggregation/participant.ts +0 -517
  281. package/src/core/beacon/aggregation/session/index.ts +0 -301
  282. package/src/core/beacon/aggregation/session/status.ts +0 -18
@@ -1,10 +1,27 @@
1
+ import type { AddressUtxo, BitcoinConnection } from '@did-btcr2/bitcoin';
1
2
  import type { KeyBytes } from '@did-btcr2/common';
2
- import type { BitcoinConnection } from '@did-btcr2/bitcoin';
3
3
  import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
4
+ import { SchnorrKeyPair } from '@did-btcr2/keypair';
5
+ import { hexToBytes } from '@noble/hashes/utils';
6
+ import { opcodes, Psbt, script } from 'bitcoinjs-lib';
4
7
  import type { BeaconProcessResult } from '../resolver.js';
5
8
  import type { SidecarData } from '../types.js';
9
+ import { BeaconError } from './error.js';
10
+ import { StaticFeeEstimator } from './fee-estimator.js';
11
+ import type { FeeEstimator } from './fee-estimator.js';
6
12
  import type { BeaconService, BeaconSignal } from './interfaces.js';
7
13
 
14
+ /** Default fee estimator used when none is supplied. ~5 sat/vB static rate. */
15
+ const DEFAULT_FEE_ESTIMATOR: FeeEstimator = new StaticFeeEstimator(5);
16
+
17
+ /**
18
+ * Options accepted by {@link Beacon.buildSignAndBroadcast}.
19
+ */
20
+ export interface BroadcastOptions {
21
+ /** Fee estimator for computing the transaction fee. Defaults to {@link DEFAULT_FEE_ESTIMATOR}. */
22
+ feeEstimator?: FeeEstimator;
23
+ }
24
+
8
25
  /**
9
26
  * Abstract base class for all BTCR2 Beacon types.
10
27
  * A Beacon is a service listed in a BTCR2 DID document that informs resolvers
@@ -52,11 +69,114 @@ export abstract class Beacon {
52
69
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
53
70
  * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
54
71
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
72
+ * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
55
73
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
56
74
  */
57
75
  abstract broadcastSignal(
58
76
  signedUpdate: SignedBTCR2Update,
59
77
  secretKey: KeyBytes,
60
- bitcoin: BitcoinConnection
78
+ bitcoin: BitcoinConnection,
79
+ options?: BroadcastOptions
61
80
  ): Promise<SignedBTCR2Update>;
81
+
82
+ /**
83
+ * Shared PSBT construction + signing + broadcast helper used by all beacon types.
84
+ *
85
+ * Steps:
86
+ * 1. Parse the beacon's `serviceEndpoint` (stripping `bitcoin:` prefix) into a Bitcoin address.
87
+ * 2. Query the address for unconfirmed/confirmed UTXOs.
88
+ * 3. Select the most recent confirmed UTXO.
89
+ * 4. Fetch the previous transaction hex for `nonWitnessUtxo`.
90
+ * 5. Build a PSBT: input (UTXO) → change output + OP_RETURN(signalBytes).
91
+ * 6. Compute the fee via the supplied (or default) {@link FeeEstimator} against the tx vsize.
92
+ * 7. Sign input 0 with an ECDSA signer derived from `secretKey`.
93
+ * 8. Finalize, extract, and broadcast via the REST transaction endpoint.
94
+ *
95
+ * Fee handling: the PSBT is constructed with a placeholder change amount, signed to measure
96
+ * vsize, then the change is adjusted to pay the actual fee and the input re-signed. This
97
+ * two-pass approach avoids hardcoded fee constants and produces a tx that matches the
98
+ * estimator's rate.
99
+ *
100
+ * @param signalBytes 32-byte payload to embed in OP_RETURN.
101
+ * @param secretKey Secret key used to sign the spending input.
102
+ * @param bitcoin Bitcoin network connection.
103
+ * @param options Broadcast options (fee estimator, etc.).
104
+ * @returns The txid of the broadcast transaction.
105
+ * @throws {BeaconError} if the address is unfunded or no UTXO is available.
106
+ */
107
+ protected async buildSignAndBroadcast(
108
+ signalBytes: Uint8Array,
109
+ secretKey: KeyBytes,
110
+ bitcoin: BitcoinConnection,
111
+ options?: BroadcastOptions
112
+ ): Promise<string> {
113
+ const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
114
+
115
+ // Strip the 'bitcoin:' prefix from the service endpoint.
116
+ const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
117
+
118
+ // Fetch UTXOs at the beacon address.
119
+ const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
120
+ if(!utxos.length) {
121
+ throw new BeaconError(
122
+ 'No UTXOs found, please fund address!',
123
+ 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
124
+ );
125
+ }
126
+
127
+ // Take the most recently confirmed UTXO.
128
+ const utxo: AddressUtxo | undefined = utxos.sort(
129
+ (a, b) => b.status.block_height - a.status.block_height
130
+ ).shift();
131
+ if(!utxo) {
132
+ throw new BeaconError(
133
+ 'Beacon bitcoin address unfunded or utxos unconfirmed.',
134
+ 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
135
+ );
136
+ }
137
+
138
+ // Get the previous tx hex for non-witness UTXO reference.
139
+ const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
140
+
141
+ // Build the ECDSA signer from the secret key.
142
+ const keyPair = SchnorrKeyPair.fromSecret(secretKey);
143
+ const signer = {
144
+ publicKey : keyPair.publicKey.compressed,
145
+ sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
146
+ };
147
+
148
+ // First pass: build with a placeholder fee (0 sats) so we can measure vsize.
149
+ const build = (fee: bigint) =>
150
+ new Psbt({ network: bitcoin.data })
151
+ .addInput({
152
+ hash : utxo.txid,
153
+ index : utxo.vout,
154
+ nonWitnessUtxo : hexToBytes(prevTx),
155
+ })
156
+ .addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - fee })
157
+ .addOutput({ script: script.compile([opcodes.OP_RETURN, signalBytes]), value: 0n });
158
+
159
+ const probeTx = build(0n)
160
+ .signInput(0, signer)
161
+ .finalizeAllInputs()
162
+ .extractTransaction();
163
+ const vsize = probeTx.virtualSize();
164
+
165
+ // Second pass: use the estimator to compute the real fee.
166
+ const fee = await feeEstimator.estimateFee(vsize);
167
+ if(BigInt(utxo.value) <= fee) {
168
+ throw new BeaconError(
169
+ `UTXO value (${utxo.value}) insufficient to cover fee (${fee}).`,
170
+ 'INSUFFICIENT_FUNDS', { bitcoinAddress, utxoValue: utxo.value, fee: fee.toString() }
171
+ );
172
+ }
173
+
174
+ const signedTxHex = build(fee)
175
+ .signInput(0, signer)
176
+ .finalizeAllInputs()
177
+ .extractTransaction()
178
+ .toHex();
179
+
180
+ return bitcoin.rest.transaction.send(signedTxHex);
181
+ }
62
182
  }
@@ -1,15 +1,21 @@
1
- import type { AddressUtxo, BitcoinConnection } from '@did-btcr2/bitcoin';
1
+ import type { BitcoinConnection } from '@did-btcr2/bitcoin';
2
2
  import type { KeyBytes } from '@did-btcr2/common';
3
3
  import { canonicalHash, canonicalize, decode, encode, hash } from '@did-btcr2/common';
4
4
  import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
5
- import { SchnorrKeyPair } from '@did-btcr2/keypair';
6
- import { hexToBytes } from '@noble/hashes/utils';
7
- import { opcodes, Psbt, script } from 'bitcoinjs-lib';
8
5
  import type { BeaconProcessResult, DataNeed } from '../resolver.js';
9
6
  import type { SidecarData } from '../types.js';
7
+ import type { BroadcastOptions } from './beacon.js';
10
8
  import { Beacon } from './beacon.js';
11
- import { CASBeaconError } from './error.js';
12
- import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
9
+ import type { BeaconService, BeaconSignal, BlockMetadata, CasPublishFn } from './interfaces.js';
10
+
11
+ /**
12
+ * CAS-specific broadcast options — extends {@link BroadcastOptions} with an optional
13
+ * `casPublish` callback used to publish the CAS Announcement off-chain after the
14
+ * OP_RETURN signal is broadcast.
15
+ */
16
+ export interface CASBroadcastOptions extends BroadcastOptions {
17
+ casPublish?: CasPublishFn;
18
+ }
13
19
 
14
20
  /**
15
21
  * Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#cas-beacon | CAS Beacon}.
@@ -39,7 +45,7 @@ export class CASBeacon extends Beacon {
39
45
  * For each signal, the signalBytes contain the hex-encoded hash of a CAS Announcement.
40
46
  * The CAS Announcement maps DIDs to their base64url-encoded update hashes.
41
47
  * This method looks up the CAS Announcement from the sidecar, extracts the update
42
- * hash for the DID being resolved, and retrieves the corresponding signed update.
48
+ * hash for the DID being resolved, and retrieves the corresponding signed update from sidecar.
43
49
  *
44
50
  * @param {Array<BeaconSignal>} signals The array of Beacon Signals to process.
45
51
  * @param {SidecarData} sidecar The sidecar data associated with the CAS Beacon.
@@ -106,99 +112,45 @@ export class CASBeacon extends Beacon {
106
112
  /**
107
113
  * Broadcasts a CAS Beacon signal to the Bitcoin network.
108
114
  *
109
- * Creates a CAS Announcement mapping the DID to the update hash, then broadcasts
110
- * the hash of the announcement via OP_RETURN. The CAS Announcement is distributed
111
- * to resolvers via sidecar data.
115
+ * Creates a CAS Announcement mapping the DID to the update hash, broadcasts the hash of the
116
+ * announcement via OP_RETURN, and optionally publishes the announcement off-chain via the
117
+ * supplied `casPublish` callback. UTXO selection, PSBT construction, fee estimation, signing,
118
+ * and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
112
119
  *
113
120
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
114
121
  * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
115
122
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
123
+ * @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
124
+ * `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
116
125
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
117
- * @throws {CASBeaconError} if the bitcoin address is invalid or unfunded.
126
+ * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
118
127
  */
119
128
  async broadcastSignal(
120
129
  signedUpdate: SignedBTCR2Update,
121
130
  secretKey: KeyBytes,
122
- bitcoin: BitcoinConnection
131
+ bitcoin: BitcoinConnection,
132
+ options?: CASBroadcastOptions
123
133
  ): Promise<SignedBTCR2Update> {
124
134
  // Extract the DID from the beacon service id (strip the #fragment)
125
135
  const did = this.service.id.split('#')[0];
126
136
 
127
- // Hash the signed update (base64url for the CAS Announcement entry)
137
+ // Hash the signed update (base64urlnopad for the CAS Announcement entry per spec)
128
138
  const updateHash = canonicalHash(signedUpdate);
129
139
 
130
140
  // Create the CAS Announcement mapping this DID to its update hash
131
141
  const casAnnouncement = { [did]: updateHash };
132
142
 
133
- // TODO: Publish CAS Announcement to content-addressed store (e.g., IPFS via Helia)
134
-
135
143
  // Canonicalize and hash the CAS Announcement for the OP_RETURN output
136
144
  const announcementHash = hash(canonicalize(casAnnouncement));
137
145
 
138
- // Convert the serviceEndpoint to a bitcoin address by removing the 'bitcoin:' prefix
139
- const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
140
-
141
- // Query the Bitcoin network for UTXOs associated with the bitcoinAddress
142
- const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
143
-
144
- // If no utxos are found, throw an error indicating the address is unfunded.
145
- if(!utxos.length) {
146
- throw new CASBeaconError(
147
- 'No UTXOs found, please fund address!',
148
- 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
149
- );
150
- }
146
+ // Delegate UTXO selection, PSBT construction, fee estimation, signing, and broadcast
147
+ await this.buildSignAndBroadcast(announcementHash, secretKey, bitcoin, options);
151
148
 
152
- // Sort utxos by block height and take the most recent one
153
- const utxo: AddressUtxo | undefined = utxos.sort(
154
- (a, b) => b.status.block_height - a.status.block_height
155
- ).shift();
156
-
157
- // If no utxos are found, throw an error.
158
- if(!utxo) {
159
- throw new CASBeaconError(
160
- 'Beacon bitcoin address unfunded or utxos unconfirmed.',
161
- 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
162
- );
149
+ // Publish CAS Announcement to content-addressed store if callback provided
150
+ if(options?.casPublish) {
151
+ await options.casPublish(casAnnouncement);
163
152
  }
164
153
 
165
- // Get the previous tx to the utxo being spent
166
- const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
167
-
168
- // Construct a spend transaction
169
- const spendTx = new Psbt({ network: bitcoin.data })
170
- // Spend tx contains the utxo as its input
171
- .addInput({
172
- hash : utxo.txid,
173
- index : utxo.vout,
174
- nonWitnessUtxo : hexToBytes(prevTx)
175
- })
176
- // Add a change output minus a fee of 500 sats
177
- // TODO: calculate fee based on transaction vsize and current fee rates
178
- .addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - BigInt(500) })
179
- // Add an OP_RETURN output containing the CAS Announcement hash
180
- .addOutput({ script: script.compile([opcodes.OP_RETURN, announcementHash]), value: 0n });
181
-
182
- // Construct a key pair and PSBT signer from the secret key
183
- const keyPair = SchnorrKeyPair.fromSecret(secretKey);
184
- const signer = {
185
- publicKey : keyPair.publicKey.compressed,
186
- sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
187
- };
188
-
189
- // Sign 0th input, finalize extract to hex in prep for broadcast
190
- const signedTx = spendTx.signInput(0, signer)
191
- .finalizeAllInputs()
192
- .extractTransaction()
193
- .toHex();
194
-
195
- // Broadcast spendTx to the Bitcoin network.
196
- const txid = await bitcoin.rest.transaction.send(signedTx);
197
-
198
- // Log the txid of the broadcasted transaction
199
- console.info(`CAS Beacon Signal Broadcasted with txid: ${txid}`);
200
-
201
- // Return the signed update
202
154
  return signedUpdate;
203
155
  }
204
156
  }
@@ -6,18 +6,6 @@ export class BeaconError extends MethodError {
6
6
  }
7
7
  }
8
8
 
9
- export class BeaconCoordinatorError extends MethodError {
10
- constructor(message: string, type: string = 'BeaconCoordinatorError', data?: Record<string, any>) {
11
- super(message, type, data);
12
- }
13
- }
14
-
15
- export class BeaconParticipantError extends MethodError {
16
- constructor(message: string, type: string = 'BeaconParticipantError', data?: Record<string, any>) {
17
- super(message, type, data);
18
- }
19
- }
20
-
21
9
  export class SingletonBeaconError extends MethodError {
22
10
  constructor(message: string, type: string = 'SingletonBeaconError', data?: Record<string, any>) {
23
11
  super(message, type, data);
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Estimates the fee (in satoshis) for a transaction of a given virtual size.
3
+ *
4
+ * Beacons delegate fee calculation to a `FeeEstimator` so that callers can
5
+ * plug in different strategies: static fee rates for tests/regtest, mempool
6
+ * APIs for mainnet, Bitcoin Core `estimatesmartfee` RPC for full-node setups,
7
+ * etc.
8
+ *
9
+ * Implementations return the **total fee in satoshis** for a transaction of
10
+ * the given virtual size (`vsize`). Callers pass the vsize; the estimator
11
+ * decides the fee rate and computes the total.
12
+ */
13
+ export interface FeeEstimator {
14
+ /**
15
+ * Estimate the total fee in satoshis for a transaction of the given vsize.
16
+ * @param vsize Transaction virtual size in vbytes.
17
+ * @returns Total fee in satoshis.
18
+ */
19
+ estimateFee(vsize: number): Promise<bigint>;
20
+ }
21
+
22
+ /**
23
+ * Fee estimator that returns a fixed fee rate regardless of network conditions.
24
+ *
25
+ * Suitable for:
26
+ * - Tests (deterministic outputs)
27
+ * - Regtest (no real fee market)
28
+ * - Environments where a fee rate is supplied out-of-band
29
+ *
30
+ * For mainnet production use, prefer a dynamic estimator that queries current
31
+ * network conditions (mempool APIs, Bitcoin Core RPC).
32
+ */
33
+ export class StaticFeeEstimator implements FeeEstimator {
34
+ readonly satsPerVbyte: number;
35
+
36
+ /**
37
+ * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
38
+ */
39
+ constructor(satsPerVbyte: number = 5) {
40
+ if(satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
41
+ throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
42
+ }
43
+ this.satsPerVbyte = satsPerVbyte;
44
+ }
45
+
46
+ async estimateFee(vsize: number): Promise<bigint> {
47
+ if(vsize < 0 || !Number.isFinite(vsize)) {
48
+ throw new Error(`Invalid vsize: ${vsize}`);
49
+ }
50
+ return BigInt(Math.ceil(vsize * this.satsPerVbyte));
51
+ }
52
+ }
@@ -64,4 +64,13 @@ export interface BeaconSignal {
64
64
  * Metadata about the block containing the Beacon Signal.
65
65
  */
66
66
  blockMetadata: BlockMetadata;
67
- }
67
+ }
68
+
69
+ /**
70
+ * Callback for publishing a CAS Announcement to a content-addressed store.
71
+ * The method package defines this type; the api layer provides the implementation
72
+ * (e.g., via CasApi.publish backed by IPFS/Helia).
73
+ *
74
+ * @param announcement The CAS Announcement object (DID → update hash mapping).
75
+ */
76
+ export type CasPublishFn = (announcement: Record<string, string>) => Promise<void>;
@@ -1,14 +1,11 @@
1
- import type { AddressUtxo, BitcoinConnection } from '@did-btcr2/bitcoin';
1
+ import type { BitcoinConnection } from '@did-btcr2/bitcoin';
2
2
  import type { KeyBytes } from '@did-btcr2/common';
3
3
  import { canonicalize, hash } from '@did-btcr2/common';
4
4
  import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
5
- import { SchnorrKeyPair } from '@did-btcr2/keypair';
6
- import { hexToBytes } from '@noble/hashes/utils';
7
- import { opcodes, Psbt, script } from 'bitcoinjs-lib';
8
5
  import type { BeaconProcessResult, DataNeed } from '../resolver.js';
9
6
  import type { SidecarData } from '../types.js';
7
+ import type { BroadcastOptions } from './beacon.js';
10
8
  import { Beacon } from './beacon.js';
11
- import { SingletonBeaconError } from './error.js';
12
9
  import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
13
10
 
14
11
  /**
@@ -64,84 +61,26 @@ export class SingletonBeacon extends Beacon {
64
61
  }
65
62
  /**
66
63
  * Broadcasts a SingletonBeacon signal to the Bitcoin network.
64
+ *
65
+ * The signal bytes embedded in OP_RETURN are the SHA-256 canonical hash of the signed update.
66
+ * UTXO selection, PSBT construction, fee estimation, signing, and broadcast are delegated to
67
+ * {@link Beacon.buildSignAndBroadcast}.
68
+ *
67
69
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
68
70
  * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
69
71
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
72
+ * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
70
73
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
71
- * @throws {SingletonBeaconError} if the bitcoin address is invalid or unfunded.
74
+ * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
72
75
  */
73
76
  async broadcastSignal(
74
77
  signedUpdate: SignedBTCR2Update,
75
78
  secretKey: KeyBytes,
76
- bitcoin: BitcoinConnection
79
+ bitcoin: BitcoinConnection,
80
+ options?: BroadcastOptions
77
81
  ): Promise<SignedBTCR2Update> {
78
- // Convert the serviceEndpoint to a bitcoin address by removing the 'bitcoin:' prefix
79
- const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
80
-
81
- // Query the Bitcoin network for UTXOs associated with the bitcoinAddress
82
- const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
83
-
84
- // If no utxos are found, throw an error indicating the address is unfunded.
85
- if(!utxos.length) {
86
- throw new SingletonBeaconError(
87
- 'No UTXOs found, please fund address!',
88
- 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
89
- );
90
- }
91
-
92
- // Sort utxos by block height and take the most recent one
93
- const utxo: AddressUtxo | undefined = utxos.sort(
94
- (a, b) => b.status.block_height - a.status.block_height
95
- ).shift();
96
-
97
- // If no utxos are found, throw an error.
98
- if(!utxo) {
99
- throw new SingletonBeaconError(
100
- 'Beacon bitcoin address unfunded or utxos unconfirmed.',
101
- 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
102
- );
103
- }
104
-
105
- // Get the previous tx to the utxo being spent
106
- const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
107
-
108
- // Canonicalize and hash the signed update for OP_RETURN output
109
- const updateHash = hash(canonicalize(signedUpdate));
110
-
111
- // Construct a spend transaction
112
- const spendTx = new Psbt({ network: bitcoin.data })
113
- // Spend tx contains the utxo as its input
114
- .addInput({
115
- hash : utxo.txid,
116
- index : utxo.vout,
117
- nonWitnessUtxo : hexToBytes(prevTx)
118
- })
119
- // Add a change output minus a fee of 500 sats
120
- // TODO: calculate fee based on transaction vsize and current fee rates
121
- .addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - BigInt(500) })
122
- // Add an OP_RETURN output containing the update hash
123
- .addOutput({ script: script.compile([opcodes.OP_RETURN, updateHash]), value: 0n });
124
-
125
- // Construct a key pair and PSBT signer from the secret key
126
- const keyPair = SchnorrKeyPair.fromSecret(secretKey);
127
- const signer = {
128
- publicKey : keyPair.publicKey.compressed,
129
- sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
130
- };
131
-
132
- // Sign 0th input, finalize extract to hex in prep for broadcast
133
- const signedTx = spendTx.signInput(0, signer)
134
- .finalizeAllInputs()
135
- .extractTransaction()
136
- .toHex();
137
-
138
- // Broadcast spendTx to the Bitcoin network.
139
- const txid = await bitcoin.rest.transaction.send(signedTx);
140
-
141
- // Log the txid of the broadcasted transaction
142
- console.info(`Singleton Beacon Signal Broadcasted with txid: ${txid}`);
143
-
144
- // Return the signed update
82
+ const signalBytes = hash(canonicalize(signedUpdate));
83
+ await this.buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options);
145
84
  return signedUpdate;
146
85
  }
147
- }
86
+ }
@@ -1,14 +1,24 @@
1
1
  import type { BitcoinConnection } from '@did-btcr2/bitcoin';
2
+ import { canonicalize } from '@did-btcr2/common';
2
3
  import type { KeyBytes } from '@did-btcr2/common';
3
4
  import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
4
- import type { BeaconProcessResult } from '../resolver.js';
5
+ import { blockHash, BTCR2MerkleTree, didToIndex, hexToHash, verifySerializedProof } from '@did-btcr2/smt';
6
+ import { randomBytes } from '@noble/hashes/utils';
7
+ import type { BeaconProcessResult, DataNeed } from '../resolver.js';
5
8
  import type { SidecarData } from '../types.js';
9
+ import type { BroadcastOptions } from './beacon.js';
6
10
  import { Beacon } from './beacon.js';
7
11
  import { SMTBeaconError } from './error.js';
8
- import type { BeaconService, BeaconSignal } from './interfaces.js';
12
+ import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
9
13
 
10
14
  /**
11
- * Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#smt-beacon | SMTBeacon}.
15
+ * Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#smt-beacon | SMT Beacon}.
16
+ *
17
+ * An SMT (Sparse Merkle Tree) Beacon aggregates updates for multiple DIDs
18
+ * into a single Merkle root hash broadcast on-chain via OP_RETURN.
19
+ * During resolution, the SMT Proof from the sidecar is verified against the
20
+ * on-chain root, and the proof's updateId is used to retrieve the signed update.
21
+ *
12
22
  * @class SMTBeacon
13
23
  * @type {SMTBeacon}
14
24
  * @extends {Beacon}
@@ -24,31 +34,119 @@ export class SMTBeacon extends Beacon {
24
34
 
25
35
  /**
26
36
  * Implements {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-smt-beacon | 7.2.e.1 Process SMT Beacon}.
37
+ *
38
+ * For each signal, the signalBytes contain the hex-encoded SMT root hash.
39
+ * This method looks up the SMT Proof from the sidecar by root hash,
40
+ * validates the Merkle inclusion proof, and retrieves the corresponding
41
+ * signed update using the proof's updateId.
42
+ *
27
43
  * @param {Array<BeaconSignal>} signals The array of Beacon Signals to process.
28
44
  * @param {SidecarData} sidecar The sidecar data associated with the SMT Beacon.
29
- * @returns {BeaconProcessResult} The processed signals.
30
- * @throws {SMTBeaconError} if processing fails.
45
+ * @returns {BeaconProcessResult} Successfully resolved updates and any data needs.
46
+ * @throws {SMTBeaconError} if proof verification fails or proof is malformed.
31
47
  */
32
48
  processSignals(
33
49
  signals: Array<BeaconSignal>,
34
50
  sidecar: SidecarData
35
51
  ): BeaconProcessResult {
36
- throw new SMTBeaconError('Method not implemented.', `METHOD_NOT_IMPLEMENTED`, {signals, sidecar});
52
+ const updates = new Array<[SignedBTCR2Update, BlockMetadata]>();
53
+ const needs = new Array<DataNeed>();
54
+
55
+ // Extract the DID from the beacon service id (strip the #fragment)
56
+ const did = this.service.id.split('#')[0];
57
+
58
+ for(const signal of signals) {
59
+ // Signal bytes are the hex-encoded SMT root hash; smtMap is keyed by proof.id (also hex)
60
+ const smtProof = sidecar.smtMap.get(signal.signalBytes);
61
+
62
+ if(!smtProof) {
63
+ // SMT Proof not available — emit a need
64
+ needs.push({
65
+ kind : 'NeedSMTProof',
66
+ smtRootHash : signal.signalBytes,
67
+ beaconServiceId : this.service.id
68
+ });
69
+ continue;
70
+ }
71
+
72
+ // Non-inclusion proof — no update for this DID in this epoch, skip
73
+ if(!smtProof.updateId) {
74
+ continue;
75
+ }
76
+
77
+ // Nonce is required for proof verification
78
+ if(!smtProof.nonce) {
79
+ throw new SMTBeaconError(
80
+ 'SMT proof missing required nonce field.',
81
+ 'INVALID_SMT_PROOF', { smtProof, did }
82
+ );
83
+ }
84
+
85
+ // Verify Merkle inclusion: leaf = hash(hash(nonce) || updateId)
86
+ const index = didToIndex(did);
87
+ const candidateHash = blockHash(blockHash(hexToHash(smtProof.nonce)), hexToHash(smtProof.updateId));
88
+ const valid = verifySerializedProof(smtProof, index, candidateHash);
89
+
90
+ if(!valid) {
91
+ throw new SMTBeaconError(
92
+ 'SMT proof verification failed.',
93
+ 'INVALID_SMT_PROOF', { smtProof, did }
94
+ );
95
+ }
96
+
97
+ // Look up the signed update in sidecar updateMap (keyed by hex canonical hash)
98
+ const signedUpdate = sidecar.updateMap.get(smtProof.updateId);
99
+
100
+ if(!signedUpdate) {
101
+ // Signed update not available — emit a need
102
+ needs.push({
103
+ kind : 'NeedSignedUpdate',
104
+ updateHash : smtProof.updateId,
105
+ beaconServiceId : this.service.id
106
+ });
107
+ continue;
108
+ }
109
+
110
+ updates.push([signedUpdate, signal.blockMetadata]);
111
+ }
112
+
113
+ return { updates, needs };
37
114
  }
38
115
 
39
116
  /**
40
- * Broadcast CAS Beacon signal to the Bitcoin network.
117
+ * Broadcasts an SMT Beacon signal to the Bitcoin network.
118
+ *
119
+ * Builds a single-entry Sparse Merkle Tree from the signed update, then broadcasts the tree's
120
+ * root hash via OP_RETURN. For multi-party aggregation, use the {@link AggregationService}
121
+ * subsystem directly instead of this method. UTXO selection, PSBT construction, fee estimation,
122
+ * signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
123
+ *
41
124
  * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
42
125
  * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
43
126
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
44
- * @return {Promise<SignedBTCR2Update>} The signed update that was broadcasted.
45
- * @throws {SMTBeaconError} if broadcasting fails.
127
+ * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
128
+ * @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
129
+ * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
46
130
  */
47
131
  async broadcastSignal(
48
132
  signedUpdate: SignedBTCR2Update,
49
133
  secretKey: KeyBytes,
50
- bitcoin: BitcoinConnection
134
+ bitcoin: BitcoinConnection,
135
+ options?: BroadcastOptions
51
136
  ): Promise<SignedBTCR2Update> {
52
- throw new SMTBeaconError('Method not implemented.', `METHOD_NOT_IMPLEMENTED`, {signedUpdate, secretKey, bitcoin});
137
+ // Extract the DID from the beacon service id (strip the #fragment)
138
+ const did = this.service.id.split('#')[0];
139
+
140
+ // Build a single-entry SMT from the signed update
141
+ const canonicalBytes = new TextEncoder().encode(canonicalize(signedUpdate));
142
+ const nonce = randomBytes(32);
143
+ const tree = new BTCR2MerkleTree();
144
+ tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
145
+ tree.finalize();
146
+
147
+ // Root hash is the signal bytes for the OP_RETURN output
148
+ await this.buildSignAndBroadcast(tree.rootHash, secretKey, bitcoin, options);
149
+
150
+ return signedUpdate;
53
151
  }
54
152
  }