@did-btcr2/method 0.27.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.
- package/README.md +38 -9
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +20181 -31588
- package/dist/browser.mjs +20110 -31517
- package/dist/cjs/index.js +1355 -422
- package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
- package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
- package/dist/esm/core/aggregation/cohort.js +31 -8
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/logger.js +15 -0
- package/dist/esm/core/aggregation/logger.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js +12 -1
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +90 -0
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/messages/index.js +1 -0
- package/dist/esm/core/aggregation/messages/index.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +39 -46
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +34 -4
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +143 -15
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/core/aggregation/signing-session.js +44 -5
- package/dist/esm/core/aggregation/signing-session.js.map +1 -1
- package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
- package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
- package/dist/esm/core/aggregation/transport/nostr.js +245 -16
- package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +147 -61
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +14 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/core/updater.js +269 -0
- package/dist/esm/core/updater.js.map +1 -0
- package/dist/esm/did-btcr2.js +30 -46
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/did-document.js +2 -2
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
- package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
- package/dist/types/core/aggregation/cohort.d.ts +20 -3
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/logger.d.ts +22 -0
- package/dist/types/core/aggregation/logger.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/base.d.ts +13 -1
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/index.d.ts +1 -0
- package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +2 -0
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +32 -6
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +8 -2
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +33 -2
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/core/aggregation/signing-session.d.ts +5 -1
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/transport.d.ts +25 -0
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +85 -18
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts +2 -2
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts +178 -0
- package/dist/types/core/updater.d.ts.map +1 -0
- package/dist/types/did-btcr2.d.ts +23 -23
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -6
- package/src/core/aggregation/beacon-strategy.ts +123 -0
- package/src/core/aggregation/cohort.ts +34 -8
- package/src/core/aggregation/logger.ts +33 -0
- package/src/core/aggregation/messages/base.ts +20 -5
- package/src/core/aggregation/messages/bodies.ts +223 -0
- package/src/core/aggregation/messages/factories.ts +1 -0
- package/src/core/aggregation/messages/index.ts +1 -0
- package/src/core/aggregation/participant.ts +40 -46
- package/src/core/aggregation/runner/events.ts +27 -3
- package/src/core/aggregation/runner/participant-runner.ts +42 -4
- package/src/core/aggregation/runner/service-runner.ts +227 -19
- package/src/core/aggregation/service.ts +189 -20
- package/src/core/aggregation/signing-session.ts +65 -7
- package/src/core/aggregation/transport/didcomm.ts +17 -0
- package/src/core/aggregation/transport/nostr.ts +266 -23
- package/src/core/aggregation/transport/transport.ts +33 -0
- package/src/core/beacon/beacon.ts +217 -76
- package/src/core/beacon/utils.ts +16 -11
- package/src/core/updater.ts +415 -0
- package/src/did-btcr2.ts +36 -71
- package/src/index.ts +4 -1
- package/src/utils/did-document.ts +2 -2
- package/dist/esm/core/update.js +0 -112
- package/dist/esm/core/update.js.map +0 -1
- package/dist/types/core/update.d.ts +0 -52
- package/dist/types/core/update.d.ts.map +0 -1
- package/src/core/update.ts +0 -158
|
@@ -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 {
|
|
4
|
+
import { getPublicKey } from '@noble/secp256k1';
|
|
5
5
|
import { hexToBytes } from '@noble/hashes/utils';
|
|
6
|
-
import {
|
|
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
|
-
*
|
|
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
|
-
*
|
|
208
|
+
* Build + sign + broadcast a single-party beacon signal transaction (P2WPKH spend).
|
|
84
209
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
134
|
-
'
|
|
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
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/core/beacon/utils.ts
CHANGED
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
|
129
|
-
const
|
|
130
|
-
const
|
|
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 (!
|
|
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:${
|
|
145
|
+
serviceEndpoint : `bitcoin:${p2pkhAddr}`
|
|
141
146
|
},
|
|
142
147
|
{
|
|
143
148
|
id : `${id}#initialP2WPKH`,
|
|
144
149
|
type : beaconType,
|
|
145
|
-
serviceEndpoint : `bitcoin:${
|
|
150
|
+
serviceEndpoint : `bitcoin:${p2wpkhAddr}`
|
|
146
151
|
},
|
|
147
152
|
{
|
|
148
153
|
id : `${id}#initialP2TR`,
|
|
149
154
|
type : beaconType,
|
|
150
|
-
serviceEndpoint : `bitcoin:${
|
|
155
|
+
serviceEndpoint : `bitcoin:${p2trAddr}`
|
|
151
156
|
},
|
|
152
157
|
];
|
|
153
158
|
} catch (error: any) {
|