@did-btcr2/method 0.26.0 → 0.27.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 +86 -233
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +24111 -20342
- package/dist/browser.mjs +24111 -20342
- package/dist/cjs/index.js +2463 -2174
- package/dist/esm/core/aggregation/cohort.js +178 -0
- package/dist/esm/core/aggregation/cohort.js.map +1 -0
- package/dist/esm/core/aggregation/errors.js +22 -0
- package/dist/esm/core/aggregation/errors.js.map +1 -0
- package/dist/esm/core/{beacon/aggregation/cohort → aggregation}/messages/base.js +0 -1
- package/dist/esm/core/aggregation/messages/base.js.map +1 -0
- package/dist/esm/core/aggregation/messages/constants.js +26 -0
- package/dist/esm/core/aggregation/messages/constants.js.map +1 -0
- package/dist/esm/core/aggregation/messages/factories.js +113 -0
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -0
- package/dist/esm/core/aggregation/messages/guards.js +37 -0
- package/dist/esm/core/aggregation/messages/guards.js.map +1 -0
- package/dist/esm/core/aggregation/messages/index.js +5 -0
- package/dist/esm/core/aggregation/messages/index.js.map +1 -0
- package/dist/esm/core/aggregation/participant.js +376 -0
- package/dist/esm/core/aggregation/participant.js.map +1 -0
- package/dist/esm/core/aggregation/phases.js +39 -0
- package/dist/esm/core/aggregation/phases.js.map +1 -0
- package/dist/esm/core/aggregation/runner/events.js +2 -0
- package/dist/esm/core/aggregation/runner/events.js.map +1 -0
- package/dist/esm/core/aggregation/runner/index.js +5 -0
- package/dist/esm/core/aggregation/runner/index.js.map +1 -0
- package/dist/esm/core/aggregation/runner/participant-runner.js +282 -0
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -0
- package/dist/esm/core/aggregation/runner/service-runner.js +290 -0
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -0
- package/dist/esm/core/aggregation/runner/typed-emitter.js +80 -0
- package/dist/esm/core/aggregation/runner/typed-emitter.js.map +1 -0
- package/dist/esm/core/aggregation/service.js +416 -0
- package/dist/esm/core/aggregation/service.js.map +1 -0
- package/dist/esm/core/aggregation/signing-session.js +133 -0
- package/dist/esm/core/aggregation/signing-session.js.map +1 -0
- package/dist/esm/core/aggregation/transport/didcomm.js +32 -0
- package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -0
- package/dist/esm/core/aggregation/transport/error.js +12 -0
- package/dist/esm/core/aggregation/transport/error.js.map +1 -0
- package/dist/esm/core/aggregation/transport/factory.js +20 -0
- package/dist/esm/core/aggregation/transport/factory.js.map +1 -0
- package/dist/esm/core/aggregation/transport/index.js +6 -0
- package/dist/esm/core/aggregation/transport/index.js.map +1 -0
- package/dist/esm/core/aggregation/transport/nostr.js +262 -0
- package/dist/esm/core/aggregation/transport/nostr.js.map +1 -0
- package/dist/esm/core/aggregation/transport/transport.js +2 -0
- package/dist/esm/core/aggregation/transport/transport.js.map +1 -0
- package/dist/esm/core/beacon/beacon.js +80 -0
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +15 -56
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/error.js +0 -10
- package/dist/esm/core/beacon/error.js.map +1 -1
- package/dist/esm/core/beacon/fee-estimator.js +30 -0
- package/dist/esm/core/beacon/fee-estimator.js.map +1 -0
- package/dist/esm/core/beacon/singleton-beacon.js +10 -53
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +85 -9
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/identifier.js +13 -0
- package/dist/esm/core/identifier.js.map +1 -1
- package/dist/esm/core/resolver.js +9 -0
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/index.js +14 -24
- package/dist/esm/index.js.map +1 -1
- package/dist/types/core/aggregation/cohort.d.ts +94 -0
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -0
- package/dist/types/core/aggregation/errors.d.ts +14 -0
- package/dist/types/core/aggregation/errors.d.ts.map +1 -0
- package/dist/types/core/{beacon/aggregation/cohort → aggregation}/messages/base.d.ts +7 -1
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/constants.d.ts +23 -0
- package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts +177 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/guards.d.ts +11 -0
- package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/index.d.ts +5 -0
- package/dist/types/core/aggregation/messages/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/participant.d.ts +101 -0
- package/dist/types/core/aggregation/participant.d.ts.map +1 -0
- package/dist/types/core/aggregation/phases.d.ts +49 -0
- package/dist/types/core/aggregation/phases.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/events.d.ts +89 -0
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/index.d.ts +5 -0
- package/dist/types/core/aggregation/runner/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +107 -0
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/service-runner.d.ts +102 -0
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/typed-emitter.d.ts +41 -0
- package/dist/types/core/aggregation/runner/typed-emitter.d.ts.map +1 -0
- package/dist/types/core/aggregation/service.d.ts +112 -0
- package/dist/types/core/aggregation/service.d.ts.map +1 -0
- package/dist/types/core/aggregation/signing-session.d.ts +69 -0
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts +20 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -0
- package/dist/types/core/{beacon/aggregation/communication → aggregation/transport}/error.d.ts +2 -2
- package/dist/types/core/aggregation/transport/error.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/factory.d.ts +13 -0
- package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts +6 -0
- package/dist/types/core/aggregation/transport/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/nostr.d.ts +55 -0
- package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/transport.d.ts +37 -0
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -0
- package/dist/types/core/beacon/beacon.d.ts +37 -2
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +19 -7
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/error.d.ts +0 -6
- package/dist/types/core/beacon/error.d.ts.map +1 -1
- package/dist/types/core/beacon/fee-estimator.d.ts +40 -0
- package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -0
- package/dist/types/core/beacon/interfaces.d.ts +8 -0
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/singleton-beacon.d.ts +9 -2
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +27 -7
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/identifier.d.ts +8 -0
- package/dist/types/core/identifier.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +2 -2
- package/dist/types/core/resolver.d.ts +11 -1
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/dist/types/index.d.ts +9 -24
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +31 -30
- package/src/core/aggregation/cohort.ts +247 -0
- package/src/core/aggregation/errors.ts +25 -0
- package/src/core/{beacon/aggregation/cohort → aggregation}/messages/base.ts +8 -3
- package/src/core/aggregation/messages/constants.ts +28 -0
- package/src/core/aggregation/messages/factories.ts +240 -0
- package/src/core/aggregation/messages/guards.ts +55 -0
- package/src/core/aggregation/messages/index.ts +4 -0
- package/src/core/aggregation/participant.ts +510 -0
- package/src/core/aggregation/phases.ts +82 -0
- package/src/core/aggregation/runner/events.ts +77 -0
- package/src/core/aggregation/runner/index.ts +4 -0
- package/src/core/aggregation/runner/participant-runner.ts +360 -0
- package/src/core/aggregation/runner/service-runner.ts +365 -0
- package/src/core/aggregation/runner/typed-emitter.ts +87 -0
- package/src/core/aggregation/service.ts +547 -0
- package/src/core/aggregation/signing-session.ts +209 -0
- package/src/core/aggregation/transport/didcomm.ts +42 -0
- package/src/core/aggregation/transport/error.ts +13 -0
- package/src/core/aggregation/transport/factory.ts +29 -0
- package/src/core/aggregation/transport/index.ts +5 -0
- package/src/core/aggregation/transport/nostr.ts +333 -0
- package/src/core/aggregation/transport/transport.ts +46 -0
- package/src/core/beacon/beacon.ts +122 -2
- package/src/core/beacon/cas-beacon.ts +28 -76
- package/src/core/beacon/error.ts +0 -12
- package/src/core/beacon/fee-estimator.ts +52 -0
- package/src/core/beacon/interfaces.ts +10 -1
- package/src/core/beacon/singleton-beacon.ts +14 -75
- package/src/core/beacon/smt-beacon.ts +109 -11
- package/src/core/identifier.ts +17 -0
- package/src/core/interfaces.ts +2 -2
- package/src/core/resolver.ts +25 -2
- package/src/index.ts +15 -29
- package/dist/esm/core/beacon/aggregation/cohort/index.js +0 -237
- package/dist/esm/core/beacon/aggregation/cohort/index.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/base.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/constants.js +0 -11
- package/dist/esm/core/beacon/aggregation/cohort/messages/constants.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/index.js +0 -98
- package/dist/esm/core/beacon/aggregation/cohort/messages/index.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.js +0 -31
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.js +0 -29
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.js +0 -27
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in.js +0 -23
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/opt-in.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/subscribe.js +0 -28
- package/dist/esm/core/beacon/aggregation/cohort/messages/keygen/subscribe.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.js +0 -29
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/authorization-request.js +0 -30
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/authorization-request.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.js +0 -30
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/request-signature.js +0 -30
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/request-signature.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/signature-authorization.js +0 -31
- package/dist/esm/core/beacon/aggregation/cohort/messages/sign/signature-authorization.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/cohort/status.js +0 -8
- package/dist/esm/core/beacon/aggregation/cohort/status.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js +0 -121
- package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js +0 -245
- package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/communication/error.js +0 -12
- package/dist/esm/core/beacon/aggregation/communication/error.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/communication/factory.js +0 -21
- package/dist/esm/core/beacon/aggregation/communication/factory.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/communication/service.js +0 -2
- package/dist/esm/core/beacon/aggregation/communication/service.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/coordinator.js +0 -343
- package/dist/esm/core/beacon/aggregation/coordinator.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/participant.js +0 -435
- package/dist/esm/core/beacon/aggregation/participant.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/session/index.js +0 -244
- package/dist/esm/core/beacon/aggregation/session/index.js.map +0 -1
- package/dist/esm/core/beacon/aggregation/session/status.js +0 -11
- package/dist/esm/core/beacon/aggregation/session/status.js.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/index.d.ts +0 -136
- package/dist/types/core/beacon/aggregation/cohort/index.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/base.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/constants.d.ts +0 -11
- package/dist/types/core/beacon/aggregation/cohort/messages/constants.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/index.d.ts +0 -65
- package/dist/types/core/beacon/aggregation/cohort/messages/index.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.d.ts +0 -29
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts +0 -26
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.d.ts +0 -24
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in.d.ts +0 -20
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/subscribe.d.ts +0 -25
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/subscribe.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.d.ts +0 -25
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/authorization-request.d.ts +0 -26
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/authorization-request.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.d.ts +0 -26
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/request-signature.d.ts +0 -26
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/request-signature.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/signature-authorization.d.ts +0 -27
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/signature-authorization.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/cohort/status.d.ts +0 -8
- package/dist/types/core/beacon/aggregation/cohort/status.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts +0 -89
- package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts +0 -103
- package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/communication/error.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/communication/factory.d.ts +0 -10
- package/dist/types/core/beacon/aggregation/communication/factory.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/communication/service.d.ts +0 -36
- package/dist/types/core/beacon/aggregation/communication/service.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/coordinator.d.ts +0 -116
- package/dist/types/core/beacon/aggregation/coordinator.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/participant.d.ts +0 -192
- package/dist/types/core/beacon/aggregation/participant.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/session/index.d.ts +0 -156
- package/dist/types/core/beacon/aggregation/session/index.d.ts.map +0 -1
- package/dist/types/core/beacon/aggregation/session/status.d.ts +0 -11
- package/dist/types/core/beacon/aggregation/session/status.d.ts.map +0 -1
- package/src/core/beacon/aggregation/cohort/index.ts +0 -305
- package/src/core/beacon/aggregation/cohort/messages/constants.ts +0 -12
- package/src/core/beacon/aggregation/cohort/messages/index.ts +0 -143
- package/src/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.ts +0 -44
- package/src/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.ts +0 -40
- package/src/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.ts +0 -35
- package/src/core/beacon/aggregation/cohort/messages/keygen/opt-in.ts +0 -34
- package/src/core/beacon/aggregation/cohort/messages/keygen/subscribe.ts +0 -36
- package/src/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.ts +0 -39
- package/src/core/beacon/aggregation/cohort/messages/sign/authorization-request.ts +0 -40
- package/src/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.ts +0 -40
- package/src/core/beacon/aggregation/cohort/messages/sign/request-signature.ts +0 -40
- package/src/core/beacon/aggregation/cohort/messages/sign/signature-authorization.ts +0 -41
- package/src/core/beacon/aggregation/cohort/status.ts +0 -7
- package/src/core/beacon/aggregation/communication/adapter/did-comm.ts +0 -148
- package/src/core/beacon/aggregation/communication/adapter/nostr.ts +0 -323
- package/src/core/beacon/aggregation/communication/error.ts +0 -13
- package/src/core/beacon/aggregation/communication/factory.ts +0 -25
- package/src/core/beacon/aggregation/communication/service.ts +0 -42
- package/src/core/beacon/aggregation/coordinator.ts +0 -419
- package/src/core/beacon/aggregation/participant.ts +0 -517
- package/src/core/beacon/aggregation/session/index.ts +0 -301
- package/src/core/beacon/aggregation/session/status.ts +0 -18
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
import type { AddressUtxo, BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
1
2
|
import type { KeyBytes } from '@did-btcr2/common';
|
|
2
|
-
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
3
3
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
5
|
+
import { hexToBytes } from '@noble/hashes/utils';
|
|
6
|
+
import { opcodes, Psbt, script } from 'bitcoinjs-lib';
|
|
4
7
|
import type { BeaconProcessResult } from '../resolver.js';
|
|
5
8
|
import type { SidecarData } from '../types.js';
|
|
9
|
+
import { BeaconError } from './error.js';
|
|
10
|
+
import { StaticFeeEstimator } from './fee-estimator.js';
|
|
11
|
+
import type { FeeEstimator } from './fee-estimator.js';
|
|
6
12
|
import type { BeaconService, BeaconSignal } from './interfaces.js';
|
|
7
13
|
|
|
14
|
+
/** Default fee estimator used when none is supplied. ~5 sat/vB static rate. */
|
|
15
|
+
const DEFAULT_FEE_ESTIMATOR: FeeEstimator = new StaticFeeEstimator(5);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options accepted by {@link Beacon.buildSignAndBroadcast}.
|
|
19
|
+
*/
|
|
20
|
+
export interface BroadcastOptions {
|
|
21
|
+
/** Fee estimator for computing the transaction fee. Defaults to {@link DEFAULT_FEE_ESTIMATOR}. */
|
|
22
|
+
feeEstimator?: FeeEstimator;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
/**
|
|
9
26
|
* Abstract base class for all BTCR2 Beacon types.
|
|
10
27
|
* A Beacon is a service listed in a BTCR2 DID document that informs resolvers
|
|
@@ -52,11 +69,114 @@ export abstract class Beacon {
|
|
|
52
69
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
53
70
|
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
54
71
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
72
|
+
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
55
73
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
56
74
|
*/
|
|
57
75
|
abstract broadcastSignal(
|
|
58
76
|
signedUpdate: SignedBTCR2Update,
|
|
59
77
|
secretKey: KeyBytes,
|
|
60
|
-
bitcoin: BitcoinConnection
|
|
78
|
+
bitcoin: BitcoinConnection,
|
|
79
|
+
options?: BroadcastOptions
|
|
61
80
|
): Promise<SignedBTCR2Update>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Shared PSBT construction + signing + broadcast helper used by all beacon types.
|
|
84
|
+
*
|
|
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.
|
|
99
|
+
*
|
|
100
|
+
* @param signalBytes 32-byte payload to embed in OP_RETURN.
|
|
101
|
+
* @param secretKey Secret key used to sign the spending input.
|
|
102
|
+
* @param bitcoin Bitcoin network connection.
|
|
103
|
+
* @param options Broadcast options (fee estimator, etc.).
|
|
104
|
+
* @returns The txid of the broadcast transaction.
|
|
105
|
+
* @throws {BeaconError} if the address is unfunded or no UTXO is available.
|
|
106
|
+
*/
|
|
107
|
+
protected async buildSignAndBroadcast(
|
|
108
|
+
signalBytes: Uint8Array,
|
|
109
|
+
secretKey: KeyBytes,
|
|
110
|
+
bitcoin: BitcoinConnection,
|
|
111
|
+
options?: BroadcastOptions
|
|
112
|
+
): Promise<string> {
|
|
113
|
+
const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
114
|
+
|
|
115
|
+
// Strip the 'bitcoin:' prefix from the service endpoint.
|
|
116
|
+
const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
|
|
117
|
+
|
|
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 }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
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) {
|
|
132
|
+
throw new BeaconError(
|
|
133
|
+
'Beacon bitcoin address unfunded or utxos unconfirmed.',
|
|
134
|
+
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
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' }),
|
|
146
|
+
};
|
|
147
|
+
|
|
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
|
+
}
|
|
173
|
+
|
|
174
|
+
const signedTxHex = build(fee)
|
|
175
|
+
.signInput(0, signer)
|
|
176
|
+
.finalizeAllInputs()
|
|
177
|
+
.extractTransaction()
|
|
178
|
+
.toHex();
|
|
179
|
+
|
|
180
|
+
return bitcoin.rest.transaction.send(signedTxHex);
|
|
181
|
+
}
|
|
62
182
|
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
2
|
import type { KeyBytes } from '@did-btcr2/common';
|
|
3
3
|
import { canonicalHash, canonicalize, decode, encode, hash } from '@did-btcr2/common';
|
|
4
4
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
5
|
-
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
6
|
-
import { hexToBytes } from '@noble/hashes/utils';
|
|
7
|
-
import { opcodes, Psbt, script } from 'bitcoinjs-lib';
|
|
8
5
|
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
9
6
|
import type { SidecarData } from '../types.js';
|
|
7
|
+
import type { BroadcastOptions } from './beacon.js';
|
|
10
8
|
import { Beacon } from './beacon.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
9
|
+
import type { BeaconService, BeaconSignal, BlockMetadata, CasPublishFn } from './interfaces.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CAS-specific broadcast options — extends {@link BroadcastOptions} with an optional
|
|
13
|
+
* `casPublish` callback used to publish the CAS Announcement off-chain after the
|
|
14
|
+
* OP_RETURN signal is broadcast.
|
|
15
|
+
*/
|
|
16
|
+
export interface CASBroadcastOptions extends BroadcastOptions {
|
|
17
|
+
casPublish?: CasPublishFn;
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#cas-beacon | CAS Beacon}.
|
|
@@ -39,7 +45,7 @@ export class CASBeacon extends Beacon {
|
|
|
39
45
|
* For each signal, the signalBytes contain the hex-encoded hash of a CAS Announcement.
|
|
40
46
|
* The CAS Announcement maps DIDs to their base64url-encoded update hashes.
|
|
41
47
|
* This method looks up the CAS Announcement from the sidecar, extracts the update
|
|
42
|
-
* hash for the DID being resolved, and retrieves the corresponding signed update.
|
|
48
|
+
* hash for the DID being resolved, and retrieves the corresponding signed update from sidecar.
|
|
43
49
|
*
|
|
44
50
|
* @param {Array<BeaconSignal>} signals The array of Beacon Signals to process.
|
|
45
51
|
* @param {SidecarData} sidecar The sidecar data associated with the CAS Beacon.
|
|
@@ -106,99 +112,45 @@ export class CASBeacon extends Beacon {
|
|
|
106
112
|
/**
|
|
107
113
|
* Broadcasts a CAS Beacon signal to the Bitcoin network.
|
|
108
114
|
*
|
|
109
|
-
* Creates a CAS Announcement mapping the DID to the update hash,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
115
|
+
* Creates a CAS Announcement mapping the DID to the update hash, broadcasts the hash of the
|
|
116
|
+
* announcement via OP_RETURN, and optionally publishes the announcement off-chain via the
|
|
117
|
+
* supplied `casPublish` callback. UTXO selection, PSBT construction, fee estimation, signing,
|
|
118
|
+
* and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
112
119
|
*
|
|
113
120
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
114
121
|
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
115
122
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
123
|
+
* @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
|
|
124
|
+
* `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
|
|
116
125
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
117
|
-
* @throws {
|
|
126
|
+
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
118
127
|
*/
|
|
119
128
|
async broadcastSignal(
|
|
120
129
|
signedUpdate: SignedBTCR2Update,
|
|
121
130
|
secretKey: KeyBytes,
|
|
122
|
-
bitcoin: BitcoinConnection
|
|
131
|
+
bitcoin: BitcoinConnection,
|
|
132
|
+
options?: CASBroadcastOptions
|
|
123
133
|
): Promise<SignedBTCR2Update> {
|
|
124
134
|
// Extract the DID from the beacon service id (strip the #fragment)
|
|
125
135
|
const did = this.service.id.split('#')[0];
|
|
126
136
|
|
|
127
|
-
// Hash the signed update (
|
|
137
|
+
// Hash the signed update (base64urlnopad for the CAS Announcement entry per spec)
|
|
128
138
|
const updateHash = canonicalHash(signedUpdate);
|
|
129
139
|
|
|
130
140
|
// Create the CAS Announcement mapping this DID to its update hash
|
|
131
141
|
const casAnnouncement = { [did]: updateHash };
|
|
132
142
|
|
|
133
|
-
// TODO: Publish CAS Announcement to content-addressed store (e.g., IPFS via Helia)
|
|
134
|
-
|
|
135
143
|
// Canonicalize and hash the CAS Announcement for the OP_RETURN output
|
|
136
144
|
const announcementHash = hash(canonicalize(casAnnouncement));
|
|
137
145
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Query the Bitcoin network for UTXOs associated with the bitcoinAddress
|
|
142
|
-
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
143
|
-
|
|
144
|
-
// If no utxos are found, throw an error indicating the address is unfunded.
|
|
145
|
-
if(!utxos.length) {
|
|
146
|
-
throw new CASBeaconError(
|
|
147
|
-
'No UTXOs found, please fund address!',
|
|
148
|
-
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
149
|
-
);
|
|
150
|
-
}
|
|
146
|
+
// Delegate UTXO selection, PSBT construction, fee estimation, signing, and broadcast
|
|
147
|
+
await this.buildSignAndBroadcast(announcementHash, secretKey, bitcoin, options);
|
|
151
148
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
(
|
|
155
|
-
).shift();
|
|
156
|
-
|
|
157
|
-
// If no utxos are found, throw an error.
|
|
158
|
-
if(!utxo) {
|
|
159
|
-
throw new CASBeaconError(
|
|
160
|
-
'Beacon bitcoin address unfunded or utxos unconfirmed.',
|
|
161
|
-
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
162
|
-
);
|
|
149
|
+
// Publish CAS Announcement to content-addressed store if callback provided
|
|
150
|
+
if(options?.casPublish) {
|
|
151
|
+
await options.casPublish(casAnnouncement);
|
|
163
152
|
}
|
|
164
153
|
|
|
165
|
-
// Get the previous tx to the utxo being spent
|
|
166
|
-
const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
167
|
-
|
|
168
|
-
// Construct a spend transaction
|
|
169
|
-
const spendTx = new Psbt({ network: bitcoin.data })
|
|
170
|
-
// Spend tx contains the utxo as its input
|
|
171
|
-
.addInput({
|
|
172
|
-
hash : utxo.txid,
|
|
173
|
-
index : utxo.vout,
|
|
174
|
-
nonWitnessUtxo : hexToBytes(prevTx)
|
|
175
|
-
})
|
|
176
|
-
// Add a change output minus a fee of 500 sats
|
|
177
|
-
// TODO: calculate fee based on transaction vsize and current fee rates
|
|
178
|
-
.addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - BigInt(500) })
|
|
179
|
-
// Add an OP_RETURN output containing the CAS Announcement hash
|
|
180
|
-
.addOutput({ script: script.compile([opcodes.OP_RETURN, announcementHash]), value: 0n });
|
|
181
|
-
|
|
182
|
-
// Construct a key pair and PSBT signer from the secret key
|
|
183
|
-
const keyPair = SchnorrKeyPair.fromSecret(secretKey);
|
|
184
|
-
const signer = {
|
|
185
|
-
publicKey : keyPair.publicKey.compressed,
|
|
186
|
-
sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// Sign 0th input, finalize extract to hex in prep for broadcast
|
|
190
|
-
const signedTx = spendTx.signInput(0, signer)
|
|
191
|
-
.finalizeAllInputs()
|
|
192
|
-
.extractTransaction()
|
|
193
|
-
.toHex();
|
|
194
|
-
|
|
195
|
-
// Broadcast spendTx to the Bitcoin network.
|
|
196
|
-
const txid = await bitcoin.rest.transaction.send(signedTx);
|
|
197
|
-
|
|
198
|
-
// Log the txid of the broadcasted transaction
|
|
199
|
-
console.info(`CAS Beacon Signal Broadcasted with txid: ${txid}`);
|
|
200
|
-
|
|
201
|
-
// Return the signed update
|
|
202
154
|
return signedUpdate;
|
|
203
155
|
}
|
|
204
156
|
}
|
package/src/core/beacon/error.ts
CHANGED
|
@@ -6,18 +6,6 @@ export class BeaconError extends MethodError {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export class BeaconCoordinatorError extends MethodError {
|
|
10
|
-
constructor(message: string, type: string = 'BeaconCoordinatorError', data?: Record<string, any>) {
|
|
11
|
-
super(message, type, data);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class BeaconParticipantError extends MethodError {
|
|
16
|
-
constructor(message: string, type: string = 'BeaconParticipantError', data?: Record<string, any>) {
|
|
17
|
-
super(message, type, data);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
9
|
export class SingletonBeaconError extends MethodError {
|
|
22
10
|
constructor(message: string, type: string = 'SingletonBeaconError', data?: Record<string, any>) {
|
|
23
11
|
super(message, type, data);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estimates the fee (in satoshis) for a transaction of a given virtual size.
|
|
3
|
+
*
|
|
4
|
+
* Beacons delegate fee calculation to a `FeeEstimator` so that callers can
|
|
5
|
+
* plug in different strategies: static fee rates for tests/regtest, mempool
|
|
6
|
+
* APIs for mainnet, Bitcoin Core `estimatesmartfee` RPC for full-node setups,
|
|
7
|
+
* etc.
|
|
8
|
+
*
|
|
9
|
+
* Implementations return the **total fee in satoshis** for a transaction of
|
|
10
|
+
* the given virtual size (`vsize`). Callers pass the vsize; the estimator
|
|
11
|
+
* decides the fee rate and computes the total.
|
|
12
|
+
*/
|
|
13
|
+
export interface FeeEstimator {
|
|
14
|
+
/**
|
|
15
|
+
* Estimate the total fee in satoshis for a transaction of the given vsize.
|
|
16
|
+
* @param vsize Transaction virtual size in vbytes.
|
|
17
|
+
* @returns Total fee in satoshis.
|
|
18
|
+
*/
|
|
19
|
+
estimateFee(vsize: number): Promise<bigint>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fee estimator that returns a fixed fee rate regardless of network conditions.
|
|
24
|
+
*
|
|
25
|
+
* Suitable for:
|
|
26
|
+
* - Tests (deterministic outputs)
|
|
27
|
+
* - Regtest (no real fee market)
|
|
28
|
+
* - Environments where a fee rate is supplied out-of-band
|
|
29
|
+
*
|
|
30
|
+
* For mainnet production use, prefer a dynamic estimator that queries current
|
|
31
|
+
* network conditions (mempool APIs, Bitcoin Core RPC).
|
|
32
|
+
*/
|
|
33
|
+
export class StaticFeeEstimator implements FeeEstimator {
|
|
34
|
+
readonly satsPerVbyte: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
|
|
38
|
+
*/
|
|
39
|
+
constructor(satsPerVbyte: number = 5) {
|
|
40
|
+
if(satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
|
|
41
|
+
throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
|
|
42
|
+
}
|
|
43
|
+
this.satsPerVbyte = satsPerVbyte;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async estimateFee(vsize: number): Promise<bigint> {
|
|
47
|
+
if(vsize < 0 || !Number.isFinite(vsize)) {
|
|
48
|
+
throw new Error(`Invalid vsize: ${vsize}`);
|
|
49
|
+
}
|
|
50
|
+
return BigInt(Math.ceil(vsize * this.satsPerVbyte));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -64,4 +64,13 @@ export interface BeaconSignal {
|
|
|
64
64
|
* Metadata about the block containing the Beacon Signal.
|
|
65
65
|
*/
|
|
66
66
|
blockMetadata: BlockMetadata;
|
|
67
|
-
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Callback for publishing a CAS Announcement to a content-addressed store.
|
|
71
|
+
* The method package defines this type; the api layer provides the implementation
|
|
72
|
+
* (e.g., via CasApi.publish backed by IPFS/Helia).
|
|
73
|
+
*
|
|
74
|
+
* @param announcement The CAS Announcement object (DID → update hash mapping).
|
|
75
|
+
*/
|
|
76
|
+
export type CasPublishFn = (announcement: Record<string, string>) => Promise<void>;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
2
|
import type { KeyBytes } from '@did-btcr2/common';
|
|
3
3
|
import { canonicalize, hash } from '@did-btcr2/common';
|
|
4
4
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
5
|
-
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
6
|
-
import { hexToBytes } from '@noble/hashes/utils';
|
|
7
|
-
import { opcodes, Psbt, script } from 'bitcoinjs-lib';
|
|
8
5
|
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
9
6
|
import type { SidecarData } from '../types.js';
|
|
7
|
+
import type { BroadcastOptions } from './beacon.js';
|
|
10
8
|
import { Beacon } from './beacon.js';
|
|
11
|
-
import { SingletonBeaconError } from './error.js';
|
|
12
9
|
import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
|
|
13
10
|
|
|
14
11
|
/**
|
|
@@ -64,84 +61,26 @@ export class SingletonBeacon extends Beacon {
|
|
|
64
61
|
}
|
|
65
62
|
/**
|
|
66
63
|
* Broadcasts a SingletonBeacon signal to the Bitcoin network.
|
|
64
|
+
*
|
|
65
|
+
* The signal bytes embedded in OP_RETURN are the SHA-256 canonical hash of the signed update.
|
|
66
|
+
* UTXO selection, PSBT construction, fee estimation, signing, and broadcast are delegated to
|
|
67
|
+
* {@link Beacon.buildSignAndBroadcast}.
|
|
68
|
+
*
|
|
67
69
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
68
70
|
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
69
71
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
72
|
+
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
70
73
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
71
|
-
* @throws {
|
|
74
|
+
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
72
75
|
*/
|
|
73
76
|
async broadcastSignal(
|
|
74
77
|
signedUpdate: SignedBTCR2Update,
|
|
75
78
|
secretKey: KeyBytes,
|
|
76
|
-
bitcoin: BitcoinConnection
|
|
79
|
+
bitcoin: BitcoinConnection,
|
|
80
|
+
options?: BroadcastOptions
|
|
77
81
|
): Promise<SignedBTCR2Update> {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Query the Bitcoin network for UTXOs associated with the bitcoinAddress
|
|
82
|
-
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
83
|
-
|
|
84
|
-
// If no utxos are found, throw an error indicating the address is unfunded.
|
|
85
|
-
if(!utxos.length) {
|
|
86
|
-
throw new SingletonBeaconError(
|
|
87
|
-
'No UTXOs found, please fund address!',
|
|
88
|
-
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Sort utxos by block height and take the most recent one
|
|
93
|
-
const utxo: AddressUtxo | undefined = utxos.sort(
|
|
94
|
-
(a, b) => b.status.block_height - a.status.block_height
|
|
95
|
-
).shift();
|
|
96
|
-
|
|
97
|
-
// If no utxos are found, throw an error.
|
|
98
|
-
if(!utxo) {
|
|
99
|
-
throw new SingletonBeaconError(
|
|
100
|
-
'Beacon bitcoin address unfunded or utxos unconfirmed.',
|
|
101
|
-
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Get the previous tx to the utxo being spent
|
|
106
|
-
const prevTx = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
107
|
-
|
|
108
|
-
// Canonicalize and hash the signed update for OP_RETURN output
|
|
109
|
-
const updateHash = hash(canonicalize(signedUpdate));
|
|
110
|
-
|
|
111
|
-
// Construct a spend transaction
|
|
112
|
-
const spendTx = new Psbt({ network: bitcoin.data })
|
|
113
|
-
// Spend tx contains the utxo as its input
|
|
114
|
-
.addInput({
|
|
115
|
-
hash : utxo.txid,
|
|
116
|
-
index : utxo.vout,
|
|
117
|
-
nonWitnessUtxo : hexToBytes(prevTx)
|
|
118
|
-
})
|
|
119
|
-
// Add a change output minus a fee of 500 sats
|
|
120
|
-
// TODO: calculate fee based on transaction vsize and current fee rates
|
|
121
|
-
.addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - BigInt(500) })
|
|
122
|
-
// Add an OP_RETURN output containing the update hash
|
|
123
|
-
.addOutput({ script: script.compile([opcodes.OP_RETURN, updateHash]), value: 0n });
|
|
124
|
-
|
|
125
|
-
// Construct a key pair and PSBT signer from the secret key
|
|
126
|
-
const keyPair = SchnorrKeyPair.fromSecret(secretKey);
|
|
127
|
-
const signer = {
|
|
128
|
-
publicKey : keyPair.publicKey.compressed,
|
|
129
|
-
sign : (hash: Uint8Array) => keyPair.secretKey.sign(hash, { scheme: 'ecdsa' }),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// Sign 0th input, finalize extract to hex in prep for broadcast
|
|
133
|
-
const signedTx = spendTx.signInput(0, signer)
|
|
134
|
-
.finalizeAllInputs()
|
|
135
|
-
.extractTransaction()
|
|
136
|
-
.toHex();
|
|
137
|
-
|
|
138
|
-
// Broadcast spendTx to the Bitcoin network.
|
|
139
|
-
const txid = await bitcoin.rest.transaction.send(signedTx);
|
|
140
|
-
|
|
141
|
-
// Log the txid of the broadcasted transaction
|
|
142
|
-
console.info(`Singleton Beacon Signal Broadcasted with txid: ${txid}`);
|
|
143
|
-
|
|
144
|
-
// Return the signed update
|
|
82
|
+
const signalBytes = hash(canonicalize(signedUpdate));
|
|
83
|
+
await this.buildSignAndBroadcast(signalBytes, secretKey, bitcoin, options);
|
|
145
84
|
return signedUpdate;
|
|
146
85
|
}
|
|
147
|
-
}
|
|
86
|
+
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
+
import { canonicalize } from '@did-btcr2/common';
|
|
2
3
|
import type { KeyBytes } from '@did-btcr2/common';
|
|
3
4
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
-
import
|
|
5
|
+
import { blockHash, BTCR2MerkleTree, didToIndex, hexToHash, verifySerializedProof } from '@did-btcr2/smt';
|
|
6
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
7
|
+
import type { BeaconProcessResult, DataNeed } from '../resolver.js';
|
|
5
8
|
import type { SidecarData } from '../types.js';
|
|
9
|
+
import type { BroadcastOptions } from './beacon.js';
|
|
6
10
|
import { Beacon } from './beacon.js';
|
|
7
11
|
import { SMTBeaconError } from './error.js';
|
|
8
|
-
import type { BeaconService, BeaconSignal } from './interfaces.js';
|
|
12
|
+
import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
|
-
* Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#smt-beacon |
|
|
15
|
+
* Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#smt-beacon | SMT Beacon}.
|
|
16
|
+
*
|
|
17
|
+
* An SMT (Sparse Merkle Tree) Beacon aggregates updates for multiple DIDs
|
|
18
|
+
* into a single Merkle root hash broadcast on-chain via OP_RETURN.
|
|
19
|
+
* During resolution, the SMT Proof from the sidecar is verified against the
|
|
20
|
+
* on-chain root, and the proof's updateId is used to retrieve the signed update.
|
|
21
|
+
*
|
|
12
22
|
* @class SMTBeacon
|
|
13
23
|
* @type {SMTBeacon}
|
|
14
24
|
* @extends {Beacon}
|
|
@@ -24,31 +34,119 @@ export class SMTBeacon extends Beacon {
|
|
|
24
34
|
|
|
25
35
|
/**
|
|
26
36
|
* Implements {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-smt-beacon | 7.2.e.1 Process SMT Beacon}.
|
|
37
|
+
*
|
|
38
|
+
* For each signal, the signalBytes contain the hex-encoded SMT root hash.
|
|
39
|
+
* This method looks up the SMT Proof from the sidecar by root hash,
|
|
40
|
+
* validates the Merkle inclusion proof, and retrieves the corresponding
|
|
41
|
+
* signed update using the proof's updateId.
|
|
42
|
+
*
|
|
27
43
|
* @param {Array<BeaconSignal>} signals The array of Beacon Signals to process.
|
|
28
44
|
* @param {SidecarData} sidecar The sidecar data associated with the SMT Beacon.
|
|
29
|
-
* @returns {BeaconProcessResult}
|
|
30
|
-
* @throws {SMTBeaconError} if
|
|
45
|
+
* @returns {BeaconProcessResult} Successfully resolved updates and any data needs.
|
|
46
|
+
* @throws {SMTBeaconError} if proof verification fails or proof is malformed.
|
|
31
47
|
*/
|
|
32
48
|
processSignals(
|
|
33
49
|
signals: Array<BeaconSignal>,
|
|
34
50
|
sidecar: SidecarData
|
|
35
51
|
): BeaconProcessResult {
|
|
36
|
-
|
|
52
|
+
const updates = new Array<[SignedBTCR2Update, BlockMetadata]>();
|
|
53
|
+
const needs = new Array<DataNeed>();
|
|
54
|
+
|
|
55
|
+
// Extract the DID from the beacon service id (strip the #fragment)
|
|
56
|
+
const did = this.service.id.split('#')[0];
|
|
57
|
+
|
|
58
|
+
for(const signal of signals) {
|
|
59
|
+
// Signal bytes are the hex-encoded SMT root hash; smtMap is keyed by proof.id (also hex)
|
|
60
|
+
const smtProof = sidecar.smtMap.get(signal.signalBytes);
|
|
61
|
+
|
|
62
|
+
if(!smtProof) {
|
|
63
|
+
// SMT Proof not available — emit a need
|
|
64
|
+
needs.push({
|
|
65
|
+
kind : 'NeedSMTProof',
|
|
66
|
+
smtRootHash : signal.signalBytes,
|
|
67
|
+
beaconServiceId : this.service.id
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Non-inclusion proof — no update for this DID in this epoch, skip
|
|
73
|
+
if(!smtProof.updateId) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Nonce is required for proof verification
|
|
78
|
+
if(!smtProof.nonce) {
|
|
79
|
+
throw new SMTBeaconError(
|
|
80
|
+
'SMT proof missing required nonce field.',
|
|
81
|
+
'INVALID_SMT_PROOF', { smtProof, did }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Verify Merkle inclusion: leaf = hash(hash(nonce) || updateId)
|
|
86
|
+
const index = didToIndex(did);
|
|
87
|
+
const candidateHash = blockHash(blockHash(hexToHash(smtProof.nonce)), hexToHash(smtProof.updateId));
|
|
88
|
+
const valid = verifySerializedProof(smtProof, index, candidateHash);
|
|
89
|
+
|
|
90
|
+
if(!valid) {
|
|
91
|
+
throw new SMTBeaconError(
|
|
92
|
+
'SMT proof verification failed.',
|
|
93
|
+
'INVALID_SMT_PROOF', { smtProof, did }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Look up the signed update in sidecar updateMap (keyed by hex canonical hash)
|
|
98
|
+
const signedUpdate = sidecar.updateMap.get(smtProof.updateId);
|
|
99
|
+
|
|
100
|
+
if(!signedUpdate) {
|
|
101
|
+
// Signed update not available — emit a need
|
|
102
|
+
needs.push({
|
|
103
|
+
kind : 'NeedSignedUpdate',
|
|
104
|
+
updateHash : smtProof.updateId,
|
|
105
|
+
beaconServiceId : this.service.id
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
updates.push([signedUpdate, signal.blockMetadata]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { updates, needs };
|
|
37
114
|
}
|
|
38
115
|
|
|
39
116
|
/**
|
|
40
|
-
*
|
|
117
|
+
* Broadcasts an SMT Beacon signal to the Bitcoin network.
|
|
118
|
+
*
|
|
119
|
+
* Builds a single-entry Sparse Merkle Tree from the signed update, then broadcasts the tree's
|
|
120
|
+
* root hash via OP_RETURN. For multi-party aggregation, use the {@link AggregationService}
|
|
121
|
+
* subsystem directly instead of this method. UTXO selection, PSBT construction, fee estimation,
|
|
122
|
+
* signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
123
|
+
*
|
|
41
124
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
42
125
|
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
43
126
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
127
|
+
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
128
|
+
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
129
|
+
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
46
130
|
*/
|
|
47
131
|
async broadcastSignal(
|
|
48
132
|
signedUpdate: SignedBTCR2Update,
|
|
49
133
|
secretKey: KeyBytes,
|
|
50
|
-
bitcoin: BitcoinConnection
|
|
134
|
+
bitcoin: BitcoinConnection,
|
|
135
|
+
options?: BroadcastOptions
|
|
51
136
|
): Promise<SignedBTCR2Update> {
|
|
52
|
-
|
|
137
|
+
// Extract the DID from the beacon service id (strip the #fragment)
|
|
138
|
+
const did = this.service.id.split('#')[0];
|
|
139
|
+
|
|
140
|
+
// Build a single-entry SMT from the signed update
|
|
141
|
+
const canonicalBytes = new TextEncoder().encode(canonicalize(signedUpdate));
|
|
142
|
+
const nonce = randomBytes(32);
|
|
143
|
+
const tree = new BTCR2MerkleTree();
|
|
144
|
+
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
145
|
+
tree.finalize();
|
|
146
|
+
|
|
147
|
+
// Root hash is the signal bytes for the OP_RETURN output
|
|
148
|
+
await this.buildSignAndBroadcast(tree.rootHash, secretKey, bitcoin, options);
|
|
149
|
+
|
|
150
|
+
return signedUpdate;
|
|
53
151
|
}
|
|
54
152
|
}
|