@did-btcr2/method 0.37.0 → 0.39.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 (245) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +47868 -34771
  3. package/dist/browser.mjs +47867 -34770
  4. package/dist/cjs/index.js +462 -6060
  5. package/dist/esm/core/beacon/beacon.js +101 -10
  6. package/dist/esm/core/beacon/beacon.js.map +1 -1
  7. package/dist/esm/core/beacon/fee-estimator.js +12 -27
  8. package/dist/esm/core/beacon/fee-estimator.js.map +1 -1
  9. package/dist/esm/core/did-sender-resolver.js +26 -0
  10. package/dist/esm/core/did-sender-resolver.js.map +1 -0
  11. package/dist/esm/index.js +3 -17
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/types/core/beacon/beacon.d.ts +59 -3
  14. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  15. package/dist/types/core/beacon/fee-estimator.d.ts +10 -37
  16. package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -1
  17. package/dist/types/core/did-sender-resolver.d.ts +14 -0
  18. package/dist/types/core/did-sender-resolver.d.ts.map +1 -0
  19. package/dist/types/index.d.ts +2 -16
  20. package/dist/types/index.d.ts.map +1 -1
  21. package/package.json +6 -5
  22. package/src/core/beacon/beacon.ts +131 -12
  23. package/src/core/beacon/fee-estimator.ts +15 -49
  24. package/src/core/did-sender-resolver.ts +25 -0
  25. package/src/index.ts +3 -17
  26. package/dist/esm/core/aggregation/beacon-strategy.js +0 -76
  27. package/dist/esm/core/aggregation/beacon-strategy.js.map +0 -1
  28. package/dist/esm/core/aggregation/cohort.js +0 -316
  29. package/dist/esm/core/aggregation/cohort.js.map +0 -1
  30. package/dist/esm/core/aggregation/conditions.js +0 -102
  31. package/dist/esm/core/aggregation/conditions.js.map +0 -1
  32. package/dist/esm/core/aggregation/errors.js +0 -22
  33. package/dist/esm/core/aggregation/errors.js.map +0 -1
  34. package/dist/esm/core/aggregation/fallback-spend.js +0 -132
  35. package/dist/esm/core/aggregation/fallback-spend.js.map +0 -1
  36. package/dist/esm/core/aggregation/logger.js +0 -15
  37. package/dist/esm/core/aggregation/logger.js.map +0 -1
  38. package/dist/esm/core/aggregation/messages/base.js +0 -36
  39. package/dist/esm/core/aggregation/messages/base.js.map +0 -1
  40. package/dist/esm/core/aggregation/messages/bodies.js +0 -149
  41. package/dist/esm/core/aggregation/messages/bodies.js.map +0 -1
  42. package/dist/esm/core/aggregation/messages/constants.js +0 -37
  43. package/dist/esm/core/aggregation/messages/constants.js.map +0 -1
  44. package/dist/esm/core/aggregation/messages/factories.js +0 -139
  45. package/dist/esm/core/aggregation/messages/factories.js.map +0 -1
  46. package/dist/esm/core/aggregation/messages/guards.js +0 -40
  47. package/dist/esm/core/aggregation/messages/guards.js.map +0 -1
  48. package/dist/esm/core/aggregation/messages/index.js +0 -6
  49. package/dist/esm/core/aggregation/messages/index.js.map +0 -1
  50. package/dist/esm/core/aggregation/participant.js +0 -588
  51. package/dist/esm/core/aggregation/participant.js.map +0 -1
  52. package/dist/esm/core/aggregation/phases.js +0 -45
  53. package/dist/esm/core/aggregation/phases.js.map +0 -1
  54. package/dist/esm/core/aggregation/recovery-policy.js +0 -156
  55. package/dist/esm/core/aggregation/recovery-policy.js.map +0 -1
  56. package/dist/esm/core/aggregation/recovery-spend.js +0 -106
  57. package/dist/esm/core/aggregation/recovery-spend.js.map +0 -1
  58. package/dist/esm/core/aggregation/runner/aggregation-runner.js +0 -74
  59. package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +0 -1
  60. package/dist/esm/core/aggregation/runner/events.js +0 -2
  61. package/dist/esm/core/aggregation/runner/events.js.map +0 -1
  62. package/dist/esm/core/aggregation/runner/index.js +0 -6
  63. package/dist/esm/core/aggregation/runner/index.js.map +0 -1
  64. package/dist/esm/core/aggregation/runner/participant-runner.js +0 -407
  65. package/dist/esm/core/aggregation/runner/participant-runner.js.map +0 -1
  66. package/dist/esm/core/aggregation/runner/service-runner.js +0 -694
  67. package/dist/esm/core/aggregation/runner/service-runner.js.map +0 -1
  68. package/dist/esm/core/aggregation/runner/typed-emitter.js +0 -80
  69. package/dist/esm/core/aggregation/runner/typed-emitter.js.map +0 -1
  70. package/dist/esm/core/aggregation/service.js +0 -774
  71. package/dist/esm/core/aggregation/service.js.map +0 -1
  72. package/dist/esm/core/aggregation/signer.js +0 -33
  73. package/dist/esm/core/aggregation/signer.js.map +0 -1
  74. package/dist/esm/core/aggregation/signing-session.js +0 -195
  75. package/dist/esm/core/aggregation/signing-session.js.map +0 -1
  76. package/dist/esm/core/aggregation/transport/didcomm.js +0 -41
  77. package/dist/esm/core/aggregation/transport/didcomm.js.map +0 -1
  78. package/dist/esm/core/aggregation/transport/error.js +0 -12
  79. package/dist/esm/core/aggregation/transport/error.js.map +0 -1
  80. package/dist/esm/core/aggregation/transport/factory.js +0 -29
  81. package/dist/esm/core/aggregation/transport/factory.js.map +0 -1
  82. package/dist/esm/core/aggregation/transport/http/client.js +0 -350
  83. package/dist/esm/core/aggregation/transport/http/client.js.map +0 -1
  84. package/dist/esm/core/aggregation/transport/http/envelope.js +0 -126
  85. package/dist/esm/core/aggregation/transport/http/envelope.js.map +0 -1
  86. package/dist/esm/core/aggregation/transport/http/errors.js +0 -11
  87. package/dist/esm/core/aggregation/transport/http/errors.js.map +0 -1
  88. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +0 -45
  89. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +0 -1
  90. package/dist/esm/core/aggregation/transport/http/index.js +0 -12
  91. package/dist/esm/core/aggregation/transport/http/index.js.map +0 -1
  92. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +0 -38
  93. package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +0 -1
  94. package/dist/esm/core/aggregation/transport/http/protocol.js +0 -28
  95. package/dist/esm/core/aggregation/transport/http/protocol.js.map +0 -1
  96. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +0 -45
  97. package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +0 -1
  98. package/dist/esm/core/aggregation/transport/http/request-auth.js +0 -100
  99. package/dist/esm/core/aggregation/transport/http/request-auth.js.map +0 -1
  100. package/dist/esm/core/aggregation/transport/http/server.js +0 -481
  101. package/dist/esm/core/aggregation/transport/http/server.js.map +0 -1
  102. package/dist/esm/core/aggregation/transport/http/sse-stream.js +0 -110
  103. package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +0 -1
  104. package/dist/esm/core/aggregation/transport/http/sse-writer.js +0 -25
  105. package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +0 -1
  106. package/dist/esm/core/aggregation/transport/in-memory.js +0 -149
  107. package/dist/esm/core/aggregation/transport/in-memory.js.map +0 -1
  108. package/dist/esm/core/aggregation/transport/index.js +0 -8
  109. package/dist/esm/core/aggregation/transport/index.js.map +0 -1
  110. package/dist/esm/core/aggregation/transport/nostr.js +0 -491
  111. package/dist/esm/core/aggregation/transport/nostr.js.map +0 -1
  112. package/dist/esm/core/aggregation/transport/transport.js +0 -2
  113. package/dist/esm/core/aggregation/transport/transport.js.map +0 -1
  114. package/dist/types/core/aggregation/beacon-strategy.d.ts +0 -56
  115. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +0 -1
  116. package/dist/types/core/aggregation/cohort.d.ts +0 -182
  117. package/dist/types/core/aggregation/cohort.d.ts.map +0 -1
  118. package/dist/types/core/aggregation/conditions.d.ts +0 -86
  119. package/dist/types/core/aggregation/conditions.d.ts.map +0 -1
  120. package/dist/types/core/aggregation/errors.d.ts +0 -14
  121. package/dist/types/core/aggregation/errors.d.ts.map +0 -1
  122. package/dist/types/core/aggregation/fallback-spend.d.ts +0 -94
  123. package/dist/types/core/aggregation/fallback-spend.d.ts.map +0 -1
  124. package/dist/types/core/aggregation/logger.d.ts +0 -22
  125. package/dist/types/core/aggregation/logger.d.ts.map +0 -1
  126. package/dist/types/core/aggregation/messages/base.d.ts +0 -58
  127. package/dist/types/core/aggregation/messages/base.d.ts.map +0 -1
  128. package/dist/types/core/aggregation/messages/bodies.d.ts +0 -177
  129. package/dist/types/core/aggregation/messages/bodies.d.ts.map +0 -1
  130. package/dist/types/core/aggregation/messages/constants.d.ts +0 -31
  131. package/dist/types/core/aggregation/messages/constants.d.ts.map +0 -1
  132. package/dist/types/core/aggregation/messages/factories.d.ts +0 -217
  133. package/dist/types/core/aggregation/messages/factories.d.ts.map +0 -1
  134. package/dist/types/core/aggregation/messages/guards.d.ts +0 -11
  135. package/dist/types/core/aggregation/messages/guards.d.ts.map +0 -1
  136. package/dist/types/core/aggregation/messages/index.d.ts +0 -6
  137. package/dist/types/core/aggregation/messages/index.d.ts.map +0 -1
  138. package/dist/types/core/aggregation/participant.d.ts +0 -162
  139. package/dist/types/core/aggregation/participant.d.ts.map +0 -1
  140. package/dist/types/core/aggregation/phases.d.ts +0 -55
  141. package/dist/types/core/aggregation/phases.d.ts.map +0 -1
  142. package/dist/types/core/aggregation/recovery-policy.d.ts +0 -132
  143. package/dist/types/core/aggregation/recovery-policy.d.ts.map +0 -1
  144. package/dist/types/core/aggregation/recovery-spend.d.ts +0 -94
  145. package/dist/types/core/aggregation/recovery-spend.d.ts.map +0 -1
  146. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +0 -66
  147. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +0 -1
  148. package/dist/types/core/aggregation/runner/events.d.ts +0 -147
  149. package/dist/types/core/aggregation/runner/events.d.ts.map +0 -1
  150. package/dist/types/core/aggregation/runner/index.d.ts +0 -6
  151. package/dist/types/core/aggregation/runner/index.d.ts.map +0 -1
  152. package/dist/types/core/aggregation/runner/participant-runner.d.ts +0 -122
  153. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +0 -1
  154. package/dist/types/core/aggregation/runner/service-runner.d.ts +0 -211
  155. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +0 -1
  156. package/dist/types/core/aggregation/runner/typed-emitter.d.ts +0 -41
  157. package/dist/types/core/aggregation/runner/typed-emitter.d.ts.map +0 -1
  158. package/dist/types/core/aggregation/service.d.ts +0 -177
  159. package/dist/types/core/aggregation/service.d.ts.map +0 -1
  160. package/dist/types/core/aggregation/signer.d.ts +0 -50
  161. package/dist/types/core/aggregation/signer.d.ts.map +0 -1
  162. package/dist/types/core/aggregation/signing-session.d.ts +0 -81
  163. package/dist/types/core/aggregation/signing-session.d.ts.map +0 -1
  164. package/dist/types/core/aggregation/transport/didcomm.d.ts +0 -23
  165. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +0 -1
  166. package/dist/types/core/aggregation/transport/error.d.ts +0 -8
  167. package/dist/types/core/aggregation/transport/error.d.ts.map +0 -1
  168. package/dist/types/core/aggregation/transport/factory.d.ts +0 -28
  169. package/dist/types/core/aggregation/transport/factory.d.ts.map +0 -1
  170. package/dist/types/core/aggregation/transport/http/client.d.ts +0 -48
  171. package/dist/types/core/aggregation/transport/http/client.d.ts.map +0 -1
  172. package/dist/types/core/aggregation/transport/http/envelope.d.ts +0 -64
  173. package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +0 -1
  174. package/dist/types/core/aggregation/transport/http/errors.d.ts +0 -9
  175. package/dist/types/core/aggregation/transport/http/errors.d.ts.map +0 -1
  176. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +0 -32
  177. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +0 -1
  178. package/dist/types/core/aggregation/transport/http/index.d.ts +0 -12
  179. package/dist/types/core/aggregation/transport/http/index.d.ts.map +0 -1
  180. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +0 -26
  181. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +0 -1
  182. package/dist/types/core/aggregation/transport/http/protocol.d.ts +0 -53
  183. package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +0 -1
  184. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +0 -41
  185. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +0 -1
  186. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +0 -50
  187. package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +0 -1
  188. package/dist/types/core/aggregation/transport/http/server.d.ts +0 -110
  189. package/dist/types/core/aggregation/transport/http/server.d.ts.map +0 -1
  190. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +0 -34
  191. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +0 -1
  192. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +0 -12
  193. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +0 -1
  194. package/dist/types/core/aggregation/transport/in-memory.d.ts +0 -64
  195. package/dist/types/core/aggregation/transport/in-memory.d.ts.map +0 -1
  196. package/dist/types/core/aggregation/transport/index.d.ts +0 -8
  197. package/dist/types/core/aggregation/transport/index.d.ts.map +0 -1
  198. package/dist/types/core/aggregation/transport/nostr.d.ts +0 -153
  199. package/dist/types/core/aggregation/transport/nostr.d.ts.map +0 -1
  200. package/dist/types/core/aggregation/transport/transport.d.ts +0 -62
  201. package/dist/types/core/aggregation/transport/transport.d.ts.map +0 -1
  202. package/src/core/aggregation/beacon-strategy.ts +0 -142
  203. package/src/core/aggregation/cohort.ts +0 -415
  204. package/src/core/aggregation/conditions.ts +0 -172
  205. package/src/core/aggregation/errors.ts +0 -25
  206. package/src/core/aggregation/fallback-spend.ts +0 -223
  207. package/src/core/aggregation/logger.ts +0 -33
  208. package/src/core/aggregation/messages/base.ts +0 -80
  209. package/src/core/aggregation/messages/bodies.ts +0 -328
  210. package/src/core/aggregation/messages/constants.ts +0 -40
  211. package/src/core/aggregation/messages/factories.ts +0 -297
  212. package/src/core/aggregation/messages/guards.ts +0 -61
  213. package/src/core/aggregation/messages/index.ts +0 -5
  214. package/src/core/aggregation/participant.ts +0 -779
  215. package/src/core/aggregation/phases.ts +0 -91
  216. package/src/core/aggregation/recovery-policy.ts +0 -237
  217. package/src/core/aggregation/recovery-spend.ts +0 -192
  218. package/src/core/aggregation/runner/aggregation-runner.ts +0 -118
  219. package/src/core/aggregation/runner/events.ts +0 -129
  220. package/src/core/aggregation/runner/index.ts +0 -5
  221. package/src/core/aggregation/runner/participant-runner.ts +0 -488
  222. package/src/core/aggregation/runner/service-runner.ts +0 -866
  223. package/src/core/aggregation/runner/typed-emitter.ts +0 -87
  224. package/src/core/aggregation/service.ts +0 -1000
  225. package/src/core/aggregation/signer.ts +0 -67
  226. package/src/core/aggregation/signing-session.ts +0 -290
  227. package/src/core/aggregation/transport/didcomm.ts +0 -59
  228. package/src/core/aggregation/transport/error.ts +0 -13
  229. package/src/core/aggregation/transport/factory.ts +0 -65
  230. package/src/core/aggregation/transport/http/client.ts +0 -409
  231. package/src/core/aggregation/transport/http/envelope.ts +0 -204
  232. package/src/core/aggregation/transport/http/errors.ts +0 -11
  233. package/src/core/aggregation/transport/http/inbox-buffer.ts +0 -53
  234. package/src/core/aggregation/transport/http/index.ts +0 -11
  235. package/src/core/aggregation/transport/http/nonce-cache.ts +0 -43
  236. package/src/core/aggregation/transport/http/protocol.ts +0 -57
  237. package/src/core/aggregation/transport/http/rate-limiter.ts +0 -75
  238. package/src/core/aggregation/transport/http/request-auth.ts +0 -164
  239. package/src/core/aggregation/transport/http/server.ts +0 -615
  240. package/src/core/aggregation/transport/http/sse-stream.ts +0 -121
  241. package/src/core/aggregation/transport/http/sse-writer.ts +0 -23
  242. package/src/core/aggregation/transport/in-memory.ts +0 -178
  243. package/src/core/aggregation/transport/index.ts +0 -7
  244. package/src/core/aggregation/transport/nostr.ts +0 -576
  245. package/src/core/aggregation/transport/transport.ts +0 -79
