@did-btcr2/method 0.28.0 → 0.29.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 (102) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +20092 -31631
  3. package/dist/browser.mjs +20019 -31558
  4. package/dist/cjs/index.js +1164 -364
  5. package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
  6. package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
  7. package/dist/esm/core/aggregation/cohort.js +31 -8
  8. package/dist/esm/core/aggregation/cohort.js.map +1 -1
  9. package/dist/esm/core/aggregation/logger.js +15 -0
  10. package/dist/esm/core/aggregation/logger.js.map +1 -0
  11. package/dist/esm/core/aggregation/messages/base.js +12 -1
  12. package/dist/esm/core/aggregation/messages/base.js.map +1 -1
  13. package/dist/esm/core/aggregation/messages/bodies.js +90 -0
  14. package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
  15. package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
  16. package/dist/esm/core/aggregation/messages/index.js +1 -0
  17. package/dist/esm/core/aggregation/messages/index.js.map +1 -1
  18. package/dist/esm/core/aggregation/participant.js +39 -46
  19. package/dist/esm/core/aggregation/participant.js.map +1 -1
  20. package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
  21. package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
  22. package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
  23. package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
  24. package/dist/esm/core/aggregation/service.js +143 -15
  25. package/dist/esm/core/aggregation/service.js.map +1 -1
  26. package/dist/esm/core/aggregation/signing-session.js +44 -5
  27. package/dist/esm/core/aggregation/signing-session.js.map +1 -1
  28. package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
  29. package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
  30. package/dist/esm/core/aggregation/transport/nostr.js +245 -16
  31. package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
  32. package/dist/esm/core/beacon/beacon.js +147 -61
  33. package/dist/esm/core/beacon/beacon.js.map +1 -1
  34. package/dist/esm/core/beacon/utils.js +14 -9
  35. package/dist/esm/core/beacon/utils.js.map +1 -1
  36. package/dist/esm/did-btcr2.js +0 -4
  37. package/dist/esm/did-btcr2.js.map +1 -1
  38. package/dist/esm/index.js +2 -0
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/utils/did-document.js +2 -2
  41. package/dist/esm/utils/did-document.js.map +1 -1
  42. package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
  43. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
  44. package/dist/types/core/aggregation/cohort.d.ts +20 -3
  45. package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
  46. package/dist/types/core/aggregation/logger.d.ts +22 -0
  47. package/dist/types/core/aggregation/logger.d.ts.map +1 -0
  48. package/dist/types/core/aggregation/messages/base.d.ts +13 -1
  49. package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
  50. package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
  51. package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
  52. package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
  53. package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
  54. package/dist/types/core/aggregation/messages/index.d.ts +1 -0
  55. package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
  56. package/dist/types/core/aggregation/participant.d.ts +2 -0
  57. package/dist/types/core/aggregation/participant.d.ts.map +1 -1
  58. package/dist/types/core/aggregation/runner/events.d.ts +32 -6
  59. package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
  60. package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
  61. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
  62. package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
  63. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
  64. package/dist/types/core/aggregation/service.d.ts +33 -2
  65. package/dist/types/core/aggregation/service.d.ts.map +1 -1
  66. package/dist/types/core/aggregation/signing-session.d.ts +5 -1
  67. package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
  68. package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
  69. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
  70. package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
  71. package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
  72. package/dist/types/core/aggregation/transport/transport.d.ts +25 -0
  73. package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
  74. package/dist/types/core/beacon/beacon.d.ts +85 -18
  75. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  76. package/dist/types/core/beacon/utils.d.ts +2 -2
  77. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  78. package/dist/types/did-btcr2.d.ts.map +1 -1
  79. package/dist/types/index.d.ts +2 -0
  80. package/dist/types/index.d.ts.map +1 -1
  81. package/package.json +5 -7
  82. package/src/core/aggregation/beacon-strategy.ts +123 -0
  83. package/src/core/aggregation/cohort.ts +34 -8
  84. package/src/core/aggregation/logger.ts +33 -0
  85. package/src/core/aggregation/messages/base.ts +20 -5
  86. package/src/core/aggregation/messages/bodies.ts +223 -0
  87. package/src/core/aggregation/messages/factories.ts +1 -0
  88. package/src/core/aggregation/messages/index.ts +1 -0
  89. package/src/core/aggregation/participant.ts +40 -46
  90. package/src/core/aggregation/runner/events.ts +27 -3
  91. package/src/core/aggregation/runner/participant-runner.ts +41 -7
  92. package/src/core/aggregation/runner/service-runner.ts +227 -19
  93. package/src/core/aggregation/service.ts +189 -20
  94. package/src/core/aggregation/signing-session.ts +65 -7
  95. package/src/core/aggregation/transport/didcomm.ts +17 -0
  96. package/src/core/aggregation/transport/nostr.ts +266 -23
  97. package/src/core/aggregation/transport/transport.ts +33 -0
  98. package/src/core/beacon/beacon.ts +217 -76
  99. package/src/core/beacon/utils.ts +16 -11
  100. package/src/did-btcr2.ts +0 -5
  101. package/src/index.ts +2 -0
  102. package/src/utils/did-document.ts +2 -2
