@did-btcr2/method 0.28.0 → 0.32.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 (193) hide show
  1. package/README.md +13 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/browser.js +34125 -44647
  4. package/dist/browser.mjs +26409 -36931
  5. package/dist/cjs/index.js +2869 -679
  6. package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
  7. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
  8. package/dist/esm/core/aggregation/cohort.js +31 -8
  9. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  10. package/dist/esm/core/aggregation/logger.js +15 -0
  11. package/dist/esm/core/aggregation/logger.js.map +1 -0
  12. package/dist/esm/core/aggregation/messages/base.js +12 -1
  13. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  14. package/dist/esm/core/aggregation/messages/bodies.js +90 -0
  15. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
  16. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  17. package/dist/esm/core/aggregation/messages/index.js +1 -0
  18. package/dist/esm/core/aggregation/messages/index.js.map +1 -1
  19. package/dist/esm/core/aggregation/participant.js +39 -46
  20. package/dist/esm/core/aggregation/participant.js.map +1 -1
  21. package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
  22. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  23. package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
  24. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  25. package/dist/esm/core/aggregation/service.js +143 -15
  26. package/dist/esm/core/aggregation/service.js.map +1 -1
  27. package/dist/esm/core/aggregation/signing-session.js +44 -5
  28. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  29. package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
  30. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
  31. package/dist/esm/core/aggregation/transport/factory.js +15 -6
  32. package/dist/esm/core/aggregation/transport/factory.js.map +1 -1
  33. package/dist/esm/core/aggregation/transport/http/client.js +350 -0
  34. package/dist/esm/core/aggregation/transport/http/client.js.map +1 -0
  35. package/dist/esm/core/aggregation/transport/http/envelope.js +126 -0
  36. package/dist/esm/core/aggregation/transport/http/envelope.js.map +1 -0
  37. package/dist/esm/core/aggregation/transport/http/errors.js +11 -0
  38. package/dist/esm/core/aggregation/transport/http/errors.js.map +1 -0
  39. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +45 -0
  40. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +1 -0
  41. package/dist/esm/core/aggregation/transport/http/index.js +12 -0
  42. package/dist/esm/core/aggregation/transport/http/index.js.map +1 -0
  43. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +38 -0
  44. package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +1 -0
  45. package/dist/esm/core/aggregation/transport/http/protocol.js +28 -0
  46. package/dist/esm/core/aggregation/transport/http/protocol.js.map +1 -0
  47. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +45 -0
  48. package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +1 -0
  49. package/dist/esm/core/aggregation/transport/http/request-auth.js +100 -0
  50. package/dist/esm/core/aggregation/transport/http/request-auth.js.map +1 -0
  51. package/dist/esm/core/aggregation/transport/http/server.js +481 -0
  52. package/dist/esm/core/aggregation/transport/http/server.js.map +1 -0
  53. package/dist/esm/core/aggregation/transport/http/sse-stream.js +110 -0
  54. package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +1 -0
  55. package/dist/esm/core/aggregation/transport/http/sse-writer.js +25 -0
  56. package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +1 -0
  57. package/dist/esm/core/aggregation/transport/index.js +1 -0
  58. package/dist/esm/core/aggregation/transport/index.js.map +1 -1
  59. package/dist/esm/core/aggregation/transport/nostr.js +245 -16
  60. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
  61. package/dist/esm/core/beacon/beacon.js +295 -63
  62. package/dist/esm/core/beacon/beacon.js.map +1 -1
  63. package/dist/esm/core/beacon/cas-beacon.js +3 -3
  64. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  65. package/dist/esm/core/beacon/singleton-beacon.js +3 -3
  66. package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
  67. package/dist/esm/core/beacon/smt-beacon.js +3 -3
  68. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  69. package/dist/esm/core/beacon/utils.js +14 -9
  70. package/dist/esm/core/beacon/utils.js.map +1 -1
  71. package/dist/esm/core/updater.js +63 -55
  72. package/dist/esm/core/updater.js.map +1 -1
  73. package/dist/esm/did-btcr2.js +0 -4
  74. package/dist/esm/did-btcr2.js.map +1 -1
  75. package/dist/esm/index.js +2 -0
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/utils/did-document.js +2 -2
  78. package/dist/esm/utils/did-document.js.map +1 -1
  79. package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
  80. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
  81. package/dist/types/core/aggregation/cohort.d.ts +20 -3
  82. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  83. package/dist/types/core/aggregation/logger.d.ts +22 -0
  84. package/dist/types/core/aggregation/logger.d.ts.map +1 -0
  85. package/dist/types/core/aggregation/messages/base.d.ts +13 -1
  86. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  87. package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
  88. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
  89. package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
  90. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  91. package/dist/types/core/aggregation/messages/index.d.ts +1 -0
  92. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
  93. package/dist/types/core/aggregation/participant.d.ts +2 -0
  94. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  95. package/dist/types/core/aggregation/runner/events.d.ts +32 -6
  96. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  97. package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
  98. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  99. package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
  100. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  101. package/dist/types/core/aggregation/service.d.ts +33 -2
  102. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  103. package/dist/types/core/aggregation/signing-session.d.ts +5 -1
  104. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  105. package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
  106. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
  107. package/dist/types/core/aggregation/transport/factory.d.ts +22 -7
  108. package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -1
  109. package/dist/types/core/aggregation/transport/http/client.d.ts +48 -0
  110. package/dist/types/core/aggregation/transport/http/client.d.ts.map +1 -0
  111. package/dist/types/core/aggregation/transport/http/envelope.d.ts +64 -0
  112. package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +1 -0
  113. package/dist/types/core/aggregation/transport/http/errors.d.ts +9 -0
  114. package/dist/types/core/aggregation/transport/http/errors.d.ts.map +1 -0
  115. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +32 -0
  116. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +1 -0
  117. package/dist/types/core/aggregation/transport/http/index.d.ts +12 -0
  118. package/dist/types/core/aggregation/transport/http/index.d.ts.map +1 -0
  119. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +26 -0
  120. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +1 -0
  121. package/dist/types/core/aggregation/transport/http/protocol.d.ts +53 -0
  122. package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +1 -0
  123. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +41 -0
  124. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +1 -0
  125. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +50 -0
  126. package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +1 -0
  127. package/dist/types/core/aggregation/transport/http/server.d.ts +110 -0
  128. package/dist/types/core/aggregation/transport/http/server.d.ts.map +1 -0
  129. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +34 -0
  130. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +1 -0
  131. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +12 -0
  132. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +1 -0
  133. package/dist/types/core/aggregation/transport/index.d.ts +1 -0
  134. package/dist/types/core/aggregation/transport/index.d.ts.map +1 -1
  135. package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
  136. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
  137. package/dist/types/core/aggregation/transport/transport.d.ts +26 -1
  138. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  139. package/dist/types/core/beacon/beacon.d.ts +149 -22
  140. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  141. package/dist/types/core/beacon/cas-beacon.d.ts +3 -3
  142. package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
  143. package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
  144. package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
  145. package/dist/types/core/beacon/smt-beacon.d.ts +3 -3
  146. package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
  147. package/dist/types/core/beacon/utils.d.ts +2 -2
  148. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  149. package/dist/types/core/updater.d.ts +27 -12
  150. package/dist/types/core/updater.d.ts.map +1 -1
  151. package/dist/types/did-btcr2.d.ts.map +1 -1
  152. package/dist/types/index.d.ts +2 -0
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/package.json +5 -7
  155. package/src/core/aggregation/beacon-strategy.ts +123 -0
  156. package/src/core/aggregation/cohort.ts +34 -8
  157. package/src/core/aggregation/logger.ts +33 -0
  158. package/src/core/aggregation/messages/base.ts +20 -5
  159. package/src/core/aggregation/messages/bodies.ts +223 -0
  160. package/src/core/aggregation/messages/factories.ts +1 -0
  161. package/src/core/aggregation/messages/index.ts +1 -0
  162. package/src/core/aggregation/participant.ts +40 -46
  163. package/src/core/aggregation/runner/events.ts +27 -3
  164. package/src/core/aggregation/runner/participant-runner.ts +41 -7
  165. package/src/core/aggregation/runner/service-runner.ts +227 -19
  166. package/src/core/aggregation/service.ts +189 -20
  167. package/src/core/aggregation/signing-session.ts +65 -7
  168. package/src/core/aggregation/transport/didcomm.ts +17 -0
  169. package/src/core/aggregation/transport/factory.ts +48 -12
  170. package/src/core/aggregation/transport/http/client.ts +409 -0
  171. package/src/core/aggregation/transport/http/envelope.ts +204 -0
  172. package/src/core/aggregation/transport/http/errors.ts +11 -0
  173. package/src/core/aggregation/transport/http/inbox-buffer.ts +53 -0
  174. package/src/core/aggregation/transport/http/index.ts +11 -0
  175. package/src/core/aggregation/transport/http/nonce-cache.ts +43 -0
  176. package/src/core/aggregation/transport/http/protocol.ts +57 -0
  177. package/src/core/aggregation/transport/http/rate-limiter.ts +75 -0
  178. package/src/core/aggregation/transport/http/request-auth.ts +164 -0
  179. package/src/core/aggregation/transport/http/server.ts +615 -0
  180. package/src/core/aggregation/transport/http/sse-stream.ts +121 -0
  181. package/src/core/aggregation/transport/http/sse-writer.ts +23 -0
  182. package/src/core/aggregation/transport/index.ts +1 -0
  183. package/src/core/aggregation/transport/nostr.ts +266 -23
  184. package/src/core/aggregation/transport/transport.ts +34 -1
  185. package/src/core/beacon/beacon.ts +411 -79
  186. package/src/core/beacon/cas-beacon.ts +4 -4
  187. package/src/core/beacon/singleton-beacon.ts +4 -4
  188. package/src/core/beacon/smt-beacon.ts +4 -4
  189. package/src/core/beacon/utils.ts +16 -11
  190. package/src/core/updater.ts +113 -67
  191. package/src/did-btcr2.ts +0 -5
  192. package/src/index.ts +2 -0
  193. package/src/utils/did-document.ts +2 -2