@@ -7,13 +7,10 @@ import { Address, OutScript, p2pkh, p2tr, p2wpkh, Script, SigHash, Transaction }
7
7
  import type { BeaconProcessResult } from '../resolver.js';
8
8
  import type { SidecarData } from '../types.js';
9
9
  import { BeaconError } from './error.js';
10
- import { StaticFeeEstimator } from './fee-estimator.js';
10
+ import { DEFAULT_FEE_ESTIMATOR } from './fee-estimator.js';
11
11
  import type { FeeEstimator } from './fee-estimator.js';
12
12
  import type { BeaconService, BeaconSignal } from './interfaces.js';
13
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
14
  /**
18
15
  * Singleton beacon script kinds. Per the did:btcr2 spec, deterministic DID documents
19
16
  * include three beacon services: P2PKH, P2WPKH, and P2TR (taproot key-path), all
@@ -51,6 +48,54 @@ export const SINGLETON_BEACON_TX_VSIZE: Readonly<Record<SingletonScriptKind, num
51
48
  p2tr : P2TR_BEACON_TX_VSIZE,
52
49
  };
53
50
 
51
+ /**
52
+ * Serialized size (vbytes) of a single change output, by script kind:
53
+ * 8 (value) + 1 (scriptPubKey length) + scriptPubKey bytes. P2PKH 25, P2WPKH 22,
54
+ * P2TR 34. These are non-witness bytes, so each contributes its full byte count to
55
+ * the transaction vsize. The {@link SINGLETON_BEACON_TX_VSIZE} constants bake in a
56
+ * same-kind change output; {@link beaconTxVsize} uses these deltas to re-size the
57
+ * fee when a caller routes change to an address of a different kind (ADR 044).
58
+ */
59
+ export const CHANGE_OUTPUT_VBYTES: Readonly<Record<SingletonScriptKind, number>> = {
60
+ p2pkh : 34,
61
+ p2wpkh : 31,
62
+ p2tr : 43,
63
+ };
64
+
65
+ /**
66
+ * Dust threshold (sats) below which a change output is not worth creating, by script
67
+ * kind (the standard Bitcoin Core dust relay thresholds at the default 3 sat/vB dust
68
+ * rate). When the change after fees falls below this, the builders omit the change
69
+ * output and let the remainder fall into the fee rather than emit an unspendable,
70
+ * relay-rejected dust output (ADR 044).
71
+ */
72
+ export const DUST_LIMIT_SATS: Readonly<Record<SingletonScriptKind, number>> = {
73
+ p2pkh : 546,
74
+ p2wpkh : 294,
75
+ p2tr : 330,
76
+ };
77
+
78
+ /**
79
+ * vsize (vbytes) for a beacon transaction that spends one input of `beaconKind`
80
+ * and returns change to an output of `changeKind`, plus the OP_RETURN(32) signal.
81
+ *
82
+ * When `changeKind === beaconKind` (the default, change to the beacon address) this
83
+ * returns the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant unchanged, so the
84
+ * default path and the constants' lock-in tests are byte-identical. A differing
85
+ * `changeKind` swaps the assumed same-kind change output for the actual one, keeping
86
+ * the result a valid upper bound. The aggregation key-path spend is the
87
+ * `beaconKind: 'p2tr'` case (its input is always the cohort's P2TR key path; only the
88
+ * change output varies), the analytical sizing ADR 045 calls for, computed without a
89
+ * secret.
90
+ */
91
+ export function beaconTxVsize(
92
+ beaconKind: SingletonScriptKind,
93
+ changeKind: SingletonScriptKind,
94
+ ): number {
95
+ const base = SINGLETON_BEACON_TX_VSIZE[beaconKind] - CHANGE_OUTPUT_VBYTES[beaconKind];
96
+ return base + CHANGE_OUTPUT_VBYTES[changeKind];
97
+ }
98
+
54
99
  /**
55
100
  * Detect the singleton script kind of a Bitcoin address (P2PKH / P2WPKH / P2TR).
56
101
  * The deterministic-DID document emits all three kinds; the broadcast path needs
@@ -88,12 +133,56 @@ export function deriveSingletonAddress(
88
133
  return p2tr(pubkey.slice(1, 33), undefined, network).address!;
89
134
  }
90
135
 
136
+ /**
137
+ * Resolve the change-output recipient for a beacon transaction. Returns the beacon
138
+ * address when no change address is supplied (preserving the prior behavior of
139
+ * returning change to the spent address), otherwise validates the caller-supplied
140
+ * address against the network and returns it. Validating here fails fast rather than
141
+ * burning a real UTXO on a transaction that breaks at broadcast (ADR 044).
142
+ */
143
+ export function resolveChangeAddress(
144
+ beaconAddress: string,
145
+ network: BTCNetwork,
146
+ changeAddress?: string,
147
+ ): string {
148
+ if(!changeAddress || changeAddress === beaconAddress) return beaconAddress;
149
+ try {
150
+ Address(network).decode(changeAddress);
151
+ } catch {
152
+ throw new BeaconError(
153
+ `Invalid change address "${changeAddress}" for network "${network}".`,
154
+ 'INVALID_CHANGE_ADDRESS',
155
+ { changeAddress, network }
156
+ );
157
+ }
158
+ return changeAddress;
159
+ }
160
+
161
+ /**
162
+ * Detect the change output's script kind for fee sizing. A change address that is not
163
+ * one of the three singleton kinds (for example P2SH or P2WSH) is sized as P2TR, the
164
+ * largest standard change output, so the estimated fee stays a valid upper bound.
165
+ */
166
+ function changeOutputKind(changeAddress: string, network: BTCNetwork): SingletonScriptKind {
167
+ try {
168
+ return detectSingletonScriptKind(changeAddress, network);
169
+ } catch {
170
+ return 'p2tr';
171
+ }
172
+ }
173
+
91
174
  /**
92
175
  * Options accepted by {@link SinglePartyBeacon.buildSignAndBroadcast} and related helpers.
93
176
  */
