@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.
- package/README.md +13 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +34125 -44647
- package/dist/browser.mjs +26409 -36931
- package/dist/cjs/index.js +2869 -679
- 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 +33 -7
- 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/factory.js +15 -6
- package/dist/esm/core/aggregation/transport/factory.js.map +1 -1
- package/dist/esm/core/aggregation/transport/http/client.js +350 -0
- package/dist/esm/core/aggregation/transport/http/client.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js +126 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/errors.js +11 -0
- package/dist/esm/core/aggregation/transport/http/errors.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/index.js +12 -0
- package/dist/esm/core/aggregation/transport/http/index.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js +38 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js +28 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js +45 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js +100 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/server.js +481 -0
- package/dist/esm/core/aggregation/transport/http/server.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js +110 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js +25 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/index.js +1 -0
- package/dist/esm/core/aggregation/transport/index.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 +295 -63
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +3 -3
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/singleton-beacon.js +3 -3
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +3 -3
- package/dist/esm/core/beacon/smt-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 +63 -55
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +0 -4
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +2 -0
- 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 +7 -5
- 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/factory.d.ts +22 -7
- package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/http/client.d.ts +48 -0
- package/dist/types/core/aggregation/transport/http/client.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts +64 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts +9 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts +53 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts +50 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts +110 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts +1 -0
- package/dist/types/core/aggregation/transport/index.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 +26 -1
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +149 -22
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +3 -3
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +3 -3
- package/dist/types/core/beacon/smt-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 +27 -12
- package/dist/types/core/updater.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -7
- 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 +41 -7
- 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/factory.ts +48 -12
- package/src/core/aggregation/transport/http/client.ts +409 -0
- package/src/core/aggregation/transport/http/envelope.ts +204 -0
- package/src/core/aggregation/transport/http/errors.ts +11 -0
- package/src/core/aggregation/transport/http/inbox-buffer.ts +53 -0
- package/src/core/aggregation/transport/http/index.ts +11 -0
- package/src/core/aggregation/transport/http/nonce-cache.ts +43 -0
- package/src/core/aggregation/transport/http/protocol.ts +57 -0
- package/src/core/aggregation/transport/http/rate-limiter.ts +75 -0
- package/src/core/aggregation/transport/http/request-auth.ts +164 -0
- package/src/core/aggregation/transport/http/server.ts +615 -0
- package/src/core/aggregation/transport/http/sse-stream.ts +121 -0
- package/src/core/aggregation/transport/http/sse-writer.ts +23 -0
- package/src/core/aggregation/transport/index.ts +1 -0
- package/src/core/aggregation/transport/nostr.ts +266 -23
- package/src/core/aggregation/transport/transport.ts +34 -1
- package/src/core/beacon/beacon.ts +411 -79
- package/src/core/beacon/cas-beacon.ts +4 -4
- package/src/core/beacon/singleton-beacon.ts +4 -4
- package/src/core/beacon/smt-beacon.ts +4 -4
- package/src/core/beacon/utils.ts +16 -11
- package/src/core/updater.ts +113 -67
- package/src/did-btcr2.ts +0 -5
- package/src/index.ts +2 -0
- 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 {
|
|
5
|
-
import { hexToBytes } from '@noble/hashes/utils';
|
|
6
|
-
import {
|
|
4
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
|
+
import { concatBytes, hexToBytes } from '@noble/hashes/utils.js';
|
|
6
|
+
import { Address, OutScript, p2pkh, p2tr, p2wpkh, Script, SigHash, 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,301 @@ import type { BeaconService, BeaconSignal } from './interfaces.js';
|
|
|
15
15
|
const DEFAULT_FEE_ESTIMATOR: FeeEstimator = new StaticFeeEstimator(5);
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Singleton beacon script kinds. Per the did:btcr2 spec, deterministic DID documents
|
|
19
|
+
* include three beacon services: P2PKH, P2WPKH, and P2TR (taproot key-path) — all
|
|
20
|
+
* derived from the genesis secp256k1 public key. The singleton broadcast path must
|
|
21
|
+
* support signing for all three.
|
|
22
|
+
*/
|
|
23
|
+
export type SingletonScriptKind = 'p2pkh' | 'p2wpkh' | 'p2tr';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Conservative vsize estimate for a 1-input P2TR key-path → 1 P2TR change + 1 OP_RETURN(32) tx.
|
|
27
|
+
* Stripped 137 + witness ≈ 68 (marker + flag + stack-count + sig-len + 64 BIP-340 sig).
|
|
28
|
+
* Weight = 137*4 + 68 = 616, vsize ≈ 154, rounded to 160 for headroom.
|
|
29
|
+
*/
|
|
30
|
+
export const P2TR_BEACON_TX_VSIZE = 160;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Conservative vsize estimate for a 1-input P2WPKH → 1 P2WPKH change + 1 OP_RETURN(32) tx.
|
|
34
|
+
* Stripped 125 + witness ≈ 110 (worst-case DER ECDSA sig 72 + sighash byte + 33 pubkey + framing).
|
|
35
|
+
* vsize = ceil((125*4 + 110) / 4) ≈ 153, rounded to 155.
|
|
36
|
+
*/
|
|
37
|
+
export const P2WPKH_BEACON_TX_VSIZE = 155;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Conservative vsize estimate for a 1-input P2PKH → 1 P2PKH change + 1 OP_RETURN(32) tx.
|
|
41
|
+
* Legacy (non-segwit): scriptSig carries the full sig+pubkey (~108 bytes), no witness
|
|
42
|
+
* discount. Stripped ≈ 4 nVer + 1 vin-count + (32+4+1+108+4) input + 1 vout-count +
|
|
43
|
+
* 34 P2PKH-change + 43 OP_RETURN + 4 nLockTime ≈ 236 bytes. vsize = 236, rounded to 240.
|
|
44
|
+
*/
|
|
45
|
+
export const P2PKH_BEACON_TX_VSIZE = 240;
|
|
46
|
+
|
|
47
|
+
/** Per-kind vsize lookup for singleton beacon fee estimation. */
|
|
48
|
+
export const SINGLETON_BEACON_TX_VSIZE: Readonly<Record<SingletonScriptKind, number>> = {
|
|
49
|
+
p2pkh : P2PKH_BEACON_TX_VSIZE,
|
|
50
|
+
p2wpkh : P2WPKH_BEACON_TX_VSIZE,
|
|
51
|
+
p2tr : P2TR_BEACON_TX_VSIZE,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect the singleton script kind of a Bitcoin address (P2PKH / P2WPKH / P2TR).
|
|
56
|
+
* The deterministic-DID document emits all three kinds; the broadcast path needs
|
|
57
|
+
* to know which is in use to construct the input and dispatch the signing primitive.
|
|
58
|
+
*/
|
|
59
|
+
export function detectSingletonScriptKind(
|
|
60
|
+
bitcoinAddress: string,
|
|
61
|
+
network: BTCNetwork,
|
|
62
|
+
): SingletonScriptKind {
|
|
63
|
+
const decoded = Address(network).decode(bitcoinAddress);
|
|
64
|
+
if(decoded.type === 'pkh') return 'p2pkh';
|
|
65
|
+
if(decoded.type === 'wpkh') return 'p2wpkh';
|
|
66
|
+
if(decoded.type === 'tr') return 'p2tr';
|
|
67
|
+
throw new BeaconError(
|
|
68
|
+
`Unsupported singleton beacon address type "${decoded.type}". `
|
|
69
|
+
+ 'Expected P2PKH, P2WPKH, or P2TR (taproot key-path).',
|
|
70
|
+
'UNSUPPORTED_BEACON_ADDRESS_TYPE',
|
|
71
|
+
{ address: bitcoinAddress, kind: decoded.type }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Derive the address that `pubkey` produces under the given script kind. Used to
|
|
77
|
+
* fail-fast when a caller wires a signer to a beacon address that the signer's
|
|
78
|
+
* pubkey cannot actually spend.
|
|
79
|
+
*/
|
|
80
|
+
export function deriveSingletonAddress(
|
|
81
|
+
kind: SingletonScriptKind,
|
|
82
|
+
pubkey: KeyBytes,
|
|
83
|
+
network: BTCNetwork,
|
|
84
|
+
): string {
|
|
85
|
+
if(kind === 'p2pkh') return p2pkh(pubkey, network).address!;
|
|
86
|
+
if(kind === 'p2wpkh') return p2wpkh(pubkey, network).address!;
|
|
87
|
+
// P2TR key-path: x-only internal key (drop the SEC prefix byte).
|
|
88
|
+
return p2tr(pubkey.slice(1, 33), undefined, network).address!;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Options accepted by {@link Beacon.buildSignAndBroadcast} and related helpers.
|
|
19
93
|
*/
|
|
20
94
|
export interface BroadcastOptions {
|
|
21
95
|
/** Fee estimator for computing the transaction fee. Defaults to {@link DEFAULT_FEE_ESTIMATOR}. */
|
|
22
96
|
feeEstimator?: FeeEstimator;
|
|
23
97
|
}
|
|
24
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Unsigned beacon transaction + the prev-output metadata needed for downstream
|
|
101
|
+
* signing (single-party ECDSA or multi-party MuSig2 Taproot).
|
|
102
|
+
*/
|
|
103
|
+
export interface BeaconTxPlan {
|
|
104
|
+
/** The unsigned scure @scure/btc-signer Transaction. */
|
|
105
|
+
tx: Transaction;
|
|
106
|
+
/** Scripts of the consumed previous outputs (needed for Taproot sighash). */
|
|
107
|
+
prevOutScripts: Uint8Array[];
|
|
108
|
+
/** Amounts (sats) of the consumed previous outputs. */
|
|
109
|
+
prevOutValues: bigint[];
|
|
110
|
+
/** Address change was sent back to (same as the beacon address). */
|
|
111
|
+
beaconAddress: string;
|
|
112
|
+
/** The UTXO this tx consumes. */
|
|
113
|
+
utxo: AddressUtxo;
|
|
114
|
+
/** The fee (sats) already deducted from the change output. */
|
|
115
|
+
feeSats: bigint;
|
|
116
|
+
/**
|
|
117
|
+
* Singleton beacon script kind, when applicable. Drives the signing dispatch
|
|
118
|
+
* in {@link Beacon.signSinglePartyTx}. Aggregation plans set this to `'p2tr'`.
|
|
119
|
+
*/
|
|
120
|
+
scriptKind: SingletonScriptKind;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build an OP_RETURN script carrying a 32-byte beacon signal.
|
|
125
|
+
* Exported as a utility so callers building txs outside Beacon (e.g., the aggregation
|
|
126
|
+
* `onProvideTxData` callback) can produce identical output.
|
|
127
|
+
*
|
|
128
|
+
* Uses the opcode *string* `'RETURN'` rather than the numeric `OP.RETURN`
|
|
129
|
+
* constant because scure's `Script.encode` interprets a number as a byte to
|
|
130
|
+
* push, not as the opcode. The string form emits the bare opcode (0x6a)
|
|
131
|
+
* followed by an `OP_PUSHBYTES_32` push, producing the standard NULL_DATA
|
|
132
|
+
* shape Bitcoin Core's `IsStandard` accepts. The numeric form silently
|
|
133
|
+
* produces `OP_PUSHBYTES_1 0x6a OP_PUSHBYTES_32 <32 bytes>`, which is
|
|
134
|
+
* non-standard and rejected at broadcast with `RPC error -26: scriptpubkey`.
|
|
135
|
+
*/
|
|
136
|
+
export function opReturnScript(signalBytes: Uint8Array): Uint8Array {
|
|
137
|
+
return Script.encode(['RETURN', signalBytes]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fetch the most recent confirmed UTXO at `bitcoinAddress` + the raw bytes of its
|
|
142
|
+
* parent transaction (needed by PSBT inputs). Throws if unfunded.
|
|
143
|
+
*/
|
|
144
|
+
async function fetchSpendableUtxo(
|
|
145
|
+
bitcoinAddress: string,
|
|
146
|
+
bitcoin: BitcoinConnection,
|
|
147
|
+
): Promise<{ utxo: AddressUtxo; prevTxBytes: Uint8Array }> {
|
|
148
|
+
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
149
|
+
if(!utxos.length) {
|
|
150
|
+
throw new BeaconError(
|
|
151
|
+
'No UTXOs found, please fund address!',
|
|
152
|
+
'UNFUNDED_BEACON_ADDRESS', { address: bitcoinAddress }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
|
|
156
|
+
if(!utxo) {
|
|
157
|
+
throw new BeaconError(
|
|
158
|
+
'Beacon bitcoin address unfunded or utxos unconfirmed.',
|
|
159
|
+
'UNFUNDED_BEACON_ADDRESS', { address: bitcoinAddress }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
163
|
+
return { utxo, prevTxBytes: hexToBytes(prevTxHex) };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build an aggregation beacon transaction (P2TR key-path spend) ready for MuSig2 signing.
|
|
168
|
+
* Returns the unsigned Transaction + prev-output metadata that an aggregation service's
|
|
169
|
+
* signing session consumes (via {@link SigningTxData}).
|
|
170
|
+
*
|
|
171
|
+
* This is the reusable counterpart to {@link Beacon.buildSignAndBroadcast}'s internal
|
|
172
|
+
* construction step — the aggregation path must produce an unsigned tx because the
|
|
173
|
+
* signature comes from a MuSig2 round, not a local secret key.
|
|
174
|
+
*
|
|
175
|
+
* @param opts Parameters including the cohort's aggregate internal pubkey.
|
|
176
|
+
* @returns A {@link BeaconTxPlan} with the unsigned tx and sighash inputs.
|
|
177
|
+
*/
|
|
178
|
+
export async function buildAggregationBeaconTx(opts: {
|
|
179
|
+
/** The beacon (cohort) address where UTXOs live and change returns to. */
|
|
180
|
+
beaconAddress: string;
|
|
181
|
+
/** The cohort's MuSig2-aggregated x-only internal pubkey (32 bytes). */
|
|
182
|
+
internalPubkey: Uint8Array;
|
|
183
|
+
/** 32-byte beacon signal embedded in the OP_RETURN output. */
|
|
184
|
+
signalBytes: Uint8Array;
|
|
185
|
+
/** Bitcoin REST connection for UTXO / prev-tx lookup. */
|
|
186
|
+
bitcoin: BitcoinConnection;
|
|
187
|
+
/** Network params used to derive the P2TR witnessUtxo script. */
|
|
188
|
+
network: BTCNetwork;
|
|
189
|
+
/** Optional fee estimator (defaults to 5 sat/vB). */
|
|
190
|
+
feeEstimator?: FeeEstimator;
|
|
191
|
+
}): Promise<BeaconTxPlan> {
|
|
192
|
+
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
193
|
+
const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
|
|
194
|
+
|
|
195
|
+
const tapOut = p2tr(opts.internalPubkey, undefined, opts.network);
|
|
196
|
+
const witnessScript = tapOut.script;
|
|
197
|
+
|
|
198
|
+
// Fee cannot be probe-measured (no secret key for MuSig2 round). Use fixed P2TR vsize.
|
|
199
|
+
const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
|
|
200
|
+
if(BigInt(utxo.value) <= feeSats) {
|
|
201
|
+
throw new BeaconError(
|
|
202
|
+
`UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
203
|
+
'INSUFFICIENT_FUNDS',
|
|
204
|
+
{ address: opts.beaconAddress, valueSats: utxo.value, feeSats }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// allowUnknownOutputs: scure does not classify OP_RETURN as a "known" output
|
|
209
|
+
// type because it is unspendable by design. The opt-in flag tells scure we
|
|
210
|
+
// know the output is intentional (the beacon signal embedded in OP_RETURN).
|
|
211
|
+
const tx = new Transaction({ allowUnknownOutputs: true });
|
|
212
|
+
tx.addInput({
|
|
213
|
+
txid : utxo.txid,
|
|
214
|
+
index : utxo.vout,
|
|
215
|
+
nonWitnessUtxo : prevTxBytes,
|
|
216
|
+
witnessUtxo : { amount: BigInt(utxo.value), script: witnessScript },
|
|
217
|
+
tapInternalKey : opts.internalPubkey,
|
|
218
|
+
});
|
|
219
|
+
tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
|
|
220
|
+
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
tx,
|
|
224
|
+
prevOutScripts : [witnessScript],
|
|
225
|
+
prevOutValues : [BigInt(utxo.value)],
|
|
226
|
+
beaconAddress : opts.beaconAddress,
|
|
227
|
+
utxo,
|
|
228
|
+
feeSats,
|
|
229
|
+
scriptKind : 'p2tr',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Sign the single input of a singleton beacon transaction. Dispatches to the
|
|
235
|
+
* correct sighash + signature-application path based on `kind`, finalizes the
|
|
236
|
+
* tx, and returns the signed raw hex.
|
|
237
|
+
*
|
|
238
|
+
* - **P2PKH**: legacy ECDSA sighash; scure assembles the scriptSig from `partialSig`.
|
|
239
|
+
* - **P2WPKH**: BIP-143 segwit-v0 sighash (P2PKH-shaped scriptCode); scure assembles
|
|
240
|
+
* the witness from `partialSig`.
|
|
241
|
+
* - **P2TR**: BIP-341 taproot key-path sighash (SIGHASH_DEFAULT); 64-byte BIP-340
|
|
242
|
+
* Schnorr signature applied via `tapKeySig`.
|
|
243
|
+
*/
|
|
244
|
+
async function signSingletonInput(
|
|
245
|
+
tx: Transaction,
|
|
246
|
+
inputIdx: number,
|
|
247
|
+
kind: SingletonScriptKind,
|
|
248
|
+
signer: Signer,
|
|
249
|
+
prevOutScript: Uint8Array,
|
|
250
|
+
amount: bigint,
|
|
251
|
+
): Promise<string> {
|
|
252
|
+
const pubkey = signer.publicKey;
|
|
253
|
+
|
|
254
|
+
if(kind === 'p2pkh') {
|
|
255
|
+
// Legacy sighash: scriptCode is the prev-output P2PKH script itself.
|
|
256
|
+
// scure-btc-signer marks `preimageLegacy` as TypeScript-private but does not
|
|
257
|
+
// expose a public alternative; its own `signIdx` consumes the secret key
|
|
258
|
+
// directly. We need only the sighash bytes so an external Signer can produce
|
|
259
|
+
// the signature, so we reach through the type system here. If scure ever
|
|
260
|
+
// renames this method, the P2PKH path tests fail loudly.
|
|
261
|
+
// TODO: track https://github.com/paulmillr/scure-btc-signer/issues/142 —
|
|
262
|
+
// drop the cast once a public preimage (e.g. `preimageP2PKH`) lands upstream.
|
|
263
|
+
const sighashType = SigHash.ALL;
|
|
264
|
+
const sighash = (tx as unknown as {
|
|
265
|
+
preimageLegacy: (idx: number, prevScript: Uint8Array, hashType: number) => Uint8Array;
|
|
266
|
+
}).preimageLegacy(inputIdx, prevOutScript, sighashType);
|
|
267
|
+
const sig = signer.sign(sighash, 'ecdsa');
|
|
268
|
+
const sigWithType = concatBytes(sig, new Uint8Array([sighashType]));
|
|
269
|
+
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
270
|
+
tx.finalize();
|
|
271
|
+
return tx.hex;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if(kind === 'p2wpkh') {
|
|
275
|
+
// BIP-143: scriptCode for a P2WPKH input is the equivalent legacy P2PKH script
|
|
276
|
+
// (`OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG`). The P2PKH-shaped
|
|
277
|
+
// script appearing here in P2WPKH signing is intentional, not a bug.
|
|
278
|
+
//
|
|
279
|
+
// Derive the hash from `prevOutScript` (the bytes actually committed on-chain),
|
|
280
|
+
// not by re-hashing `signer.publicKey`. BIP-143 commits to the prev output, so
|
|
281
|
+
// the sighash must follow those bytes exactly. Rebuilding from the signer's
|
|
282
|
+
// pubkey assumes (rather than verifies) the two are in sync.
|
|
283
|
+
const decoded = OutScript.decode(prevOutScript);
|
|
284
|
+
if(decoded.type !== 'wpkh') {
|
|
285
|
+
throw new BeaconError(
|
|
286
|
+
`Expected P2WPKH prev-output script, got "${decoded.type}".`,
|
|
287
|
+
'PREVOUT_SCRIPT_MISMATCH',
|
|
288
|
+
{ kind, observedScriptType: decoded.type }
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const sighashScript = OutScript.encode({ type: 'pkh', hash: decoded.hash });
|
|
292
|
+
const sighashType = SigHash.ALL;
|
|
293
|
+
const sighash = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
|
|
294
|
+
const sig = signer.sign(sighash, 'ecdsa');
|
|
295
|
+
const sigWithType = concatBytes(sig, new Uint8Array([sighashType]));
|
|
296
|
+
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
297
|
+
tx.finalize();
|
|
298
|
+
return tx.hex;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// P2TR key-path. BIP-341 requires signing with the taproot-tweaked secret
|
|
302
|
+
// `d' = taprootTweakPrivKey(d, merkleRoot)`; the verifier checks against the
|
|
303
|
+
// tweaked output internal key `Q = P + tG`. The tweak lives inside the Signer
|
|
304
|
+
// (it needs the secret key), so we use scheme 'bip341' rather than the raw
|
|
305
|
+
// 'bip340' scheme. No script tree on singleton beacons → no merkleRoot.
|
|
306
|
+
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], SigHash.DEFAULT, [amount]);
|
|
307
|
+
const sig = signer.sign(sighash, 'bip341');
|
|
308
|
+
tx.updateInput(inputIdx, { tapKeySig: sig });
|
|
309
|
+
tx.finalize();
|
|
310
|
+
return tx.hex;
|
|
311
|
+
}
|
|
312
|
+
|
|
25
313
|
/**
|
|
26
314
|
* Abstract base class for all BTCR2 Beacon types.
|
|
27
315
|
* A Beacon is a service listed in a BTCR2 DID document that informs resolvers
|
|
@@ -67,116 +355,160 @@ export abstract class Beacon {
|
|
|
67
355
|
* Broadcasts a signed update as a Beacon Signal to the Bitcoin network.
|
|
68
356
|
* Used during the update path.
|
|
69
357
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
70
|
-
* @param {
|
|
358
|
+
* @param {Signer} signer Signer that produces the signature for the spending input.
|
|
359
|
+
* ECDSA for P2PKH / P2WPKH singletons, Schnorr (BIP-340) for P2TR key-path.
|
|
71
360
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
72
361
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
73
362
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
74
363
|
*/
|
|
75
364
|
abstract broadcastSignal(
|
|
76
365
|
signedUpdate: SignedBTCR2Update,
|
|
77
|
-
|
|
366
|
+
signer: Signer,
|
|
78
367
|
bitcoin: BitcoinConnection,
|
|
79
368
|
options?: BroadcastOptions
|
|
80
369
|
): Promise<SignedBTCR2Update>;
|
|
81
370
|
|
|
82
371
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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.
|
|
372
|
+
* Build + sign + broadcast a singleton beacon signal transaction. The beacon
|
|
373
|
+
* address's script kind (P2PKH / P2WPKH / P2TR) is detected automatically
|
|
374
|
+
* and the input is constructed and signed accordingly.
|
|
94
375
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
376
|
+
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
377
|
+
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
378
|
+
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead —
|
|
379
|
+
* the multi-party path can't share the signing phase, but the tx-construction
|
|
380
|
+
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
99
381
|
*
|
|
100
382
|
* @param signalBytes 32-byte payload to embed in OP_RETURN.
|
|
101
|
-
* @param
|
|
383
|
+
* @param signer Signer used to sign the spending input.
|
|
102
384
|
* @param bitcoin Bitcoin network connection.
|
|
103
385
|
* @param options Broadcast options (fee estimator, etc.).
|
|
104
386
|
* @returns The txid of the broadcast transaction.
|
|
105
|
-
* @throws {BeaconError} if the address is unfunded
|
|
387
|
+
* @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
|
|
106
388
|
*/
|
|
107
389
|
protected async buildSignAndBroadcast(
|
|
108
390
|
signalBytes: Uint8Array,
|
|
109
|
-
|
|
391
|
+
signer: Signer,
|
|
110
392
|
bitcoin: BitcoinConnection,
|
|
111
393
|
options?: BroadcastOptions
|
|
112
394
|
): Promise<string> {
|
|
113
395
|
const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
396
|
+
const beaconAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
|
|
397
|
+
const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
|
|
398
|
+
const plan = await this.buildSinglePartyTx({
|
|
399
|
+
signalBytes, beaconAddress, utxo, prevTxBytes, signer, bitcoin, feeEstimator,
|
|
400
|
+
});
|
|
401
|
+
const signedHex = await this.signSinglePartyTx(plan, signer);
|
|
402
|
+
return this.broadcastRawTx(bitcoin, signedHex);
|
|
403
|
+
}
|
|
114
404
|
|
|
115
|
-
|
|
116
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Build an unsigned singleton beacon tx ready for {@link signSinglePartyTx}.
|
|
407
|
+
*
|
|
408
|
+
* Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
|
|
409
|
+
* the input accordingly. Validates that the signer's pubkey produces the beacon
|
|
410
|
+
* address under that script kind — without this check, a misconfigured caller
|
|
411
|
+
* would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
|
|
412
|
+
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
|
|
413
|
+
* round-trip.
|
|
414
|
+
*/
|
|
415
|
+
protected async buildSinglePartyTx(opts: {
|
|
416
|
+
signalBytes: Uint8Array;
|
|
417
|
+
beaconAddress: string;
|
|
418
|
+
utxo: AddressUtxo;
|
|
419
|
+
prevTxBytes: Uint8Array;
|
|
420
|
+
signer: Signer;
|
|
421
|
+
bitcoin: BitcoinConnection;
|
|
422
|
+
feeEstimator: FeeEstimator;
|
|
423
|
+
}): Promise<BeaconTxPlan> {
|
|
424
|
+
const network = opts.bitcoin.data;
|
|
425
|
+
const pubkey = opts.signer.publicKey;
|
|
426
|
+
const kind = detectSingletonScriptKind(opts.beaconAddress, network);
|
|
117
427
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if(!utxos.length) {
|
|
428
|
+
const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
|
|
429
|
+
if(derivedAddress !== opts.beaconAddress) {
|
|
121
430
|
throw new BeaconError(
|
|
122
|
-
|
|
123
|
-
'
|
|
431
|
+
`Signer pubkey produces ${kind.toUpperCase()} address "${derivedAddress}", but beacon address is "${opts.beaconAddress}".`,
|
|
432
|
+
'SIGNER_KEY_MISMATCH',
|
|
433
|
+
{ kind, address: opts.beaconAddress, derivedAddress }
|
|
124
434
|
);
|
|
125
435
|
}
|
|
126
436
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
).shift();
|
|
131
|
-
if(!utxo) {
|
|
437
|
+
const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
|
|
438
|
+
const amount = BigInt(opts.utxo.value);
|
|
439
|
+
if(amount <= feeSats) {
|
|
132
440
|
throw new BeaconError(
|
|
133
|
-
|
|
134
|
-
'
|
|
441
|
+
`UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
442
|
+
'INSUFFICIENT_FUNDS',
|
|
443
|
+
{ address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
|
|
135
444
|
);
|
|
136
445
|
}
|
|
137
446
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const keyPair = SchnorrKeyPair.fromSecret(secretKey);
|
|
143
|
-
const signer = {
|
|
144
|
-
publicKey : keyPair.publicKey.compressed,
|
|
145
|
-
sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
|
|
146
|
-
};
|
|
447
|
+
// allowUnknownOutputs: scure does not classify OP_RETURN as a "known" output
|
|
448
|
+
// type because it is unspendable by design. The opt-in flag tells scure we
|
|
449
|
+
// know the output is intentional (the beacon signal embedded in OP_RETURN).
|
|
450
|
+
const tx = new Transaction({ allowUnknownOutputs: true });
|
|
147
451
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
452
|
+
// Per-kind input setup: P2PKH consumes via nonWitnessUtxo only (legacy);
|
|
453
|
+
// P2WPKH and P2TR also carry a witnessUtxo (and P2TR carries tapInternalKey).
|
|
454
|
+
let prevOutScript: Uint8Array;
|
|
455
|
+
if(kind === 'p2pkh') {
|
|
456
|
+
prevOutScript = p2pkh(pubkey, network).script;
|
|
457
|
+
tx.addInput({
|
|
458
|
+
txid : opts.utxo.txid,
|
|
459
|
+
index : opts.utxo.vout,
|
|
460
|
+
nonWitnessUtxo : opts.prevTxBytes,
|
|
461
|
+
});
|
|
462
|
+
} else if(kind === 'p2wpkh') {
|
|
463
|
+
prevOutScript = p2wpkh(pubkey, network).script;
|
|
464
|
+
tx.addInput({
|
|
465
|
+
txid : opts.utxo.txid,
|
|
466
|
+
index : opts.utxo.vout,
|
|
467
|
+
nonWitnessUtxo : opts.prevTxBytes,
|
|
468
|
+
witnessUtxo : { amount, script: prevOutScript },
|
|
469
|
+
});
|
|
470
|
+
} else {
|
|
471
|
+
// p2tr key-path
|
|
472
|
+
const internalKey = pubkey.slice(1, 33);
|
|
473
|
+
prevOutScript = p2tr(internalKey, undefined, network).script;
|
|
474
|
+
tx.addInput({
|
|
475
|
+
txid : opts.utxo.txid,
|
|
476
|
+
index : opts.utxo.vout,
|
|
477
|
+
nonWitnessUtxo : opts.prevTxBytes,
|
|
478
|
+
witnessUtxo : { amount, script: prevOutScript },
|
|
479
|
+
tapInternalKey : internalKey,
|
|
480
|
+
});
|
|
172
481
|
}
|
|
173
482
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.finalizeAllInputs()
|
|
177
|
-
.extractTransaction()
|
|
178
|
-
.toHex();
|
|
483
|
+
tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
|
|
484
|
+
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
179
485
|
|
|
180
|
-
return
|
|
486
|
+
return {
|
|
487
|
+
tx,
|
|
488
|
+
prevOutScripts : [prevOutScript],
|
|
489
|
+
prevOutValues : [amount],
|
|
490
|
+
beaconAddress : opts.beaconAddress,
|
|
491
|
+
utxo : opts.utxo,
|
|
492
|
+
feeSats,
|
|
493
|
+
scriptKind : kind,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Sign + finalize the unsigned single-party tx and return its raw hex.
|
|
499
|
+
* Dispatches to the correct signing primitive based on `plan.scriptKind`.
|
|
500
|
+
*/
|
|
501
|
+
protected async signSinglePartyTx(plan: BeaconTxPlan, signer: Signer): Promise<string> {
|
|
502
|
+
return signSingletonInput(
|
|
503
|
+
plan.tx, 0, plan.scriptKind, signer,
|
|
504
|
+
plan.prevOutScripts[0]!, plan.prevOutValues[0]!,
|
|
505
|
+
);
|
|
181
506
|
}
|
|
182
|
-
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
|
|
510
|
+
*/
|
|
511
|
+
protected async broadcastRawTx(bitcoin: BitcoinConnection, rawHex: string): Promise<string> {
|
|
512
|
+
return bitcoin.rest.transaction.send(rawHex);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import type { KeyBytes } from '@did-btcr2/common';
|
|
3
2
|
import { canonicalHash, canonicalize, decode, encode, hash } from '@did-btcr2/common';
|
|
4
3
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
5
|
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
6
6
|
import type { SidecarData } from '../types.js';
|
|
7
7
|
import type { BroadcastOptions } from './beacon.js';
|
|
@@ -118,7 +118,7 @@ export class CASBeacon extends Beacon {
|
|
|
118
118
|
* and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
119
119
|
*
|
|
120
120
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
121
|
-
* @param {
|
|
121
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
122
122
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
123
123
|
* @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
|
|
124
124
|
* `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
|
|
@@ -127,7 +127,7 @@ export class CASBeacon extends Beacon {
|
|
|
127
127
|
*/
|
|
128
128
|
async broadcastSignal(
|
|
129
129
|
signedUpdate: SignedBTCR2Update,
|
|
130
|
-
|
|
130
|
+
signer: Signer,
|
|
131
131
|
bitcoin: BitcoinConnection,
|
|
132
132
|
options?: CASBroadcastOptions
|
|
133
133
|
): Promise<SignedBTCR2Update> {
|
|
@@ -144,7 +144,7 @@ export class CASBeacon extends Beacon {
|
|
|
144
144
|
const announcementHash = hash(canonicalize(casAnnouncement));
|
|
145
145
|
|
|
146
146
|
// Delegate UTXO selection, PSBT construction, fee estimation, signing, and broadcast
|
|
147
|
-
await this.buildSignAndBroadcast(announcementHash,
|
|
147
|
+
await this.buildSignAndBroadcast(announcementHash, signer, bitcoin, options);
|
|
148
148
|
|
|
149
149
|
// Publish CAS Announcement to content-addressed store if callback provided
|
|
150
150
|
if(options?.casPublish) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import type { KeyBytes } from '@did-btcr2/common';
|
|
3
2
|
import { canonicalize, hash } from '@did-btcr2/common';
|
|
4
3
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
5
|
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
6
6
|
import type { SidecarData } from '../types.js';
|
|
7
7
|
import type { BroadcastOptions } from './beacon.js';
|
|
@@ -67,7 +67,7 @@ export class SingletonBeacon extends Beacon {
|
|
|
67
67
|
* {@link Beacon.buildSignAndBroadcast}.
|
|
68
68
|
*
|
|
69
69
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
70
|
-
* @param {
|
|
70
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
71
71
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
72
72
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
73
73
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
@@ -75,12 +75,12 @@ export class SingletonBeacon extends Beacon {
|
|
|
75
75
|
*/
|
|
76
76
|
async broadcastSignal(
|
|
77
77
|
signedUpdate: SignedBTCR2Update,
|
|
78
|
-
|
|
78
|
+
signer: Signer,
|
|
79
79
|
bitcoin: BitcoinConnection,
|
|
80
80
|
options?: BroadcastOptions
|
|
81
81
|
): Promise<SignedBTCR2Update> {
|
|
82
82
|
const signalBytes = hash(canonicalize(signedUpdate));
|
|
83
|
-
await this.buildSignAndBroadcast(signalBytes,
|
|
83
|
+
await this.buildSignAndBroadcast(signalBytes, signer, bitcoin, options);
|
|
84
84
|
return signedUpdate;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
2
|
import { canonicalize } from '@did-btcr2/common';
|
|
3
|
-
import type { KeyBytes } from '@did-btcr2/common';
|
|
4
3
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
5
|
import { blockHash, BTCR2MerkleTree, didToIndex, hexToHash, verifySerializedProof } from '@did-btcr2/smt';
|
|
6
6
|
import { randomBytes } from '@noble/hashes/utils';
|
|
7
7
|
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
@@ -122,7 +122,7 @@ export class SMTBeacon extends Beacon {
|
|
|
122
122
|
* signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
123
123
|
*
|
|
124
124
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
125
|
-
* @param {
|
|
125
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
126
126
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
127
127
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
128
128
|
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
@@ -130,7 +130,7 @@ export class SMTBeacon extends Beacon {
|
|
|
130
130
|
*/
|
|
131
131
|
async broadcastSignal(
|
|
132
132
|
signedUpdate: SignedBTCR2Update,
|
|
133
|
-
|
|
133
|
+
signer: Signer,
|
|
134
134
|
bitcoin: BitcoinConnection,
|
|
135
135
|
options?: BroadcastOptions
|
|
136
136
|
): Promise<SignedBTCR2Update> {
|
|
@@ -145,7 +145,7 @@ export class SMTBeacon extends Beacon {
|
|
|
145
145
|
tree.finalize();
|
|
146
146
|
|
|
147
147
|
// Root hash is the signal bytes for the OP_RETURN output
|
|
148
|
-
await this.buildSignAndBroadcast(tree.rootHash,
|
|
148
|
+
await this.buildSignAndBroadcast(tree.rootHash, signer, bitcoin, options);
|
|
149
149
|
|
|
150
150
|
return signedUpdate;
|
|
151
151
|
}
|