@@ -1,8 +1,8 @@
1
+ import type { BTCNetwork } from '@did-btcr2/bitcoin';
1
2
  import { getNetwork } from '@did-btcr2/bitcoin';
2
3
  import type { KeyBytes, Maybe} from '@did-btcr2/common';
3
4
  import { DidMethodError, MethodError } from '@did-btcr2/common';
4
- import type { networks} from 'bitcoinjs-lib';
5
- import { payments } from 'bitcoinjs-lib';
5
+ import { p2pkh, p2tr, p2wpkh } from '@scure/btc-signer';
6
6
  import { Appendix } from '../../utils/appendix.js';
7
7
  import type { DidDocument } from '../../utils/did-document.js';
8
8
  import { Identifier } from '../identifier.js';
@@ -100,7 +100,12 @@ export class BeaconUtils {
100
100
  // Build the id
101
101
  const id = `${did}#initial${addressType.toUpperCase()}`;
102
102
  // Generate the bitcoin address
103
- const serviceEndpoint = `bitcoin:${payments[addressType]({ pubkey, network }).address}`;
103
+ const address = addressType === 'p2tr'
104
+ ? p2tr(pubkey.slice(1, 33), undefined, network).address
105
+ : addressType === 'p2wpkh'
106
+ ? p2wpkh(pubkey, network).address
107
+ : p2pkh(pubkey, network).address;
108
+ const serviceEndpoint = `bitcoin:${address}`;
104
109
  // Return the beacon serviceD
105
110
  return { id, type: beaconType, serviceEndpoint, };
106
111
  } catch (error: any) {
@@ -120,16 +125,16 @@ export class BeaconUtils {
120
125
  static generateBeaconServices({ id, publicKey, network, beaconType }: {
121
126
  id: string;
122
127
  publicKey: KeyBytes;
123
- network: networks.Network;
128
+ network: BTCNetwork;
124
129
  beaconType: string;
125
130
  }): Array<BeaconService> {
126
131
  try {
127
132
  // Generate the bitcoin addresses for the given public key and network
128
- const p2pkh = payments.p2pkh({ pubkey: publicKey, network }).address;
129
- const p2wpkh = payments.p2wpkh({ pubkey: publicKey, network }).address;
130
- const p2tr = payments.p2tr({ network, internalPubkey: publicKey.slice(1, 33) }).address;
133
+ const p2pkhAddr = p2pkh(publicKey, network).address;
134
+ const p2wpkhAddr = p2wpkh(publicKey, network).address;
135
+ const p2trAddr = p2tr(publicKey.slice(1, 33), undefined, network).address;
131
136
  // Check that all addresses were generated successfully
132
- if (!p2pkh || !p2wpkh || !p2tr) {
137
+ if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
133
138
  throw new DidMethodError('Failed to generate bitcoin addresses');
134
139
  }
135
140
  // Return the beacon services with the generated addresses as service endpoints
@@ -137,17 +142,17 @@ export class BeaconUtils {
137
142
  {
138
143
  id : `${id}#initialP2PKH`,
139
144
  type : beaconType,
140
- serviceEndpoint : `bitcoin:${p2pkh}`
145
+ serviceEndpoint : `bitcoin:${p2pkhAddr}`
141
146
  },
142
147
  {
143
148
  id : `${id}#initialP2WPKH`,
144
149
  type : beaconType,
145
- serviceEndpoint : `bitcoin:${p2wpkh}`
150
+ serviceEndpoint : `bitcoin:${p2wpkhAddr}`
146
151
  },
147
152
  {
148
153
  id : `${id}#initialP2TR`,
149
154
  type : beaconType,
150
- serviceEndpoint : `bitcoin:${p2tr}`
155
+ serviceEndpoint : `bitcoin:${p2trAddr}`
151
156
  },
152
157
  ];
153
158
  } catch (error: any) {
@@ -1,7 +1,8 @@
1
1
  import type { BitcoinConnection } from '@did-btcr2/bitcoin';
2
- import type { KeyBytes, PatchOperation } from '@did-btcr2/common';
2
+ import type { PatchOperation } from '@did-btcr2/common';
3
3
  import { canonicalHash, INVALID_DID_UPDATE, JSONPatch, UpdateError } from '@did-btcr2/common';
4
4
  import { SchnorrMultikey, type DataIntegrityConfig, type SignedBTCR2Update, type UnsignedBTCR2Update } from '@did-btcr2/cryptosuite';
5
+ import type { Signer } from '@did-btcr2/keypair';
5
6
  import { DidDocument, type Btcr2DidDocument, type DidVerificationMethod } from '../utils/did-document.js';
6
7
  import { BeaconFactory } from './beacon/factory.js';
7
8
  import type { BeaconService } from './beacon/interfaces.js';
@@ -9,9 +10,10 @@ import type { BeaconService } from './beacon/interfaces.js';
9
10
  // ─── DataNeed types ──────────────────────────────────────────────────────────
10
11
 
11
12
  /**
12
- * The updater needs the caller to supply a signing key (or a KMS-backed signature)
13
- * for the given verification method. The unsigned update is attached so the caller
14
- * can inspect it before producing a signature.
13
+ * The updater needs the caller to supply a {@link Signer} for the given
14
+ * verification method. The unsigned update is attached so the caller can
15
+ * inspect it before producing a signature. The signer can wrap a local secret
16
+ * key (`LocalSigner`), a KMS-managed key (`KeyManagerSigner`), or any custom backend.
15
17
  */
16
18
  export interface NeedSigningKey {
17
19
  readonly kind: 'NeedSigningKey';
@@ -36,6 +38,19 @@ export interface NeedFunding {
36
38
  readonly beaconService: BeaconService;
37
39
  }
38
40
 
41
+ /**
42
+ * Optional proof the caller passes when fulfilling {@link NeedFunding}. The
43
+ * state machine asserts the proof before transitioning to Broadcast. Sans-I/O
44
+ * is preserved: the caller still performs the UTXO lookup; this is just a
45
+ * contract-level handshake.
46
+ */
47
+ export interface FundingProof {
48
+ /** Number of spendable UTXOs the caller observed at the beacon address. Must be >= 1. */
49
+ utxoCount: number;
50
+ /** Optional txid the caller funded with, for diagnostics. */
51
+ txid?: string;
52
+ }
53
+
39
54
  /**
40
55
  * The updater needs the caller to broadcast the signed update via the beacon.
41
56
  *
@@ -75,16 +90,18 @@ export type UpdaterState =
75
90
  | { status: 'complete'; result: UpdaterResult };
76
91
 
77
92
  /**
78
- * Internal phases of the Updater state machine.
93
+ * Discriminated union of the updater's internal state. Each phase tag pins the
94
+ * exact set of values the state machine has computed so far, so consumers of
95
+ * `#state` narrow correctly under `switch (this.#state.phase)`. No nullable
96
+ * scratch slots, no `!`-asserts.
79
97
  * @internal
80
98
  */
81
- enum UpdaterPhase {
82
- Construct = 'Construct',
83
- Sign = 'Sign',
84
- Fund = 'Fund',
85
- Broadcast = 'Broadcast',
86
- Complete = 'Complete',
87
- }
99
+ type InternalState =
100
+ | { phase: 'Construct' }
101
+ | { phase: 'Sign'; unsignedUpdate: UnsignedBTCR2Update }
102
+ | { phase: 'Fund'; unsignedUpdate: UnsignedBTCR2Update; signedUpdate: SignedBTCR2Update }
103
+ | { phase: 'Broadcast'; unsignedUpdate: UnsignedBTCR2Update; signedUpdate: SignedBTCR2Update }
104
+ | { phase: 'Complete'; signedUpdate: SignedBTCR2Update };
88
105
 
89
106
  /**
90
107
  * Parameters for constructing an {@link Updater}. Built by
@@ -106,20 +123,21 @@ export interface UpdaterParams {
106
123
  *
107
124
  * ```typescript
108
125
  * const updater = DidBtcr2.update({ sourceDocument, patches, ... });
126
+ * const signer = new LocalSigner(secretKeyBytes); // or KeyManagerSigner / custom
109
127
  * let state = updater.advance();
110
128
  *
111
129
  * while(state.status === 'action-required') {
112
130
  * for(const need of state.needs) {
113
131
  * switch(need.kind) {
114
132
  * case 'NeedSigningKey':
115
- * updater.provide(need, secretKeyBytes);
133
+ * updater.provide(need, signer);
116
134
  * break;
117
135
  * case 'NeedFunding':
118
136
  * // Check UTXOs at need.beaconAddress, fund if needed
119
137
  * updater.provide(need);
120
138
  * break;
121
139
  * case 'NeedBroadcast':
122
- * await Updater.announce(need.beaconService, need.signedUpdate, secretKey, bitcoin);
140
+ * await Updater.announce(need.beaconService, need.signedUpdate, signer, bitcoin);
123
141
  * updater.provide(need);
124
142
  * break;
125
143
  * }
@@ -142,16 +160,13 @@ export interface UpdaterParams {
142
160
  * @class Updater
143
161
  */
144
162
  export class Updater {
145
- #phase: UpdaterPhase = UpdaterPhase.Construct;
163
+ #state: InternalState = { phase: 'Construct' };
146
164
  readonly #sourceDocument: Btcr2DidDocument;
147
165
  readonly #patches: PatchOperation[];
148
166
  readonly #sourceVersionId: number;
149
167
  readonly #verificationMethod: DidVerificationMethod;
150
168
  readonly #beaconService: BeaconService;
151
169
 
152
- #unsignedUpdate: UnsignedBTCR2Update | null = null;
153
- #signedUpdate: SignedBTCR2Update | null = null;
154
-
155
170
  /**
156
171
  * @internal — Use {@link DidBtcr2.update} to create instances.
157
172
  */
@@ -196,6 +211,16 @@ export class Updater {
196
211
 
197
212
  const targetDocument = JSONPatch.apply(sourceDocument, patches);
198
213
 
214
+ // Spec (operations/update.md): "An INVALID_DID_UPDATE error MUST be raised if
215
+ // didTargetDocument.id is not equal to didSourceDocument.id." `DidDocument.isValid`
216
+ // checks W3C conformance but not this equality, so it's enforced explicitly here.
217
+ if(targetDocument.id !== sourceDocument.id) {
218
+ throw new UpdateError(
219
+ `Patches must not change the DID document id (source "${sourceDocument.id}" → target "${targetDocument.id}").`,
220
+ INVALID_DID_UPDATE, { sourceId: sourceDocument.id, targetId: targetDocument.id }
221
+ );
222
+ }
223
+
199
224
  try {
200
225
  DidDocument.isValid(targetDocument);
201
226
  } catch (error) {
@@ -215,18 +240,31 @@ export class Updater {
215
240
  * @param {string} did The did-btcr2 identifier to derive the root capability from.
216
241
  * @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
217
242
  * @param {DidVerificationMethod} verificationMethod The verification method for signing.
218
- * @param {KeyBytes} secretKey The secret key bytes.
243
+ * @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
219
244
  * @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
220
245
  */
221
246
  static sign(
222
247
  did: string,
223
248
  unsignedUpdate: UnsignedBTCR2Update,
224
249
  verificationMethod: DidVerificationMethod,
225
- secretKey: KeyBytes,
250
+ signer: Signer,
226
251
  ): SignedBTCR2Update {
252
+ if(!did.startsWith('did:btcr2:')) {
253
+ throw new UpdateError(
254
+ `Expected a did:btcr2 identifier for the root capability; got "${did}".`,
255
+ INVALID_DID_UPDATE, { did }
256
+ );
257
+ }
227
258
  const controller = verificationMethod.controller;
228
- const id = verificationMethod.id.slice(verificationMethod.id.indexOf('#'));
229
- const multikey = SchnorrMultikey.fromSecretKey(id, controller, secretKey);
259
+ const hashIdx = verificationMethod.id.indexOf('#');
260
+ if(hashIdx < 0) {
261
+ throw new UpdateError(
262
+ `Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
263
+ INVALID_DID_UPDATE, { verificationMethodId: verificationMethod.id }
264
+ );
265
+ }
266
+ const id = verificationMethod.id.slice(hashIdx);
267
+ const multikey = SchnorrMultikey.fromSigner(id, controller, signer);
230
268
 
231
269
  const config: DataIntegrityConfig = {
232
270
  '@context' : [
@@ -253,21 +291,21 @@ export class Updater {
253
291
  *
254
292
  * @param {BeaconService} beaconService The beacon service to broadcast through.
255
293
  * @param {SignedBTCR2Update} update The signed update to announce.
256
- * @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
294
+ * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
257
295
  * @param {BitcoinConnection} bitcoin The Bitcoin network connection.
258
296
  * @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
259
297
  */
260
298
  static async announce(
261
299
  beaconService: BeaconService,
262
300
  update: SignedBTCR2Update,
263
- secretKey: KeyBytes,
301
+ signer: Signer,
264
302
  bitcoin: BitcoinConnection
265
303
  ): Promise<SignedBTCR2Update> {
266
304
  const beacon = BeaconFactory.establish(beaconService);
267
- return beacon.broadcastSignal(update, secretKey, bitcoin);
305
+ return beacon.broadcastSignal(update, signer, bitcoin);
268
306
  }
269
307
 
270
- // ─── Private instance wrappers ─────────────────────────────────────────────
308
+ // Private instance wrappers
271
309
  // Delegate to the public statics with bound instance fields for cleaner
272
310
  // advance/provide code.
273
311
 
@@ -275,12 +313,6 @@ export class Updater {
275
313
  return Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
276
314
  }
277
315
 
278
- #sign(secretKey: KeyBytes): SignedBTCR2Update {
279
- return Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate!, this.#verificationMethod, secretKey);
280
- }
281
-
282
- // ─── State machine ─────────────────────────────────────────────────────────
283
-
284
316
  /**
285
317
  * Advance the state machine. Returns either:
286
318
  * - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
@@ -288,25 +320,25 @@ export class Updater {
288
320
  */
289
321
  advance(): UpdaterState {
290
322
  while(true) {
291
- switch(this.#phase) {
323
+ switch(this.#state.phase) {
292
324
 
293
325
  // Phase: Construct
294
326
  // Build the unsigned update from source doc + patches. Pure, synchronous.
295
- case UpdaterPhase.Construct: {
296
- this.#unsignedUpdate = this.#construct();
297
- this.#phase = UpdaterPhase.Sign;
327
+ case 'Construct': {
328
+ const unsignedUpdate = this.#construct();
329
+ this.#state = { phase: 'Sign', unsignedUpdate };
298
330
  continue;
299
331
  }
300
332
 
301
333
  // Phase: Sign
302
334
  // Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
303
- case UpdaterPhase.Sign: {
335
+ case 'Sign': {
304
336
  return {
305
337
  status : 'action-required',
306
338
  needs : [{
307
339
  kind : 'NeedSigningKey',
308
340
  verificationMethodId : this.#verificationMethod.id,
309
- unsignedUpdate : this.#unsignedUpdate!,
341
+ unsignedUpdate : this.#state.unsignedUpdate,
310
342
  }],
311
343
  };
312
344
  }
@@ -314,7 +346,7 @@ export class Updater {
314
346
  // Phase: Fund
315
347
  // Emit NeedFunding with the beacon address. The caller checks UTXOs,
316
348
  // funds the address if needed, and provides to continue.
317
- case UpdaterPhase.Fund: {
349
+ case 'Fund': {
318
350
  const beaconAddress = this.#beaconService.serviceEndpoint.replace('bitcoin:', '');
319
351
  return {
320
352
  status : 'action-required',
@@ -329,22 +361,22 @@ export class Updater {
329
361
  // Phase: Broadcast
330
362
  // Emit NeedBroadcast with the signed update + beacon service. The caller performs
331
363
  // the actual on-chain announcement (or hands off to the aggregation protocol).
332
- case UpdaterPhase.Broadcast: {
364
+ case 'Broadcast': {
333
365
  return {
334
366
  status : 'action-required',
335
367
  needs : [{
336
368
  kind : 'NeedBroadcast',
337
369
  beaconService : this.#beaconService,
338
- signedUpdate : this.#signedUpdate!,
370
+ signedUpdate : this.#state.signedUpdate,
339
371
  }],
340
372
  };
341
373
  }
342
374
 
343
375
  // Phase: Complete
344
- case UpdaterPhase.Complete: {
376
+ case 'Complete': {
345
377
  return {
346
378
  status : 'complete',
347
- result : { signedUpdate: this.#signedUpdate! },
379
+ result : { signedUpdate: this.#state.signedUpdate },
348
380
  };
349
381
  }
350
382
  }
@@ -358,56 +390,70 @@ export class Updater {
358
390
  * @param need The DataNeed being fulfilled (from the `needs` array).
359
391
  * @param data The data payload corresponding to the need kind (omit for NeedFunding/NeedBroadcast).
360
392
  */
361
- provide(need: NeedSigningKey, data: KeyBytes): void;
362
- provide(need: NeedFunding): void;
393
+ provide(need: NeedSigningKey, data: Signer): void;
394
+ provide(need: NeedFunding, proof?: FundingProof): void;
363
395
  provide(need: NeedBroadcast): void;
364
- provide(need: UpdaterDataNeed, data?: KeyBytes): void {
396
+ provide(need: UpdaterDataNeed, data?: Signer | FundingProof): void {
365
397
  switch(need.kind) {
366
398
  case 'NeedSigningKey': {
367
- if(this.#phase !== UpdaterPhase.Sign) {
399
+ if(this.#state.phase !== 'Sign') {
368
400
  throw new UpdateError(
369
- `Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
370
- INVALID_DID_UPDATE, { phase: this.#phase }
401
+ `Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
402
+ INVALID_DID_UPDATE, { phase: this.#state.phase }
371
403
  );
372
404
  }
373
405
  if(!data) {
374
406
  throw new UpdateError(
375
- 'NeedSigningKey requires secret key bytes.',
376
- INVALID_DID_UPDATE
377
- );
378
- }
379
- if(!this.#unsignedUpdate) {
380
- throw new UpdateError(
381
- 'Internal error: unsigned update missing in Sign phase.',
407
+ 'NeedSigningKey requires a Signer.',
382
408
  INVALID_DID_UPDATE
383
409
  );
384
410
  }
385
- this.#signedUpdate = this.#sign(data);
386
- this.#phase = UpdaterPhase.Fund;
411
+ const unsignedUpdate = this.#state.unsignedUpdate;
412
+ const signedUpdate = Updater.sign(
413
+ this.#sourceDocument.id, unsignedUpdate, this.#verificationMethod, data as Signer,
414
+ );
415
+ this.#state = { phase: 'Fund', unsignedUpdate, signedUpdate };
387
416
  break;
388
417
  }
389
418
 
390
419
  case 'NeedFunding': {
391
- if(this.#phase !== UpdaterPhase.Fund) {
420
+ if(this.#state.phase !== 'Fund') {
392
421
  throw new UpdateError(
393
- `Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
394
- INVALID_DID_UPDATE, { phase: this.#phase }
422
+ `Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
423
+ INVALID_DID_UPDATE, { phase: this.#state.phase }
395
424
  );
396
425
  }
397
- // Caller has confirmed funding (or it was already funded). Continue.
398
- this.#phase = UpdaterPhase.Broadcast;
426
+ // If the caller supplies a FundingProof, assert it before transitioning.
427
+ // Optional payload preserves the sans-I/O contract: the caller still does
428
+ // the actual UTXO lookup; this is a contract-level handshake that catches
429
+ // a class of caller bugs (forgot to fund, race with mempool, etc.) at the
430
+ // state-machine boundary rather than at broadcast time.
431
+ if(data !== undefined) {
432
+ const proof = data as FundingProof;
433
+ if(typeof proof.utxoCount !== 'number' || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
434
+ throw new UpdateError(
435
+ `NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
436
+ INVALID_DID_UPDATE, { utxoCount: proof.utxoCount }
437
+ );
438
+ }
439
+ }
440
+ this.#state = {
441
+ phase : 'Broadcast',
442
+ unsignedUpdate : this.#state.unsignedUpdate,
443
+ signedUpdate : this.#state.signedUpdate,
444
+ };
399
445
  break;
400
446
  }
401
447
 
402
448
  case 'NeedBroadcast': {
403
- if(this.#phase !== UpdaterPhase.Broadcast) {
449
+ if(this.#state.phase !== 'Broadcast') {
404
450
  throw new UpdateError(
405
- `Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
406
- INVALID_DID_UPDATE, { phase: this.#phase }
451
+ `Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
452
+ INVALID_DID_UPDATE, { phase: this.#state.phase }
407
453
  );
408
454
  }
409
455
  // Caller has broadcast externally. Transition to Complete.
410
- this.#phase = UpdaterPhase.Complete;
456
+ this.#state = { phase: 'Complete', signedUpdate: this.#state.signedUpdate };
411
457
  break;
412
458
  }
413
459
  }
package/src/did-btcr2.ts CHANGED
@@ -17,8 +17,6 @@ import {
17
17
  DidError,
18
18
  DidErrorCode
19
19
  } from '@web5/dids';
20
- import * as ecc from '@bitcoinerlab/secp256k1';
21
- import { initEccLib } from 'bitcoinjs-lib';
22
20
  import type { BeaconService } from './core/beacon/interfaces.js';
23
21
  import { Identifier } from './core/identifier.js';
24
22
  import type { ResolutionOptions } from './core/interfaces.js';
@@ -36,9 +34,6 @@ export interface DidCreateOptions {
36
34
  network?: string;
37
35
  }
38
36
 
39
- /** Initialize secp256k1 ECC library */
40
- initEccLib(ecc);
41
-
42
37
  /**
43
38
  * Implements {@link https://dcdpr.github.io/did-btcr2 | did:btcr2 DID Method Specification}.
44
39
  * did:btcr2 is a censorship-resistant Decentralized Identifier (DID) method using
package/src/index.ts CHANGED
@@ -5,6 +5,8 @@ export * from './core/aggregation/cohort.js';
5
5
  export * from './core/aggregation/signing-session.js';
6
6
  export * from './core/aggregation/phases.js';
7
7
  export * from './core/aggregation/errors.js';
8
+ export * from './core/aggregation/beacon-strategy.js';
9
+ export * from './core/aggregation/logger.js';
8
10
  export * from './core/aggregation/messages/index.js';
9
11
  export * from './core/aggregation/transport/index.js';
10
12
  export * from './core/aggregation/runner/index.js';
@@ -15,7 +15,7 @@ import {
15
15
  import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
16
16
  import type { DidDocument as W3CDidDocument, DidVerificationMethod as W3CDidVerificationMethod } from '@web5/dids';
17
17
  import { isDidService } from '@web5/dids/utils';
18
- import { payments } from 'bitcoinjs-lib';
18
+ import { p2pkh } from '@scure/btc-signer';
19
19
  import type { BeaconService } from '../core/beacon/interfaces.js';
20
20
  import { Identifier } from '../core/identifier.js';
21
21
  import { Appendix } from './appendix.js';
@@ -491,7 +491,7 @@ export class GenesisDocument extends DidDocument {
491
491
  public static fromPublicKey(publicKey: KeyBytes, network: string): GenesisDocument {
492
492
  const pk = new CompressedSecp256k1PublicKey(publicKey);
493
493
  const id = ID_PLACEHOLDER_VALUE;
494
- const address = payments.p2pkh({ pubkey: pk.compressed, network: getNetwork(network) })?.address;
494
+ const address = p2pkh(pk.compressed, getNetwork(network)).address;
495
495
  const services = [{
496
496
  id : `${id}#service-0`,
497
497
  serviceEndpoint : `bitcoin:${address}`,