94
177
  export interface BroadcastOptions {
95
178
  /** Fee estimator for computing the transaction fee. Defaults to {@link DEFAULT_FEE_ESTIMATOR}. */
96
179
  feeEstimator?: FeeEstimator;
180
+ /**
181
+ * Address to send change to. Defaults to the beacon address (reuses the spent
182
+ * address, the prior behavior). Supply a fresh address the controller owns to
183
+ * stop linking the beacon's announcements into one on-chain chain (ADR 044).
184
+ */
185
+ changeAddress?: string;
97
186
  }
98
187
 
99
188
  /**
@@ -107,8 +196,10 @@ export interface BeaconTxPlan {
107
196
  prevOutScripts: Uint8Array[];
108
197
  /** Amounts (sats) of the consumed previous outputs. */
109
198
  prevOutValues: bigint[];
110
- /** Address change was sent back to (same as the beacon address). */
199
+ /** The beacon address this tx spends from. */
111
200
  beaconAddress: string;
201
+ /** Address the change output was sent to (the beacon address unless a change address was supplied). */
202
+ changeAddress: string;
112
203
  /** The UTXO this tx consumes. */
113
204
  utxo: AddressUtxo;
114
205
  /** The fee (sats) already deducted from the change output. */
@@ -188,9 +279,17 @@ export async function buildAggregationBeaconTx(opts: {
188
279
  network: BTCNetwork;
189
280
  /** Optional fee estimator (defaults to 5 sat/vB). */
190
281
  feeEstimator?: FeeEstimator;
282
+ /**
283
+ * Address to send change to. Defaults to the beacon (cohort) address. Supply the
284
+ * funder's address (an operator-funded cohort's funding wallet) to stop reusing the
285
+ * cohort address for change (ADR 044). Change ownership is the funder's call, which
286
+ * the cohort-condition model leaves to the caller (ADR 039).
287
+ */
288
+ changeAddress?: string;
191
289
  }): Promise<BeaconTxPlan> {
192
290
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
193
291
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
292
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, opts.network, opts.changeAddress);
194
293
 