@@ -1,9 +1,9 @@
1
- import type { AddressUtxo, BitcoinConnection } from '@did-btcr2/bitcoin';
1
+ import type { AddressUtxo, BitcoinConnection, BTCNetwork } from '@did-btcr2/bitcoin';
2
2
  import type { KeyBytes } from '@did-btcr2/common';
3
3
  import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
4
- import { SchnorrKeyPair } from '@did-btcr2/keypair';
4
+ import { getPublicKey } from '@noble/secp256k1';
5
5
  import { hexToBytes } from '@noble/hashes/utils';
6
- import { opcodes, Psbt, script } from 'bitcoinjs-lib';
6
+ import { OP, p2tr, p2wpkh, Script, Transaction } from '@scure/btc-signer';
7
7
  import type { BeaconProcessResult } from '../resolver.js';
8
8
  import type { SidecarData } from '../types.js';
9
9
  import { BeaconError } from './error.js';
@@ -15,13 +15,138 @@ import type { BeaconService, BeaconSignal } from './interfaces.js';
15
15
  const DEFAULT_FEE_ESTIMATOR: FeeEstimator = new StaticFeeEstimator(5);
16
16
 
17
17
  /**
18
- * Options accepted by {@link Beacon.buildSignAndBroadcast}.
18
+ * Conservative vsize estimate for a 1-input P2TR key-path → 1 P2TR change + 1 OP_RETURN(32) tx.
19
+ * Taproot key-path witness is a fixed 64-byte Schnorr signature, so vsize is predictable
20
+ * without having to sign. Used for fee estimation in the aggregation path where MuSig2
21
+ * signatures are produced externally.
22
+ */
23
+ const P2TR_BEACON_TX_VSIZE = 140;
24
+
25
+ /**
26
+ * Options accepted by {@link Beacon.buildSignAndBroadcast} and related helpers.
19
27
  */
20
28
  export interface BroadcastOptions {
21
29
  /** Fee estimator for computing the transaction fee. Defaults to {@link DEFAULT_FEE_ESTIMATOR}. */
22
30
  feeEstimator?: FeeEstimator;
23
31
  }
24
32
 
33
+ /**
34
+ * Unsigned beacon transaction + the prev-output metadata needed for downstream
35
+ * signing (single-party ECDSA or multi-party MuSig2 Taproot).
36
+ */
37
+ export interface BeaconTxPlan {
38
+ /** The unsigned scure @scure/btc-signer Transaction. */
39
+ tx: Transaction;
40
+ /** Scripts of the consumed previous outputs (needed for Taproot sighash). */
41
+ prevOutScripts: Uint8Array[];
42
+ /** Amounts (sats) of the consumed previous outputs. */
43
+ prevOutValues: bigint[];
44
+ /** Address change was sent back to (same as the beacon address). */
45
+ beaconAddress: string;
46
+ /** The UTXO this tx consumes. */
47
+ utxo: AddressUtxo;
48
+ /** The fee (sats) already deducted from the change output. */
49
+ feeSats: bigint;
50
+ }
51
+
52
+ /**
53
+ * Build an OP_RETURN script carrying a 32-byte beacon signal.
54
+ * Exported as a utility so callers building txs outside Beacon (e.g., the aggregation
55
+ * `onProvideTxData` callback) can produce identical output.
56
+ */
57
+ export function opReturnScript(signalBytes: Uint8Array): Uint8Array {
58
+ return Script.encode([OP.RETURN, signalBytes]);
59
+ }
60
+
61
+ /**
62
+ * Fetch the most recent confirmed UTXO at `bitcoinAddress` + the raw bytes of its
63
+ * parent transaction (needed by PSBT inputs). Throws if unfunded.
64
+ */
65
+ async function fetchSpendableUtxo(
66
+ bitcoinAddress: string,
67
+ bitcoin: BitcoinConnection,
68
+ ): Promise<{ utxo: AddressUtxo; prevTxBytes: Uint8Array }> {
69
+ const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
70
+ if(!utxos.length) {
71
+ throw new BeaconError(
72
+ 'No UTXOs found, please fund address!',
73
+ 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
74
+ );
75
+ }
76
+ const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
77
+ if(!utxo) {
78
+ throw new BeaconError(
79
+ 'Beacon bitcoin address unfunded or utxos unconfirmed.',
80
+ 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
81
+ );
82
+ }
83
+ const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
84
+ return { utxo, prevTxBytes: hexToBytes(prevTxHex) };
85
+ }
86
+
87
+ /**
88
+ * Build an aggregation beacon transaction (P2TR key-path spend) ready for MuSig2 signing.
89
+ * Returns the unsigned Transaction + prev-output metadata that an aggregation service's
90
+ * signing session consumes (via {@link SigningTxData}).
91
+ *
92
+ * This is the reusable counterpart to {@link Beacon.buildSignAndBroadcast}'s internal
93
+ * construction step — the aggregation path must produce an unsigned tx because the
94
+ * signature comes from a MuSig2 round, not a local secret key.
95
+ *
96
+ * @param opts Parameters including the cohort's aggregate internal pubkey.
97
+ * @returns A {@link BeaconTxPlan} with the unsigned tx and sighash inputs.
98
+ */
99
+ export async function buildAggregationBeaconTx(opts: {
100
+ /** The beacon (cohort) address where UTXOs live and change returns to. */
101
+ beaconAddress: string;
102
+ /** The cohort's MuSig2-aggregated x-only internal pubkey (32 bytes). */
103
+ internalPubkey: Uint8Array;
104
+ /** 32-byte beacon signal embedded in the OP_RETURN output. */
105
+ signalBytes: Uint8Array;
106
+ /** Bitcoin REST connection for UTXO / prev-tx lookup. */
107
+ bitcoin: BitcoinConnection;
108
+ /** Network params used to derive the P2TR witnessUtxo script. */
109
+ network: BTCNetwork;
110
+ /** Optional fee estimator (defaults to 5 sat/vB). */
111
+ feeEstimator?: FeeEstimator;
112
+ }): Promise<BeaconTxPlan> {
113
+ const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
114
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
115
+
116
+ const tapOut = p2tr(opts.internalPubkey, undefined, opts.network);
117
+ const witnessScript = tapOut.script;
118
+
119
+ // Fee cannot be probe-measured (no secret key for MuSig2 round). Use fixed P2TR vsize.
120
+ const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
121
+ if(BigInt(utxo.value) <= feeSats) {
122
+ throw new BeaconError(
123
+ `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
124
+ 'INSUFFICIENT_FUNDS',
125
+ { bitcoinAddress: opts.beaconAddress, utxoValue: utxo.value, fee: feeSats.toString() }
126
+ );
127
+ }
128
+
129
+ const tx = new Transaction();
130
+ tx.addInput({
131
+ txid : utxo.txid,
132
+ index : utxo.vout,
133
+ nonWitnessUtxo : prevTxBytes,
134
+ witnessUtxo : { amount: BigInt(utxo.value), script: witnessScript },
135
+ tapInternalKey : opts.internalPubkey,
136
+ });
137
+ tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
138
+ tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
139
+
140
+ return {
141
+ tx,
142
+ prevOutScripts : [witnessScript],
143
+ prevOutValues : [BigInt(utxo.value)],
144
+ beaconAddress : opts.beaconAddress,
145
+ utxo,
146
+ feeSats,
147
+ };
148
+ }
149
+
25
150
  /**
26
151
  * Abstract base class for all BTCR2 Beacon types.
27
152
  * A Beacon is a service listed in a BTCR2 DID document that informs resolvers
@@ -80,29 +205,20 @@ export abstract class Beacon {
80
205
  ): Promise<SignedBTCR2Update>;
81
206
 
82
207
  /**
83
- * Shared PSBT construction + signing + broadcast helper used by all beacon types.
208
+ * Build + sign + broadcast a single-party beacon signal transaction (P2WPKH spend).
84
209
  *
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.
210
+ * Composed from the three extracted phases ({@link buildSinglePartyTx},
211
+ * {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
212
+ * in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead —
213
+ * the multi-party path can't share the signing phase, but the tx-construction
214
+ * plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
99
215
  *
100
216
  * @param signalBytes 32-byte payload to embed in OP_RETURN.
101
217
  * @param secretKey Secret key used to sign the spending input.
102
218
  * @param bitcoin Bitcoin network connection.
103
219
  * @param options Broadcast options (fee estimator, etc.).
104
220
  * @returns The txid of the broadcast transaction.
105
- * @throws {BeaconError} if the address is unfunded or no UTXO is available.
221
+ * @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
106
222
  */
107
223
  protected async buildSignAndBroadcast(
108
224
  signalBytes: Uint8Array,
@@ -111,72 +227,97 @@ export abstract class Beacon {
111
227
  options?: BroadcastOptions
112
228
  ): Promise<string> {
113
229
  const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
230
+ const beaconAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
231
+ const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
232
+ const plan = await this.buildSinglePartyTx({
233
+ signalBytes, beaconAddress, utxo, prevTxBytes, secretKey, bitcoin, feeEstimator,
234
+ });
235
+ const signedHex = this.signSinglePartyTx(plan.tx, secretKey);
236
+ return this.broadcastRawTx(bitcoin, signedHex);
237
+ }
114
238
 
115
- // Strip the 'bitcoin:' prefix from the service endpoint.
116
- const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
239
+ /**
240
+ * Build an unsigned P2WPKH single-party beacon tx + probe-sign to determine vsize,
241
+ * then rebuild with the real fee. Returns the tx and prev-output metadata.
242
+ *
243
+ * The secret key is required here (not just in `signSinglePartyTx`) because the
244
+ * two-pass fee estimation requires an actual signature to measure vsize accurately.
245
+ */
246
+ protected async buildSinglePartyTx(opts: {
247
+ signalBytes: Uint8Array;
248
+ beaconAddress: string;
249
+ utxo: AddressUtxo;
250
+ prevTxBytes: Uint8Array;
251
+ secretKey: KeyBytes;
252
+ bitcoin: BitcoinConnection;
253
+ feeEstimator: FeeEstimator;
254
+ }): Promise<BeaconTxPlan> {
255
+ const pubkey = this.#derivePubkey(opts.secretKey);
256
+ const witnessOut = p2wpkh(pubkey, opts.bitcoin.data);
257
+ const witnessScript = witnessOut.script;
117
258
 
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 }
259
+ const build = (feeSats: bigint): Transaction => {
260
+ const tx = new Transaction();
261
+ tx.addInput({
262
+ txid : opts.utxo.txid,
263
+ index : opts.utxo.vout,
264
+ nonWitnessUtxo : opts.prevTxBytes,
265
+ witnessUtxo : { amount: BigInt(opts.utxo.value), script: witnessScript },
266
+ });
267
+ tx.addOutputAddress(
268
+ opts.beaconAddress,
269
+ BigInt(opts.utxo.value) - feeSats,
270
+ opts.bitcoin.data,
124
271
  );
125
- }
272
+ tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
273
+ return tx;
274
+ };
275
+
276
+ // First pass: sign with zero fee to measure vsize.
277
+ const probe = build(0n);
278
+ probe.signIdx(opts.secretKey, 0);
279
+ probe.finalize();
280
+ const vsize = probe.vsize;
126
281
 
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) {
282
+ const feeSats = await opts.feeEstimator.estimateFee(vsize);
283
+ if(BigInt(opts.utxo.value) <= feeSats) {
132
284
  throw new BeaconError(
133
- 'Beacon bitcoin address unfunded or utxos unconfirmed.',
134
- 'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
285
+ `UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
286
+ 'INSUFFICIENT_FUNDS',
287
+ { bitcoinAddress: opts.beaconAddress, utxoValue: opts.utxo.value, fee: feeSats.toString() }
135
288
  );
136
289
  }
137
290
 
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' }),
291
+ // Second pass: real fee.
292
+ const tx = build(feeSats);
293
+ return {
294
+ tx,
295
+ prevOutScripts : [witnessScript],
296
+ prevOutValues : [BigInt(opts.utxo.value)],
297
+ beaconAddress : opts.beaconAddress,
298
+ utxo : opts.utxo,
299
+ feeSats,
146
300
  };
301
+ }
147
302
 
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
- }
303
+ /**
304
+ * Sign + finalize the unsigned single-party tx and return its raw hex.
305
+ */
306
+ protected signSinglePartyTx(tx: Transaction, secretKey: KeyBytes): string {
307
+ tx.signIdx(secretKey, 0);
308
+ tx.finalize();
309
+ return tx.hex;
310
+ }
173
311
 
174
- const signedTxHex = build(fee)
175
- .signInput(0, signer)
176
- .finalizeAllInputs()
177
- .extractTransaction()
178
- .toHex();
312
+ /**
313
+ * Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
314
+ */
315
+ protected async broadcastRawTx(bitcoin: BitcoinConnection, rawHex: string): Promise<string> {
316
+ return bitcoin.rest.transaction.send(rawHex);
317
+ }
179
318
 
180
- return bitcoin.rest.transaction.send(signedTxHex);
319
+ /** Derive the compressed secp256k1 public key from a raw secret key. */
320
+ #derivePubkey(secretKey: KeyBytes): Uint8Array {
321
+ return getPublicKey(secretKey, true);
181
322
  }
182
- }
323
+ }
@@ -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) {
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}`,