195
294
  // The funded beacon output is a Taproot script-tree output: key path is the
196
295
  // MuSig2 aggregate, script path is the k-of-n fallback + CSV recovery leaves
@@ -200,8 +299,11 @@ export async function buildAggregationBeaconTx(opts: {
200
299
  // key-path sighash and the fallback script-path sighash.
201
300
  const witnessScript = OutScript.encode(Address(opts.network).decode(opts.beaconAddress));
202
301
 
203
- // Fee cannot be probe-measured (no secret key for MuSig2 round). Use fixed P2TR vsize.
204
- const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
302
+ // The fee cannot be probe-measured (no secret key until the downstream MuSig2
303
+ // round), so size it analytically. The input is the cohort's P2TR key path; only
304
+ // the change output's kind varies, so the vsize follows the change address (ADR 045).
305
+ const changeKind = changeOutputKind(changeAddress, opts.network);
306
+ const feeSats = await feeEstimator.estimateFee(beaconTxVsize('p2tr', changeKind));
205
307
  if(BigInt(utxo.value) <= feeSats) {
206
308
  throw new BeaconError(
207
309
  `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
@@ -221,7 +323,12 @@ export async function buildAggregationBeaconTx(opts: {
221
323
  witnessUtxo : { amount: BigInt(utxo.value), script: witnessScript },
222
324
  tapInternalKey : opts.internalPubkey,
223
325
  });
224
- tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
326
+ // Change first (omitted when it would be dust, sweeping the remainder into the
327
+ // fee), then the OP_RETURN signal, which the spec requires to be the last output.
328
+ const changeValue = BigInt(utxo.value) - feeSats;
329
+ if(changeValue >= BigInt(DUST_LIMIT_SATS[changeKind])) {
330
+ tx.addOutputAddress(changeAddress, changeValue, opts.network);
331
+ }
225
332
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
226
333
 
227
334
  return {
@@ -229,6 +336,7 @@ export async function buildAggregationBeaconTx(opts: {
229
336
  prevOutScripts : [witnessScript],
230
337
  prevOutValues : [BigInt(utxo.value)],
231
338
  beaconAddress : opts.beaconAddress,
339
+ changeAddress,
232
340
  utxo,
233
341
  feeSats,
234
342
  scriptKind : 'p2tr',
@@ -404,6 +512,7 @@ export abstract class SinglePartyBeacon {
404
512
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
405
513
  const plan = await this.buildSinglePartyTx({
406
514
  signalBytes, beaconAddress, utxo, prevTxBytes, signer, bitcoin, feeEstimator,
515
+ changeAddress : options?.changeAddress,
407
516
  });
408
517
  const signedHex = await this.signSinglePartyTx(plan, signer);
409
518
  return this.broadcastRawTx(bitcoin, signedHex);
@@ -416,8 +525,9 @@ export abstract class SinglePartyBeacon {
416
525
  * the input accordingly. Validates that the signer's pubkey produces the beacon
417
526
  * address under that script kind: without this check, a misconfigured caller
418
527
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
419
- * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
420
- * round-trip.
528
+ * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant (via {@link beaconTxVsize}),
529
+ * avoiding any probe-sign round-trip; a change address of a different kind re-sizes
530
+ * the fee by the change output's size delta so it stays a valid upper bound.
421
531
  */
422
532
  protected async buildSinglePartyTx(opts: {
423
533
  signalBytes: Uint8Array;
@@ -427,10 +537,12 @@ export abstract class SinglePartyBeacon {
427
537
  signer: Signer;
428
538
  bitcoin: BitcoinConnection;
429
539
  feeEstimator: FeeEstimator;
540
+ changeAddress?: string;
430
541
  }): Promise<BeaconTxPlan> {
431
542
  const network = opts.bitcoin.data;
432
543
  const pubkey = opts.signer.publicKey;
433
544
  const kind = detectSingletonScriptKind(opts.beaconAddress, network);
545
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, network, opts.changeAddress);
434
546
 
435
547
  const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
436
548
  if(derivedAddress !== opts.beaconAddress) {
@@ -441,7 +553,8 @@ export abstract class SinglePartyBeacon {
441
553
  );
442
554
  }
443
555
 
444
- const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
556
+ const changeKind = changeOutputKind(changeAddress, network);
557
+ const feeSats = await opts.feeEstimator.estimateFee(beaconTxVsize(kind, changeKind));
445
558
  const amount = BigInt(opts.utxo.value);
446
559
  if(amount <= feeSats) {
447
560
  throw new BeaconError(
@@ -487,7 +600,12 @@ export abstract class SinglePartyBeacon {
487
600
  });
488
601
  }
489
602
 
490
- tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
603
+ // Change first (omitted when it would be dust, sweeping the remainder into the
604
+ // fee), then the OP_RETURN signal, which the spec requires to be the last output.
605
+ const changeValue = amount - feeSats;
606
+ if(changeValue >= BigInt(DUST_LIMIT_SATS[changeKind])) {
607
+ tx.addOutputAddress(changeAddress, changeValue, network);
608
+ }
491
609
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
492
610
 
493
611
  return {
@@ -495,6 +613,7 @@ export abstract class SinglePartyBeacon {
495
613
  prevOutScripts : [prevOutScript],
496
614
  prevOutValues : [amount],
497
615
  beaconAddress : opts.beaconAddress,
616
+ changeAddress,
498
617
  utxo : opts.utxo,
499
618
  feeSats,
500
619
  scriptKind : kind,
@@ -1,52 +1,18 @@
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
- }
1
+ // The FeeEstimator contract and the StaticFeeEstimator implementation live in
2
+ // @did-btcr2/bitcoin, the natural home for a sat/vB fee primitive shared by the
3
+ // single-party and aggregated beacon broadcast paths. They are re-exported here so
4
+ // the @did-btcr2/method public surface is unchanged.
5
+ import { StaticFeeEstimator } from '@did-btcr2/bitcoin';
6
+ import type { FeeEstimator } from '@did-btcr2/bitcoin';
7
+
8
+ export { StaticFeeEstimator };
9
+ export type { FeeEstimator };
21
10
 
22
11
  /**
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).
12
+ * Default fee estimator used when a caller supplies none: a static 5 sat/vB rate.
13
+ * Suitable for tests and regtest. Production callers should inject a dynamic
14
+ * estimator (a mempool API, or Bitcoin Core `estimatesmartfee`) at the point the
15
+ * beacon transaction is built (single-party broadcast options, or the aggregation
16
+ * service runner's fee estimator).
32
17
  */
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
- }
18
+ export const DEFAULT_FEE_ESTIMATOR: FeeEstimator = new StaticFeeEstimator(5);
@@ -0,0 +1,25 @@
1
+ import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
2
+ import { Identifier } from './identifier.js';
3
+
4
+ /**
5
+ * Resolve a did:btcr2 sender's communication public key from its DID, for the
6
+ * aggregation HTTP transport's `resolveSenderPk` option: a KEY identifier decodes to
7
+ * its genesis public key. The aggregation transport is DID-method-agnostic and does
8
+ * not name `Identifier`; method supplies this resolver when it wires the transport so
9
+ * a sender that is not a pre-registered peer can still be authenticated from its DID.
10
+ *
11
+ * @param did The sender's DID.
12
+ * @returns The sender's compressed public key, or `undefined` when the DID is not a
13
+ * decodable did:btcr2 KEY identifier (resolution then falls back to registered peers).
14
+ */
15
+ export function resolveBtcr2SenderPk(did: string): CompressedSecp256k1PublicKey | undefined {
16
+ try {
17
+ const components = Identifier.decode(did);
18
+ if(components.idType === 'KEY') {
19
+ return new CompressedSecp256k1PublicKey(components.genesisBytes);
20
+ }
21
+ } catch {
22
+ // Not a decodable did:btcr2 KEY identifier.
23
+ }
24
+ return undefined;
25
+ }
package/src/index.ts CHANGED
@@ -1,20 +1,5 @@
1
- // Aggregation
2
- export * from './core/aggregation/service.js';
3
- export * from './core/aggregation/participant.js';
4
- export * from './core/aggregation/signer.js';
5
- export * from './core/aggregation/conditions.js';
6
- export * from './core/aggregation/recovery-policy.js';
7
- export * from './core/aggregation/recovery-spend.js';
8
- export * from './core/aggregation/fallback-spend.js';
9
- export * from './core/aggregation/cohort.js';
10
- export * from './core/aggregation/signing-session.js';
11
- export * from './core/aggregation/phases.js';
12
- export * from './core/aggregation/errors.js';
13
- export * from './core/aggregation/beacon-strategy.js';
14
- export * from './core/aggregation/logger.js';
15
- export * from './core/aggregation/messages/index.js';
16
- export * from './core/aggregation/transport/index.js';
17
- export * from './core/aggregation/runner/index.js';
1
+ // Aggregation (extracted to @did-btcr2/aggregation; re-exported here for backward compatibility)
2
+ export * from '@did-btcr2/aggregation';
18
3
 
19
4
  // Beacons
20
5
  export * from './core/beacon/beacon.js';
@@ -29,6 +14,7 @@ export * from './core/beacon/smt-beacon.js';
29
14
  export * from './core/beacon/utils.js';
30
15
 
31
16
  // Core
17
+ export * from './core/did-sender-resolver.js';
32
18
  export * from './core/identifier.js';
33
19
  export * from './core/interfaces.js';
34
20
  export * from './core/resolver.js';
@@ -1,76 +0,0 @@
1
- import { canonicalize } from '@did-btcr2/common';
2
- import { base64UrlToHash, blockHash, didToIndex, hashToBase64Url, verifySerializedProof } from '@did-btcr2/smt';
3
- const CAS_STRATEGY = {
4
- type: 'CASBeacon',
5
- buildAggregatedData(cohort) {
6
- cohort.buildCASAnnouncement();
7
- },
8
- getDistributePayload(cohort) {
9
- return { casAnnouncement: cohort.casAnnouncement };
10
- },
11
- validateParticipantView({ participantDid, included, expectedHash, body }) {
12
- const casAnnouncement = body.casAnnouncement;
13
- if (!casAnnouncement)
14
- return { matches: false };
15
- if (!included) {
16
- // Cooperative non-inclusion: the decliner must be ABSENT from the map.
17
- return { matches: !(participantDid in casAnnouncement), casAnnouncement };
18
- }
19
- return {
20
- matches: casAnnouncement[participantDid] === expectedHash,
21
- casAnnouncement,
22
- };
23
- },
24
- };
25
- const SMT_STRATEGY = {
26
- type: 'SMTBeacon',
27
- buildAggregatedData(cohort) {
28
- cohort.buildSMTTree();
29
- },
30
- getDistributePayload(cohort, participantDid) {
31
- const proof = cohort.smtProofs?.get(participantDid);
32
- return { smtProof: proof };
33
- },
34
- validateParticipantView({ participantDid, included, submittedUpdate, body }) {
35
- const smtProof = body.smtProof;
36
- const index = didToIndex(participantDid);
37
- if (!included) {
38
- // Cooperative non-inclusion: the proof has a nonce but no updateId by
39
- // construction. The leaf is SHA-256(SHA-256(nonce)); do NOT run the
40
- // inclusion-only updateId guard (a missing updateId is correct here).
41
- if (!smtProof?.nonce || smtProof?.updateId)
42
- return { matches: false, smtProof };
43
- const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)));
44
- return { matches: verifySerializedProof(smtProof, index, candidateHash), smtProof };
45
- }
46
- if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate)
47
- return { matches: false, smtProof };
48
- // Verify updateId matches the canonicalized update hash. Proof hash fields
49
- // are base64url (no padding) per the SMT Proof spec.
50
- const canonicalBytes = new TextEncoder().encode(canonicalize(submittedUpdate));
51
- const expectedUpdateId = hashToBase64Url(blockHash(canonicalBytes));
52
- if (smtProof.updateId !== expectedUpdateId) {
53
- return { matches: false, smtProof };
54
- }
55
- // Verify Merkle inclusion. The leaf is SHA-256(SHA-256(nonce) || SHA-256(update)).
56
- const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)), base64UrlToHash(smtProof.updateId));
57
- return {
58
- matches: verifySerializedProof(smtProof, index, candidateHash),
59
- smtProof,
60
- };
61
- },
62
- };
63
- /** Registered strategies keyed by `beaconType` string. */
64
- const STRATEGIES = new Map([
65
- [CAS_STRATEGY.type, CAS_STRATEGY],
66
- [SMT_STRATEGY.type, SMT_STRATEGY],
67
- ]);
68
- /** Register a custom beacon strategy. Overwrites any existing entry with the same type. */
69
- export function registerBeaconStrategy(strategy) {
70
- STRATEGIES.set(strategy.type, strategy);
71
- }
72
- /** Look up a registered beacon strategy by type, or undefined if not registered. */
73
- export function getBeaconStrategy(type) {
74
- return STRATEGIES.get(type);
75
- }
76
- //# sourceMappingURL=beacon-strategy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"beacon-strategy.js","sourceRoot":"","sources":["../../../../src/core/aggregation/beacon-strategy.ts"],"names":[],"mappings":"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"}