@did-btcr2/method 0.36.1 → 0.38.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/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +1149 -126
- package/dist/browser.mjs +1149 -126
- package/dist/cjs/index.js +1244 -208
- package/dist/esm/core/aggregation/beacon-strategy.js +19 -6
- package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -1
- package/dist/esm/core/aggregation/cohort.js +142 -33
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/conditions.js +27 -0
- package/dist/esm/core/aggregation/conditions.js.map +1 -1
- package/dist/esm/core/aggregation/fallback-spend.js +132 -0
- package/dist/esm/core/aggregation/fallback-spend.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +46 -1
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -1
- package/dist/esm/core/aggregation/messages/constants.js +11 -0
- package/dist/esm/core/aggregation/messages/constants.js.map +1 -1
- package/dist/esm/core/aggregation/messages/factories.js +27 -1
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/messages/guards.js +4 -1
- package/dist/esm/core/aggregation/messages/guards.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +210 -21
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/phases.js +6 -0
- package/dist/esm/core/aggregation/phases.js.map +1 -1
- package/dist/esm/core/aggregation/recovery-policy.js +156 -0
- package/dist/esm/core/aggregation/recovery-policy.js.map +1 -0
- package/dist/esm/core/aggregation/recovery-spend.js +106 -0
- package/dist/esm/core/aggregation/recovery-spend.js.map +1 -0
- package/dist/esm/core/aggregation/runner/aggregation-runner.js +12 -3
- package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +74 -22
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +104 -11
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +221 -11
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/core/aggregation/signing-session.js +3 -3
- package/dist/esm/core/aggregation/signing-session.js.map +1 -1
- package/dist/esm/core/aggregation/transport/http/envelope.js +3 -3
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +1 -1
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js +1 -1
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js +1 -1
- package/dist/esm/core/aggregation/transport/http/request-auth.js +1 -1
- package/dist/esm/core/aggregation/transport/http/server.js +1 -1
- package/dist/esm/core/aggregation/transport/http/sse-stream.js +1 -1
- package/dist/esm/core/aggregation/transport/http/sse-writer.js +1 -1
- package/dist/esm/core/aggregation/transport/in-memory.js +1 -1
- package/dist/esm/core/aggregation/transport/nostr.js +6 -6
- package/dist/esm/core/beacon/beacon.js +116 -20
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +6 -6
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/fee-estimator.js +8 -0
- package/dist/esm/core/beacon/fee-estimator.js.map +1 -1
- package/dist/esm/core/beacon/singleton-beacon.js +2 -2
- 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/resolver.js +8 -8
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/updater.js +6 -6
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +2 -2
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.d.ts +8 -4
- package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -1
- package/dist/types/core/aggregation/cohort.d.ts +88 -17
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/conditions.d.ts +28 -0
- package/dist/types/core/aggregation/conditions.d.ts.map +1 -1
- package/dist/types/core/aggregation/fallback-spend.d.ts +94 -0
- package/dist/types/core/aggregation/fallback-spend.d.ts.map +1 -0
- package/dist/types/core/aggregation/logger.d.ts +1 -1
- package/dist/types/core/aggregation/messages/base.d.ts +6 -0
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +50 -2
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/constants.d.ts +8 -0
- package/dist/types/core/aggregation/messages/constants.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/factories.d.ts +40 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/guards.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +41 -7
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/phases.d.ts +8 -2
- package/dist/types/core/aggregation/phases.d.ts.map +1 -1
- package/dist/types/core/aggregation/recovery-policy.d.ts +132 -0
- package/dist/types/core/aggregation/recovery-policy.d.ts.map +1 -0
- package/dist/types/core/aggregation/recovery-spend.d.ts +94 -0
- package/dist/types/core/aggregation/recovery-spend.d.ts.map +1 -0
- package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +19 -3
- package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +28 -7
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +3 -3
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +47 -5
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +26 -2
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/core/aggregation/signing-session.d.ts +2 -2
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/http/envelope.d.ts +3 -3
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +1 -1
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +1 -1
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +1 -1
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts +1 -1
- package/dist/types/core/aggregation/transport/http/server.d.ts +1 -1
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +2 -2
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +1 -1
- package/dist/types/core/aggregation/transport/in-memory.d.ts +1 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts +4 -4
- package/dist/types/core/aggregation/transport/transport.d.ts +4 -4
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +66 -10
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +2 -2
- package/dist/types/core/beacon/fee-estimator.d.ts +8 -0
- package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -1
- package/dist/types/core/beacon/interfaces.d.ts +1 -1
- package/dist/types/core/resolver.d.ts +3 -3
- package/dist/types/core/updater.d.ts +4 -4
- package/dist/types/did-btcr2.d.ts +2 -2
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/aggregation/beacon-strategy.ts +27 -9
- package/src/core/aggregation/cohort.ts +171 -35
- package/src/core/aggregation/conditions.ts +56 -0
- package/src/core/aggregation/fallback-spend.ts +223 -0
- package/src/core/aggregation/logger.ts +1 -1
- package/src/core/aggregation/messages/base.ts +6 -0
- package/src/core/aggregation/messages/bodies.ts +94 -1
- package/src/core/aggregation/messages/constants.ts +12 -0
- package/src/core/aggregation/messages/factories.ts +57 -0
- package/src/core/aggregation/messages/guards.ts +6 -0
- package/src/core/aggregation/participant.ts +255 -23
- package/src/core/aggregation/phases.ts +9 -0
- package/src/core/aggregation/recovery-policy.ts +237 -0
- package/src/core/aggregation/recovery-spend.ts +192 -0
- package/src/core/aggregation/runner/aggregation-runner.ts +34 -5
- package/src/core/aggregation/runner/events.ts +26 -7
- package/src/core/aggregation/runner/participant-runner.ts +75 -23
- package/src/core/aggregation/runner/service-runner.ts +144 -12
- package/src/core/aggregation/service.ts +260 -14
- package/src/core/aggregation/signing-session.ts +3 -3
- package/src/core/aggregation/transport/http/envelope.ts +3 -3
- package/src/core/aggregation/transport/http/inbox-buffer.ts +1 -1
- package/src/core/aggregation/transport/http/nonce-cache.ts +1 -1
- package/src/core/aggregation/transport/http/rate-limiter.ts +1 -1
- package/src/core/aggregation/transport/http/request-auth.ts +1 -1
- package/src/core/aggregation/transport/http/server.ts +1 -1
- package/src/core/aggregation/transport/http/sse-stream.ts +2 -2
- package/src/core/aggregation/transport/http/sse-writer.ts +1 -1
- package/src/core/aggregation/transport/in-memory.ts +1 -1
- package/src/core/aggregation/transport/nostr.ts +6 -6
- package/src/core/aggregation/transport/transport.ts +4 -4
- package/src/core/beacon/beacon.ts +149 -25
- package/src/core/beacon/cas-beacon.ts +7 -7
- package/src/core/beacon/fee-estimator.ts +9 -0
- package/src/core/beacon/interfaces.ts +1 -1
- package/src/core/beacon/singleton-beacon.ts +2 -2
- package/src/core/beacon/smt-beacon.ts +3 -3
- package/src/core/resolver.ts +8 -8
- package/src/core/updater.ts +6 -6
- package/src/did-btcr2.ts +2 -2
- package/src/index.ts +3 -0
package/dist/cjs/index.js
CHANGED
|
@@ -55,6 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
BeaconUtils: () => BeaconUtils,
|
|
56
56
|
CASBeacon: () => CASBeacon,
|
|
57
57
|
CASBeaconError: () => CASBeaconError,
|
|
58
|
+
CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
|
|
58
59
|
COHORT_ADVERT: () => COHORT_ADVERT,
|
|
59
60
|
COHORT_OPT_IN: () => COHORT_OPT_IN,
|
|
60
61
|
COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
|
|
@@ -63,17 +64,23 @@ __export(index_exports, {
|
|
|
63
64
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
64
65
|
DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
65
66
|
DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
|
|
67
|
+
DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
|
|
68
|
+
DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
|
|
66
69
|
DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
67
70
|
DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
|
|
68
71
|
DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
|
|
72
|
+
DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
|
|
69
73
|
DID_REGEX: () => DID_REGEX,
|
|
70
74
|
DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
|
|
75
|
+
DUST_LIMIT_SATS: () => DUST_LIMIT_SATS2,
|
|
71
76
|
DidBtcr2: () => DidBtcr2,
|
|
72
77
|
DidCommTransport: () => DidCommTransport,
|
|
73
78
|
DidDocument: () => DidDocument,
|
|
74
79
|
DidDocumentBuilder: () => DidDocumentBuilder,
|
|
75
80
|
DidVerificationMethod: () => DidVerificationMethod,
|
|
76
81
|
Document: () => Document,
|
|
82
|
+
FALLBACK_AUTHORIZATION_REQUEST: () => FALLBACK_AUTHORIZATION_REQUEST,
|
|
83
|
+
FALLBACK_SIGNATURE: () => FALLBACK_SIGNATURE,
|
|
77
84
|
GenesisDocument: () => GenesisDocument,
|
|
78
85
|
HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
|
|
79
86
|
HTTP_ROUTE: () => HTTP_ROUTE,
|
|
@@ -87,7 +94,9 @@ __export(index_exports, {
|
|
|
87
94
|
InMemoryTransport: () => InMemoryTransport,
|
|
88
95
|
InboxBuffer: () => InboxBuffer,
|
|
89
96
|
KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
|
|
97
|
+
KNOWN_FUNDING_MODELS: () => KNOWN_FUNDING_MODELS,
|
|
90
98
|
KeyPairAggregationSigner: () => KeyPairAggregationSigner,
|
|
99
|
+
MAX_RECOVERY_SEQUENCE: () => MAX_RECOVERY_SEQUENCE,
|
|
91
100
|
NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
|
|
92
101
|
NonceCache: () => NonceCache,
|
|
93
102
|
NostrTransport: () => NostrTransport,
|
|
@@ -104,6 +113,7 @@ __export(index_exports, {
|
|
|
104
113
|
SMTBeacon: () => SMTBeacon,
|
|
105
114
|
SMTBeaconError: () => SMTBeaconError,
|
|
106
115
|
SSE_EVENT: () => SSE_EVENT,
|
|
116
|
+
SUBMIT_NONINCLUDED: () => SUBMIT_NONINCLUDED,
|
|
107
117
|
SUBMIT_UPDATE: () => SUBMIT_UPDATE,
|
|
108
118
|
ServiceCohortPhase: () => ServiceCohortPhase,
|
|
109
119
|
SigningSessionError: () => SigningSessionError,
|
|
@@ -112,13 +122,20 @@ __export(index_exports, {
|
|
|
112
122
|
SingletonBeacon: () => SingletonBeacon,
|
|
113
123
|
SingletonBeaconError: () => SingletonBeaconError,
|
|
114
124
|
StaticFeeEstimator: () => StaticFeeEstimator,
|
|
125
|
+
TAPROOT_LEAF_VERSION: () => TAPROOT_LEAF_VERSION,
|
|
115
126
|
TransportAdapterError: () => TransportAdapterError,
|
|
116
127
|
TransportError: () => TransportError,
|
|
117
128
|
TransportFactory: () => TransportFactory,
|
|
118
129
|
TypedEventEmitter: () => TypedEventEmitter,
|
|
119
130
|
Updater: () => Updater,
|
|
120
131
|
VALIDATION_ACK: () => VALIDATION_ACK,
|
|
132
|
+
beaconTxVsize: () => beaconTxVsize,
|
|
121
133
|
buildAggregationBeaconTx: () => buildAggregationBeaconTx,
|
|
134
|
+
buildFallbackLeaf: () => buildFallbackLeaf,
|
|
135
|
+
buildFallbackSpend: () => buildFallbackSpend,
|
|
136
|
+
buildRecoveryLeaves: () => buildRecoveryLeaves,
|
|
137
|
+
buildRecoveryScript: () => buildRecoveryScript,
|
|
138
|
+
buildRecoverySpend: () => buildRecoverySpend,
|
|
122
139
|
buildRequestAuth: () => buildRequestAuth,
|
|
123
140
|
createAggregatedNonceMessage: () => createAggregatedNonceMessage,
|
|
124
141
|
createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
|
|
@@ -127,13 +144,17 @@ __export(index_exports, {
|
|
|
127
144
|
createCohortOptInMessage: () => createCohortOptInMessage,
|
|
128
145
|
createCohortReadyMessage: () => createCohortReadyMessage,
|
|
129
146
|
createDistributeAggregatedDataMessage: () => createDistributeAggregatedDataMessage,
|
|
147
|
+
createFallbackAuthorizationRequestMessage: () => createFallbackAuthorizationRequestMessage,
|
|
148
|
+
createFallbackSignatureMessage: () => createFallbackSignatureMessage,
|
|
130
149
|
createNonceContributionMessage: () => createNonceContributionMessage,
|
|
131
150
|
createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
|
|
151
|
+
createSubmitNonIncludedMessage: () => createSubmitNonIncludedMessage,
|
|
132
152
|
createSubmitUpdateMessage: () => createSubmitUpdateMessage,
|
|
133
153
|
createValidationAckMessage: () => createValidationAckMessage,
|
|
134
154
|
defaultReconnectBackoff: () => defaultReconnectBackoff,
|
|
135
155
|
deriveSingletonAddress: () => deriveSingletonAddress,
|
|
136
156
|
detectSingletonScriptKind: () => detectSingletonScriptKind,
|
|
157
|
+
fallbackSighash: () => fallbackSighash,
|
|
137
158
|
formatSseComment: () => formatSseComment,
|
|
138
159
|
formatSseEvent: () => formatSseEvent,
|
|
139
160
|
getBeaconStrategy: () => getBeaconStrategy,
|
|
@@ -145,10 +166,13 @@ __export(index_exports, {
|
|
|
145
166
|
isCohortOptInMessage: () => isCohortOptInMessage,
|
|
146
167
|
isCohortReadyMessage: () => isCohortReadyMessage,
|
|
147
168
|
isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
|
|
169
|
+
isFallbackAuthorizationRequestMessage: () => isFallbackAuthorizationRequestMessage,
|
|
170
|
+
isFallbackSignatureMessage: () => isFallbackSignatureMessage,
|
|
148
171
|
isKeygenMessageType: () => isKeygenMessageType,
|
|
149
172
|
isNonceContributionMessage: () => isNonceContributionMessage,
|
|
150
173
|
isSignMessageType: () => isSignMessageType,
|
|
151
174
|
isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
|
|
175
|
+
isSubmitNonIncludedMessage: () => isSubmitNonIncludedMessage,
|
|
152
176
|
isSubmitUpdateMessage: () => isSubmitUpdateMessage,
|
|
153
177
|
isUpdateMessageType: () => isUpdateMessageType,
|
|
154
178
|
isValidationAckMessage: () => isValidationAckMessage,
|
|
@@ -157,8 +181,11 @@ __export(index_exports, {
|
|
|
157
181
|
parseRequestAuth: () => parseRequestAuth,
|
|
158
182
|
parseSseStream: () => parseSseStream,
|
|
159
183
|
registerBeaconStrategy: () => registerBeaconStrategy,
|
|
184
|
+
resolveChangeAddress: () => resolveChangeAddress,
|
|
185
|
+
resolveFallbackThreshold: () => resolveFallbackThreshold,
|
|
160
186
|
reviveFromWire: () => reviveFromWire,
|
|
161
187
|
signEnvelope: () => signEnvelope,
|
|
188
|
+
tapLeafHash: () => tapLeafHash,
|
|
162
189
|
validateCohortConditions: () => validateCohortConditions,
|
|
163
190
|
verifyEnvelope: () => verifyEnvelope,
|
|
164
191
|
verifyRequestAuth: () => verifyRequestAuth
|
|
@@ -168,7 +195,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
168
195
|
// src/core/aggregation/service.ts
|
|
169
196
|
var import_common4 = require("@did-btcr2/common");
|
|
170
197
|
var import_cryptosuite = require("@did-btcr2/cryptosuite");
|
|
171
|
-
var
|
|
198
|
+
var import_secp256k14 = require("@noble/curves/secp256k1.js");
|
|
199
|
+
var import_utils3 = require("@noble/hashes/utils");
|
|
172
200
|
|
|
173
201
|
// src/core/aggregation/beacon-strategy.ts
|
|
174
202
|
var import_common = require("@did-btcr2/common");
|
|
@@ -181,9 +209,12 @@ var CAS_STRATEGY = {
|
|
|
181
209
|
getDistributePayload(cohort) {
|
|
182
210
|
return { casAnnouncement: cohort.casAnnouncement };
|
|
183
211
|
},
|
|
184
|
-
validateParticipantView({ participantDid, expectedHash, body }) {
|
|
212
|
+
validateParticipantView({ participantDid, included, expectedHash, body }) {
|
|
185
213
|
const casAnnouncement = body.casAnnouncement;
|
|
186
214
|
if (!casAnnouncement) return { matches: false };
|
|
215
|
+
if (!included) {
|
|
216
|
+
return { matches: !(participantDid in casAnnouncement), casAnnouncement };
|
|
217
|
+
}
|
|
187
218
|
return {
|
|
188
219
|
matches: casAnnouncement[participantDid] === expectedHash,
|
|
189
220
|
casAnnouncement
|
|
@@ -199,15 +230,20 @@ var SMT_STRATEGY = {
|
|
|
199
230
|
const proof = cohort.smtProofs?.get(participantDid);
|
|
200
231
|
return { smtProof: proof };
|
|
201
232
|
},
|
|
202
|
-
validateParticipantView({ participantDid, submittedUpdate, body }) {
|
|
233
|
+
validateParticipantView({ participantDid, included, submittedUpdate, body }) {
|
|
203
234
|
const smtProof = body.smtProof;
|
|
204
|
-
|
|
235
|
+
const index = (0, import_smt.didToIndex)(participantDid);
|
|
236
|
+
if (!included) {
|
|
237
|
+
if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
|
|
238
|
+
const candidateHash2 = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)));
|
|
239
|
+
return { matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash2), smtProof };
|
|
240
|
+
}
|
|
241
|
+
if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
|
|
205
242
|
const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
|
|
206
243
|
const expectedUpdateId = (0, import_smt.hashToBase64Url)((0, import_smt.blockHash)(canonicalBytes));
|
|
207
244
|
if (smtProof.updateId !== expectedUpdateId) {
|
|
208
245
|
return { matches: false, smtProof };
|
|
209
246
|
}
|
|
210
|
-
const index = (0, import_smt.didToIndex)(participantDid);
|
|
211
247
|
const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)), (0, import_smt.base64UrlToHash)(smtProof.updateId));
|
|
212
248
|
return {
|
|
213
249
|
matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
|
|
@@ -230,10 +266,10 @@ function getBeaconStrategy(type) {
|
|
|
230
266
|
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
231
267
|
var import_common3 = require("@did-btcr2/common");
|
|
232
268
|
var import_smt2 = require("@did-btcr2/smt");
|
|
233
|
-
var
|
|
234
|
-
var
|
|
235
|
-
var
|
|
236
|
-
var
|
|
269
|
+
var import_secp256k12 = require("@noble/curves/secp256k1.js");
|
|
270
|
+
var import_utils2 = require("@noble/hashes/utils");
|
|
271
|
+
var import_btc_signer2 = require("@scure/btc-signer");
|
|
272
|
+
var import_musig22 = require("@scure/btc-signer/musig2");
|
|
237
273
|
|
|
238
274
|
// src/core/aggregation/errors.ts
|
|
239
275
|
var import_common2 = require("@did-btcr2/common");
|
|
@@ -258,6 +294,105 @@ var SigningSessionError = class extends import_common2.MethodError {
|
|
|
258
294
|
}
|
|
259
295
|
};
|
|
260
296
|
|
|
297
|
+
// src/core/aggregation/recovery-policy.ts
|
|
298
|
+
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
299
|
+
var import_utils = require("@noble/hashes/utils");
|
|
300
|
+
var import_btc_signer = require("@scure/btc-signer");
|
|
301
|
+
var import_musig2 = require("@scure/btc-signer/musig2");
|
|
302
|
+
var DEFAULT_FUNDING_MODEL = "operator-funded";
|
|
303
|
+
var DEFAULT_RECOVERY_SEQUENCE = 144;
|
|
304
|
+
var MAX_RECOVERY_SEQUENCE = 65535;
|
|
305
|
+
var TAPROOT_LEAF_VERSION = 192;
|
|
306
|
+
function compactSize(n) {
|
|
307
|
+
if (n < 253) return new Uint8Array([n]);
|
|
308
|
+
if (n <= 65535) return new Uint8Array([253, n & 255, n >> 8 & 255]);
|
|
309
|
+
return new Uint8Array([254, n & 255, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255]);
|
|
310
|
+
}
|
|
311
|
+
function tapLeafHash(script, leafVersion = TAPROOT_LEAF_VERSION) {
|
|
312
|
+
return import_secp256k1.schnorr.utils.taggedHash(
|
|
313
|
+
"TapLeaf",
|
|
314
|
+
(0, import_utils.concatBytes)(new Uint8Array([leafVersion]), compactSize(script.length), script)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
function resolveFallbackThreshold(advertised, n) {
|
|
318
|
+
return advertised ?? Math.max(1, n - 1);
|
|
319
|
+
}
|
|
320
|
+
function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
|
|
321
|
+
const n = cohortKeys.length;
|
|
322
|
+
if (n === 0) {
|
|
323
|
+
throw new AggregationCohortError(
|
|
324
|
+
"Cannot build fallback leaf: no cohort keys.",
|
|
325
|
+
"NO_COHORT_KEYS"
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
for (const key of cohortKeys) {
|
|
329
|
+
if (key.length !== 33) {
|
|
330
|
+
throw new AggregationCohortError(
|
|
331
|
+
`Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
|
|
332
|
+
"INVALID_COHORT_KEY",
|
|
333
|
+
{ length: key.length }
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n) {
|
|
338
|
+
throw new AggregationCohortError(
|
|
339
|
+
`Fallback threshold must be an integer in [1, ${n}] (k-of-n), got ${fallbackThreshold}.`,
|
|
340
|
+
"INVALID_FALLBACK_THRESHOLD",
|
|
341
|
+
{ fallbackThreshold, n }
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function buildFallbackLeaf(params) {
|
|
346
|
+
assertFallbackParams(params);
|
|
347
|
+
const xOnlyKeys = (0, import_musig2.sortKeys)(params.cohortKeys).map((k) => k.slice(1));
|
|
348
|
+
return (0, import_btc_signer.p2tr_ms)(params.fallbackThreshold, xOnlyKeys).script;
|
|
349
|
+
}
|
|
350
|
+
function assertRecoveryParams({ recoveryKey, recoverySequence }) {
|
|
351
|
+
if (recoveryKey.length !== 32) {
|
|
352
|
+
throw new AggregationCohortError(
|
|
353
|
+
`Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
|
|
354
|
+
"INVALID_RECOVERY_KEY"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
358
|
+
throw new AggregationCohortError(
|
|
359
|
+
`Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
|
|
360
|
+
"INVALID_RECOVERY_SEQUENCE"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function buildRecoveryScript(params) {
|
|
365
|
+
assertRecoveryParams(params);
|
|
366
|
+
return import_btc_signer.Script.encode([
|
|
367
|
+
params.recoverySequence,
|
|
368
|
+
"CHECKSEQUENCEVERIFY",
|
|
369
|
+
"DROP",
|
|
370
|
+
params.recoveryKey,
|
|
371
|
+
"CHECKSIG"
|
|
372
|
+
]);
|
|
373
|
+
}
|
|
374
|
+
function buildRecoveryLeaves(fundingModel, params) {
|
|
375
|
+
switch (fundingModel) {
|
|
376
|
+
case "operator-funded":
|
|
377
|
+
return [
|
|
378
|
+
{ script: buildFallbackLeaf(params) },
|
|
379
|
+
{ script: buildRecoveryScript(params) }
|
|
380
|
+
];
|
|
381
|
+
case "participant-funded":
|
|
382
|
+
throw new AggregationCohortError(
|
|
383
|
+
"Funding model 'participant-funded' is reserved and not yet implemented.",
|
|
384
|
+
"UNSUPPORTED_FUNDING_MODEL",
|
|
385
|
+
{ fundingModel }
|
|
386
|
+
);
|
|
387
|
+
default:
|
|
388
|
+
throw new AggregationCohortError(
|
|
389
|
+
`Unknown funding model: ${fundingModel}.`,
|
|
390
|
+
"UNKNOWN_FUNDING_MODEL",
|
|
391
|
+
{ fundingModel }
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
261
396
|
// src/core/aggregation/cohort.ts
|
|
262
397
|
var AggregationCohort = class {
|
|
263
398
|
/** Unique identifier for the cohort. */
|
|
@@ -273,8 +408,8 @@ var AggregationCohort = class {
|
|
|
273
408
|
/** List of participant DIDs that have been accepted into the cohort. */
|
|
274
409
|
participants = [];
|
|
275
410
|
/**
|
|
276
|
-
* Mapping from participant DID
|
|
277
|
-
* Distinct from {@link cohortKeys} (which is sorted per BIP-327)
|
|
411
|
+
* Mapping from participant DID to their compressed secp256k1 public key.
|
|
412
|
+
* Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
|
|
278
413
|
* callers look up a participant's key without knowing their position in the
|
|
279
414
|
* sorted array. Populated by the service at `acceptParticipant` time.
|
|
280
415
|
*/
|
|
@@ -282,16 +417,41 @@ var AggregationCohort = class {
|
|
|
282
417
|
/** Sorted list of cohort participants' compressed public keys. */
|
|
283
418
|
#cohortKeys = [];
|
|
284
419
|
/**
|
|
285
|
-
* BIP-341 TapTweak
|
|
286
|
-
*
|
|
287
|
-
*
|
|
420
|
+
* BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
|
|
421
|
+
* The beacon output is an internal key (the MuSig2 aggregate) plus a script
|
|
422
|
+
* tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
|
|
423
|
+
* The MuSig2 signing session applies this as an x-only tweak; a value that does
|
|
424
|
+
* not match the root the funded address was derived from silently yields an
|
|
425
|
+
* invalid key-path signature.
|
|
288
426
|
*/
|
|
289
427
|
tapTweak = new Uint8Array();
|
|
290
|
-
/** The n-of-n MuSig2
|
|
428
|
+
/** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
|
|
429
|
+
internalKey = new Uint8Array();
|
|
430
|
+
/** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
|
|
431
|
+
tapMerkleRoot = new Uint8Array();
|
|
432
|
+
/** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
|
|
433
|
+
recoveryKey;
|
|
434
|
+
/** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
|
|
435
|
+
recoverySequence;
|
|
436
|
+
/** Funding model governing the recovery leaves (default 'operator-funded'). */
|
|
437
|
+
fundingModel;
|
|
438
|
+
/**
|
|
439
|
+
* Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
|
|
440
|
+
* address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
|
|
441
|
+
*/
|
|
442
|
+
fallbackThreshold;
|
|
443
|
+
/** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
|
|
291
444
|
beaconAddress = "";
|
|
292
445
|
/** Pending DID updates submitted by participants, keyed by DID. */
|
|
293
446
|
pendingUpdates = /* @__PURE__ */ new Map();
|
|
294
|
-
/**
|
|
447
|
+
/**
|
|
448
|
+
* Participant DIDs that explicitly declined to submit an update this round
|
|
449
|
+
* (cooperative non-inclusion). A decliner is absent from the CAS Announcement
|
|
450
|
+
* Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
|
|
451
|
+
* disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
|
|
452
|
+
*/
|
|
453
|
+
nonIncluded = /* @__PURE__ */ new Set();
|
|
454
|
+
/** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
|
|
295
455
|
casAnnouncement;
|
|
296
456
|
/** Per-participant SMT proofs, set by buildSMTTree(). */
|
|
297
457
|
smtProofs;
|
|
@@ -301,23 +461,45 @@ var AggregationCohort = class {
|
|
|
301
461
|
validationAcks = /* @__PURE__ */ new Set();
|
|
302
462
|
/** Set of participant DIDs that have rejected the aggregated data. */
|
|
303
463
|
validationRejections = /* @__PURE__ */ new Set();
|
|
304
|
-
constructor({ id, minParticipants, serviceDid, network, beaconType }) {
|
|
464
|
+
constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
|
|
305
465
|
this.id = id || crypto.randomUUID();
|
|
306
466
|
this.minParticipants = minParticipants ?? 2;
|
|
307
467
|
this.serviceDid = serviceDid || "";
|
|
308
468
|
this.network = network;
|
|
309
469
|
this.beaconType = beaconType || "CASBeacon";
|
|
470
|
+
this.recoveryKey = recoveryKey;
|
|
471
|
+
this.recoverySequence = recoverySequence;
|
|
472
|
+
this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
|
|
473
|
+
this.fallbackThreshold = fallbackThreshold;
|
|
310
474
|
}
|
|
311
475
|
/** Sorted cohort keys (sorted on assignment per BIP-327). */
|
|
312
476
|
get cohortKeys() {
|
|
313
477
|
return this.#cohortKeys;
|
|
314
478
|
}
|
|
315
479
|
set cohortKeys(keys) {
|
|
316
|
-
this.#cohortKeys = (0,
|
|
480
|
+
this.#cohortKeys = (0, import_musig22.sortKeys)(keys);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* The resolved k of the k-of-n fallback leaf: the advertised
|
|
484
|
+
* {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
|
|
485
|
+
* the current cohort size. This is the value the beacon address commits to and
|
|
486
|
+
* the spend builders must reproduce. Returns 0 before any cohort keys are set.
|
|
487
|
+
*/
|
|
488
|
+
get effectiveFallbackThreshold() {
|
|
489
|
+
if (this.#cohortKeys.length === 0) return 0;
|
|
490
|
+
return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
|
|
317
491
|
}
|
|
318
492
|
/**
|
|
319
|
-
* Computes the
|
|
320
|
-
*
|
|
493
|
+
* Computes the Taproot beacon address from the cohort keys and recovery params.
|
|
494
|
+
*
|
|
495
|
+
* The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
|
|
496
|
+
* script path is the recovery tree (a CSV recovery leaf so a missing signer
|
|
497
|
+
* cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
|
|
498
|
+
* the `tapTweak` the MuSig2 session must apply for the key-path spend.
|
|
499
|
+
*
|
|
500
|
+
* The tweak is derived from the Merkle root the address was built with (read
|
|
501
|
+
* back from the payment), never recomputed by hand, so the MuSig2 key-path
|
|
502
|
+
* signature is guaranteed to validate against the funded address.
|
|
321
503
|
*/
|
|
322
504
|
computeBeaconAddress() {
|
|
323
505
|
if (this.#cohortKeys.length === 0) {
|
|
@@ -327,10 +509,25 @@ var AggregationCohort = class {
|
|
|
327
509
|
{ cohortId: this.id }
|
|
328
510
|
);
|
|
329
511
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
512
|
+
if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
|
|
513
|
+
throw new AggregationCohortError(
|
|
514
|
+
"Cannot compute beacon address: missing recovery key or sequence.",
|
|
515
|
+
"NO_RECOVERY_PARAMS",
|
|
516
|
+
{ cohortId: this.id }
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
const keyAggContext = (0, import_musig22.keyAggregate)(this.#cohortKeys);
|
|
520
|
+
const aggPubkey = (0, import_musig22.keyAggExport)(keyAggContext);
|
|
521
|
+
const leaves = buildRecoveryLeaves(this.fundingModel, {
|
|
522
|
+
recoveryKey: this.recoveryKey,
|
|
523
|
+
recoverySequence: this.recoverySequence,
|
|
524
|
+
cohortKeys: this.#cohortKeys,
|
|
525
|
+
fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
|
|
526
|
+
});
|
|
527
|
+
const payment = (0, import_btc_signer2.p2tr)(aggPubkey, leaves, (0, import_bitcoin.getNetwork)(this.network), true);
|
|
528
|
+
this.internalKey = aggPubkey;
|
|
529
|
+
this.tapMerkleRoot = payment.tapMerkleRoot;
|
|
530
|
+
this.tapTweak = import_secp256k12.schnorr.utils.taggedHash("TapTweak", (0, import_utils2.concatBytes)(aggPubkey, payment.tapMerkleRoot));
|
|
334
531
|
if (!payment.address) {
|
|
335
532
|
throw new AggregationCohortError(
|
|
336
533
|
"Failed to compute Taproot address",
|
|
@@ -354,7 +551,7 @@ var AggregationCohort = class {
|
|
|
354
551
|
{ cohortId: this.id, participantPkHex }
|
|
355
552
|
);
|
|
356
553
|
}
|
|
357
|
-
this.cohortKeys = cohortKeysHex.map((k) => (0,
|
|
554
|
+
this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils2.hexToBytes)(k));
|
|
358
555
|
const computed = this.computeBeaconAddress();
|
|
359
556
|
if (computed !== expectedBeaconAddress) {
|
|
360
557
|
throw new AggregationCohortError(
|
|
@@ -386,20 +583,54 @@ var AggregationCohort = class {
|
|
|
386
583
|
}
|
|
387
584
|
this.pendingUpdates.set(participantDid, signedUpdate);
|
|
388
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* Record that a participant declined to submit an update this round
|
|
588
|
+
* (cooperative non-inclusion). The member stays in the cohort and still signs.
|
|
589
|
+
*/
|
|
590
|
+
addNonInclusion(participantDid) {
|
|
591
|
+
if (!this.participants.includes(participantDid)) {
|
|
592
|
+
throw new AggregationCohortError(
|
|
593
|
+
`Participant ${participantDid} is not in cohort ${this.id}.`,
|
|
594
|
+
"UNKNOWN_PARTICIPANT",
|
|
595
|
+
{ cohortId: this.id, participantDid }
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
if (this.pendingUpdates.has(participantDid)) {
|
|
599
|
+
throw new AggregationCohortError(
|
|
600
|
+
`Participant ${participantDid} already submitted an update; cannot also decline.`,
|
|
601
|
+
"CONFLICTING_RESPONSE",
|
|
602
|
+
{ cohortId: this.id, participantDid }
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
this.nonIncluded.add(participantDid);
|
|
606
|
+
}
|
|
389
607
|
hasAllUpdates() {
|
|
390
608
|
return this.pendingUpdates.size === this.participants.length;
|
|
391
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* True when every participant has responded for this round, either with an
|
|
612
|
+
* update or with an explicit non-inclusion. This is the aggregation gate when
|
|
613
|
+
* non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
|
|
614
|
+
* {@link hasAllValidationResponses} generalizes a unanimous ack.
|
|
615
|
+
*/
|
|
616
|
+
hasAllResponses() {
|
|
617
|
+
return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
|
|
618
|
+
}
|
|
392
619
|
/**
|
|
393
620
|
* Builds a CAS Announcement Map from collected updates.
|
|
394
|
-
* Maps each participant DID
|
|
621
|
+
* Maps each participant DID to base64url canonical hash of their signed update.
|
|
395
622
|
* Computes signal bytes as SHA-256 of canonicalized announcement.
|
|
623
|
+
*
|
|
624
|
+
* Members who declined (cooperative non-inclusion) are naturally absent from
|
|
625
|
+
* the map: the body iterates {@link pendingUpdates}, which never holds a
|
|
626
|
+
* decliner. Absence from the map is exactly the CAS non-inclusion signal.
|
|
396
627
|
*/
|
|
397
628
|
buildCASAnnouncement() {
|
|
398
|
-
if (!this.
|
|
629
|
+
if (!this.hasAllResponses()) {
|
|
399
630
|
throw new AggregationCohortError(
|
|
400
|
-
"Cannot build CAS Announcement: not all
|
|
401
|
-
"
|
|
402
|
-
{ cohortId: this.id,
|
|
631
|
+
"Cannot build CAS Announcement: not all participants have responded.",
|
|
632
|
+
"INCOMPLETE_RESPONSES",
|
|
633
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
403
634
|
);
|
|
404
635
|
}
|
|
405
636
|
const announcement = {};
|
|
@@ -411,31 +642,41 @@ var AggregationCohort = class {
|
|
|
411
642
|
return announcement;
|
|
412
643
|
}
|
|
413
644
|
/**
|
|
414
|
-
* Builds an SMT tree
|
|
415
|
-
*
|
|
416
|
-
*
|
|
645
|
+
* Builds an SMT tree with one leaf per participant.
|
|
646
|
+
*
|
|
647
|
+
* A member who submitted an update gets an inclusion leaf
|
|
648
|
+
* (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
|
|
649
|
+
* non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
|
|
650
|
+
* omitted). The cohort mints each member's nonce and returns it inside that
|
|
651
|
+
* member's serialized proof, so a decliner can self-validate its own
|
|
652
|
+
* non-inclusion slot and the resolver can recompute the leaf. Stores
|
|
653
|
+
* per-participant proofs and the SMT root as signalBytes.
|
|
417
654
|
*/
|
|
418
655
|
buildSMTTree() {
|
|
419
|
-
if (!this.
|
|
656
|
+
if (!this.hasAllResponses()) {
|
|
420
657
|
throw new AggregationCohortError(
|
|
421
|
-
"Cannot build SMT tree: not all
|
|
422
|
-
"
|
|
423
|
-
{ cohortId: this.id }
|
|
658
|
+
"Cannot build SMT tree: not all participants have responded.",
|
|
659
|
+
"INCOMPLETE_RESPONSES",
|
|
660
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
424
661
|
);
|
|
425
662
|
}
|
|
426
663
|
const tree = new import_smt2.BTCR2MerkleTree();
|
|
427
664
|
const entries = [];
|
|
428
665
|
const encoder = new TextEncoder();
|
|
429
|
-
for (const
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
666
|
+
for (const did of this.participants) {
|
|
667
|
+
const nonce = (0, import_utils2.randomBytes)(32);
|
|
668
|
+
const signedUpdate = this.pendingUpdates.get(did);
|
|
669
|
+
if (signedUpdate) {
|
|
670
|
+
entries.push({ did, nonce, signedUpdate: encoder.encode((0, import_common3.canonicalize)(signedUpdate)) });
|
|
671
|
+
} else {
|
|
672
|
+
entries.push({ did, nonce });
|
|
673
|
+
}
|
|
433
674
|
}
|
|
434
675
|
tree.addEntries(entries);
|
|
435
676
|
tree.finalize();
|
|
436
677
|
this.signalBytes = tree.rootHash;
|
|
437
678
|
this.smtProofs = /* @__PURE__ */ new Map();
|
|
438
|
-
for (const
|
|
679
|
+
for (const did of this.participants) {
|
|
439
680
|
this.smtProofs.set(did, tree.proof(did));
|
|
440
681
|
}
|
|
441
682
|
return this.smtProofs;
|
|
@@ -461,7 +702,7 @@ var AggregationCohort = class {
|
|
|
461
702
|
return this.validationAcks.size + this.validationRejections.size === this.participants.length;
|
|
462
703
|
}
|
|
463
704
|
/**
|
|
464
|
-
* True when all participants approved. Note: differs from {@link hasAllValidationResponses}
|
|
705
|
+
* True when all participants approved. Note: differs from {@link hasAllValidationResponses},
|
|
465
706
|
* this returns false if any participant rejected, even if all responses are in.
|
|
466
707
|
*/
|
|
467
708
|
isFullyValidated() {
|
|
@@ -471,6 +712,7 @@ var AggregationCohort = class {
|
|
|
471
712
|
|
|
472
713
|
// src/core/aggregation/conditions.ts
|
|
473
714
|
var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
|
|
715
|
+
var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
|
|
474
716
|
function checkPair(problems, label, min, max) {
|
|
475
717
|
if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
|
|
476
718
|
problems.push(`min${label} must be an integer >= 0`);
|
|
@@ -514,11 +756,130 @@ function validateCohortConditions(c) {
|
|
|
514
756
|
if (c.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
|
|
515
757
|
problems.push("pendingUpdateTrigger must be an integer >= 1");
|
|
516
758
|
}
|
|
759
|
+
if (c.fallbackThreshold !== void 0) {
|
|
760
|
+
if (!Number.isInteger(c.fallbackThreshold) || c.fallbackThreshold < 1) {
|
|
761
|
+
problems.push("fallbackThreshold must be an integer >= 1");
|
|
762
|
+
} else if (c.maxParticipants !== void 0 && Number.isInteger(c.maxParticipants) && c.fallbackThreshold > c.maxParticipants) {
|
|
763
|
+
problems.push("fallbackThreshold must be <= maxParticipants");
|
|
764
|
+
}
|
|
765
|
+
}
|
|
517
766
|
checkCost(problems, "costOfEnrollment", c.costOfEnrollment);
|
|
518
767
|
checkCost(problems, "costPerAnnouncement", c.costPerAnnouncement);
|
|
768
|
+
if (typeof c.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c.recoveryKey)) {
|
|
769
|
+
problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
|
|
770
|
+
}
|
|
771
|
+
if (!Number.isInteger(c.recoverySequence) || c.recoverySequence < 1 || c.recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
772
|
+
problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
|
|
773
|
+
}
|
|
774
|
+
if (c.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c.fundingModel)) {
|
|
775
|
+
problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
|
|
776
|
+
}
|
|
519
777
|
return problems;
|
|
520
778
|
}
|
|
521
779
|
|
|
780
|
+
// src/core/aggregation/fallback-spend.ts
|
|
781
|
+
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
782
|
+
var import_secp256k13 = require("@noble/curves/secp256k1.js");
|
|
783
|
+
var import_btc_signer3 = require("@scure/btc-signer");
|
|
784
|
+
var import_musig23 = require("@scure/btc-signer/musig2");
|
|
785
|
+
function bytesEqual(a, b) {
|
|
786
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
787
|
+
}
|
|
788
|
+
function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
|
|
789
|
+
return tx.preimageWitnessV1(
|
|
790
|
+
inputIndex,
|
|
791
|
+
[prevOutScript],
|
|
792
|
+
import_btc_signer3.SigHash.DEFAULT,
|
|
793
|
+
[prevOutValue],
|
|
794
|
+
void 0,
|
|
795
|
+
fallbackLeafScript,
|
|
796
|
+
TAPROOT_LEAF_VERSION
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
function buildFallbackSpend(params) {
|
|
800
|
+
const {
|
|
801
|
+
pendingTx,
|
|
802
|
+
cohortKeys,
|
|
803
|
+
recoveryKey,
|
|
804
|
+
recoverySequence,
|
|
805
|
+
fundingModel,
|
|
806
|
+
network,
|
|
807
|
+
prevOutScript,
|
|
808
|
+
prevOutValue,
|
|
809
|
+
signatures
|
|
810
|
+
} = params;
|
|
811
|
+
const inputIndex = params.inputIndex ?? 0;
|
|
812
|
+
if (cohortKeys.length === 0) {
|
|
813
|
+
throw new AggregationCohortError(
|
|
814
|
+
"Cannot build fallback spend: no cohort keys.",
|
|
815
|
+
"NO_COHORT_KEYS"
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
|
|
819
|
+
const internalKey = (0, import_musig23.keyAggExport)((0, import_musig23.keyAggregate)((0, import_musig23.sortKeys)(cohortKeys)));
|
|
820
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
821
|
+
recoveryKey,
|
|
822
|
+
recoverySequence,
|
|
823
|
+
cohortKeys,
|
|
824
|
+
fallbackThreshold
|
|
825
|
+
});
|
|
826
|
+
const net = (0, import_bitcoin2.getNetwork)(network);
|
|
827
|
+
const payment = (0, import_btc_signer3.p2tr)(internalKey, leaves, net, true);
|
|
828
|
+
if (!bytesEqual(payment.script, prevOutScript)) {
|
|
829
|
+
throw new AggregationCohortError(
|
|
830
|
+
"Reconstructed beacon output script does not match the spent prevout script.",
|
|
831
|
+
"PREVOUT_SCRIPT_MISMATCH"
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
|
|
835
|
+
const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
|
|
836
|
+
const script = scriptVer.slice(0, scriptVer.length - 1);
|
|
837
|
+
return bytesEqual(script, fallbackLeaf);
|
|
838
|
+
});
|
|
839
|
+
if (!leafEntry) {
|
|
840
|
+
throw new AggregationCohortError(
|
|
841
|
+
"Could not locate the fallback leaf in the reconstructed beacon output.",
|
|
842
|
+
"FALLBACK_LEAF_NOT_FOUND"
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const cohortXOnly = (0, import_musig23.sortKeys)(cohortKeys).map((k) => k.slice(1));
|
|
846
|
+
const tx = pendingTx;
|
|
847
|
+
const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
|
|
848
|
+
const leafHash = tapLeafHash(fallbackLeaf);
|
|
849
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
850
|
+
for (const { pubKey, signature } of signatures) {
|
|
851
|
+
if (pubKey.length !== 32 || signature.length !== 64) continue;
|
|
852
|
+
const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
|
|
853
|
+
if (!isCohortKey) continue;
|
|
854
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
855
|
+
if (accepted.has(hex)) continue;
|
|
856
|
+
let ok = false;
|
|
857
|
+
try {
|
|
858
|
+
ok = import_secp256k13.schnorr.verify(signature, sighash, pubKey);
|
|
859
|
+
} catch {
|
|
860
|
+
ok = false;
|
|
861
|
+
}
|
|
862
|
+
if (ok) accepted.set(hex, signature);
|
|
863
|
+
}
|
|
864
|
+
if (accepted.size < fallbackThreshold) {
|
|
865
|
+
throw new AggregationCohortError(
|
|
866
|
+
`Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
|
|
867
|
+
"NOT_ENOUGH_FALLBACK_SIGNATURES",
|
|
868
|
+
{ have: accepted.size, need: fallbackThreshold }
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
|
|
872
|
+
tx.updateInput(inputIndex, {
|
|
873
|
+
tapLeafScript: [leafEntry],
|
|
874
|
+
tapScriptSig: chosen.map((pubKey) => {
|
|
875
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
876
|
+
return [{ pubKey, leafHash }, accepted.get(hex)];
|
|
877
|
+
})
|
|
878
|
+
});
|
|
879
|
+
tx.finalize();
|
|
880
|
+
return tx;
|
|
881
|
+
}
|
|
882
|
+
|
|
522
883
|
// src/core/aggregation/messages/base.ts
|
|
523
884
|
var AGGREGATION_WIRE_VERSION = 1;
|
|
524
885
|
var BaseMessage = class {
|
|
@@ -556,12 +917,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
|
556
917
|
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
557
918
|
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
558
919
|
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
920
|
+
var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
|
|
559
921
|
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
560
922
|
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
561
923
|
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
562
924
|
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
563
925
|
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
564
926
|
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
927
|
+
var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
|
|
928
|
+
var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
|
|
565
929
|
|
|
566
930
|
// src/core/aggregation/messages/factories.ts
|
|
567
931
|
function createCohortAdvertMessage(fields) {
|
|
@@ -584,6 +948,10 @@ function createSubmitUpdateMessage(fields) {
|
|
|
584
948
|
const { from: from2, to, ...body } = fields;
|
|
585
949
|
return new BaseMessage({ type: SUBMIT_UPDATE, from: from2, to, body });
|
|
586
950
|
}
|
|
951
|
+
function createSubmitNonIncludedMessage(fields) {
|
|
952
|
+
const { from: from2, to, ...body } = fields;
|
|
953
|
+
return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from2, to, body });
|
|
954
|
+
}
|
|
587
955
|
function createDistributeAggregatedDataMessage(fields) {
|
|
588
956
|
const { from: from2, to, ...body } = fields;
|
|
589
957
|
return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from2, to, body });
|
|
@@ -608,6 +976,14 @@ function createSignatureAuthorizationMessage(fields) {
|
|
|
608
976
|
const { from: from2, to, ...body } = fields;
|
|
609
977
|
return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from2, to, body });
|
|
610
978
|
}
|
|
979
|
+
function createFallbackAuthorizationRequestMessage(fields) {
|
|
980
|
+
const { from: from2, to, ...body } = fields;
|
|
981
|
+
return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from2, to, body });
|
|
982
|
+
}
|
|
983
|
+
function createFallbackSignatureMessage(fields) {
|
|
984
|
+
const { from: from2, to, ...body } = fields;
|
|
985
|
+
return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from2, to, body });
|
|
986
|
+
}
|
|
611
987
|
|
|
612
988
|
// src/core/aggregation/phases.ts
|
|
613
989
|
var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
@@ -621,6 +997,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
|
621
997
|
ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
|
|
622
998
|
ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
|
|
623
999
|
ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
|
|
1000
|
+
ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
|
|
624
1001
|
ServiceCohortPhase2["Complete"] = "Complete";
|
|
625
1002
|
ServiceCohortPhase2["Failed"] = "Failed";
|
|
626
1003
|
return ServiceCohortPhase2;
|
|
@@ -630,11 +1007,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
|
|
|
630
1007
|
ParticipantCohortPhase2["OptedIn"] = "OptedIn";
|
|
631
1008
|
ParticipantCohortPhase2["CohortReady"] = "CohortReady";
|
|
632
1009
|
ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
|
|
1010
|
+
ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
|
|
633
1011
|
ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
|
|
634
1012
|
ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
|
|
635
1013
|
ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
|
|
636
1014
|
ParticipantCohortPhase2["NonceSent"] = "NonceSent";
|
|
637
1015
|
ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
|
|
1016
|
+
ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
|
|
638
1017
|
ParticipantCohortPhase2["Complete"] = "Complete";
|
|
639
1018
|
ParticipantCohortPhase2["Failed"] = "Failed";
|
|
640
1019
|
return ParticipantCohortPhase2;
|
|
@@ -651,7 +1030,7 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
|
|
|
651
1030
|
|
|
652
1031
|
// src/core/aggregation/signing-session.ts
|
|
653
1032
|
var import_keypair = require("@did-btcr2/keypair");
|
|
654
|
-
var
|
|
1033
|
+
var import_btc_signer4 = require("@scure/btc-signer");
|
|
655
1034
|
var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
|
|
656
1035
|
var BeaconSigningSession = class {
|
|
657
1036
|
/** Unique identifier for this signing session. */
|
|
@@ -664,11 +1043,11 @@ var BeaconSigningSession = class {
|
|
|
664
1043
|
prevOutScripts;
|
|
665
1044
|
/** Previous output values for Taproot sighash computation. */
|
|
666
1045
|
prevOutValues;
|
|
667
|
-
/** Map of participant publicKey-hex
|
|
1046
|
+
/** Map of participant publicKey-hex to public nonce contribution. */
|
|
668
1047
|
nonceContributions = /* @__PURE__ */ new Map();
|
|
669
1048
|
/** Aggregated MuSig2 nonce (66 bytes). */
|
|
670
1049
|
aggregatedNonce;
|
|
671
|
-
/** Map of participant DID
|
|
1050
|
+
/** Map of participant DID to partial signature. */
|
|
672
1051
|
partialSignatures = /* @__PURE__ */ new Map();
|
|
673
1052
|
/** Final 64-byte Schnorr signature. */
|
|
674
1053
|
signature;
|
|
@@ -703,7 +1082,7 @@ var BeaconSigningSession = class {
|
|
|
703
1082
|
return this.pendingTx.preimageWitnessV1(
|
|
704
1083
|
0,
|
|
705
1084
|
this.prevOutScripts,
|
|
706
|
-
|
|
1085
|
+
import_btc_signer4.SigHash.DEFAULT,
|
|
707
1086
|
this.prevOutValues
|
|
708
1087
|
);
|
|
709
1088
|
}
|
|
@@ -851,7 +1230,7 @@ var BeaconSigningSession = class {
|
|
|
851
1230
|
throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
|
|
852
1231
|
}
|
|
853
1232
|
if (!this.#secretNonce) {
|
|
854
|
-
throw new SigningSessionError("Secret nonce not available
|
|
1233
|
+
throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
|
|
855
1234
|
}
|
|
856
1235
|
const session = new musig2.Session(
|
|
857
1236
|
this.aggregatedNonce,
|
|
@@ -921,6 +1300,9 @@ var AggregationService = class {
|
|
|
921
1300
|
case SUBMIT_UPDATE:
|
|
922
1301
|
this.#handleSubmitUpdate(message);
|
|
923
1302
|
break;
|
|
1303
|
+
case SUBMIT_NONINCLUDED:
|
|
1304
|
+
this.#handleSubmitNonInclusion(message);
|
|
1305
|
+
break;
|
|
924
1306
|
case VALIDATION_ACK:
|
|
925
1307
|
this.#handleValidationAck(message);
|
|
926
1308
|
break;
|
|
@@ -930,6 +1312,9 @@ var AggregationService = class {
|
|
|
930
1312
|
case SIGNATURE_AUTHORIZATION:
|
|
931
1313
|
this.#handleSignatureAuthorization(message);
|
|
932
1314
|
break;
|
|
1315
|
+
case FALLBACK_SIGNATURE:
|
|
1316
|
+
this.#handleFallbackSignature(message);
|
|
1317
|
+
break;
|
|
933
1318
|
default:
|
|
934
1319
|
break;
|
|
935
1320
|
}
|
|
@@ -948,7 +1333,7 @@ var AggregationService = class {
|
|
|
948
1333
|
}
|
|
949
1334
|
/**
|
|
950
1335
|
* Create a new cohort with the given config. Returns the cohort ID.
|
|
951
|
-
* Cohort starts in `Created` phase
|
|
1336
|
+
* Cohort starts in `Created` phase - call `advertise()` to broadcast.
|
|
952
1337
|
*/
|
|
953
1338
|
createCohort(config) {
|
|
954
1339
|
const problems = validateCohortConditions(config);
|
|
@@ -963,7 +1348,11 @@ var AggregationService = class {
|
|
|
963
1348
|
serviceDid: this.did,
|
|
964
1349
|
minParticipants: config.minParticipants,
|
|
965
1350
|
network: config.network,
|
|
966
|
-
beaconType: config.beaconType
|
|
1351
|
+
beaconType: config.beaconType,
|
|
1352
|
+
recoveryKey: (0, import_utils3.hexToBytes)(config.recoveryKey),
|
|
1353
|
+
recoverySequence: config.recoverySequence,
|
|
1354
|
+
fundingModel: config.fundingModel,
|
|
1355
|
+
fallbackThreshold: config.fallbackThreshold
|
|
967
1356
|
});
|
|
968
1357
|
this.#cohortStates.set(cohort.id, {
|
|
969
1358
|
phase: "Created" /* Created */,
|
|
@@ -1164,11 +1553,64 @@ var AggregationService = class {
|
|
|
1164
1553
|
});
|
|
1165
1554
|
return;
|
|
1166
1555
|
}
|
|
1556
|
+
if (state.cohort.nonIncluded.has(message.from)) {
|
|
1557
|
+
state.rejections.push({
|
|
1558
|
+
from: message.from,
|
|
1559
|
+
code: "UPDATE_MALFORMED",
|
|
1560
|
+
reason: "Participant already declined this round; cannot also submit an update"
|
|
1561
|
+
});
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
if (state.cohort.pendingUpdates.has(message.from)) {
|
|
1565
|
+
state.rejections.push({
|
|
1566
|
+
from: message.from,
|
|
1567
|
+
code: "UPDATE_MALFORMED",
|
|
1568
|
+
reason: "Participant already submitted an update this round; cannot resubmit"
|
|
1569
|
+
});
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1167
1572
|
state.cohort.addUpdate(message.from, signedUpdate);
|
|
1168
1573
|
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
1169
1574
|
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
1170
1575
|
}
|
|
1171
|
-
if (state.cohort.
|
|
1576
|
+
if (state.cohort.hasAllResponses()) {
|
|
1577
|
+
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
|
|
1582
|
+
* update this round (cooperative non-inclusion). Membership is proven by the
|
|
1583
|
+
* signed transport envelope, so the body carries only the cohortId. The member
|
|
1584
|
+
* stays in the cohort and still signs; it is absent from the CAS map and
|
|
1585
|
+
* carries a non-inclusion leaf in the SMT.
|
|
1586
|
+
*/
|
|
1587
|
+
#handleSubmitNonInclusion(message) {
|
|
1588
|
+
const cohortId = message.body?.cohortId;
|
|
1589
|
+
if (!cohortId) return;
|
|
1590
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1591
|
+
if (!state) return;
|
|
1592
|
+
if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
|
|
1593
|
+
if (!state.cohort.participants.includes(message.from)) {
|
|
1594
|
+
state.rejections.push({
|
|
1595
|
+
from: message.from,
|
|
1596
|
+
code: "UPDATE_MALFORMED",
|
|
1597
|
+
reason: "Sender is not a member of this cohort"
|
|
1598
|
+
});
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (state.cohort.pendingUpdates.has(message.from) || state.cohort.nonIncluded.has(message.from)) {
|
|
1602
|
+
state.rejections.push({
|
|
1603
|
+
from: message.from,
|
|
1604
|
+
code: "UPDATE_MALFORMED",
|
|
1605
|
+
reason: "Participant already responded this round"
|
|
1606
|
+
});
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
state.cohort.addNonInclusion(message.from);
|
|
1610
|
+
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
1611
|
+
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
1612
|
+
}
|
|
1613
|
+
if (state.cohort.hasAllResponses()) {
|
|
1172
1614
|
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
1173
1615
|
}
|
|
1174
1616
|
}
|
|
@@ -1227,7 +1669,7 @@ var AggregationService = class {
|
|
|
1227
1669
|
);
|
|
1228
1670
|
}
|
|
1229
1671
|
strategy.buildAggregatedData(state.cohort);
|
|
1230
|
-
const signalBytesHex = (0,
|
|
1672
|
+
const signalBytesHex = (0, import_utils3.bytesToHex)(state.cohort.signalBytes);
|
|
1231
1673
|
state.phase = "DataDistributed" /* DataDistributed */;
|
|
1232
1674
|
const messages = [];
|
|
1233
1675
|
for (const participantDid of state.cohort.participants) {
|
|
@@ -1278,7 +1720,7 @@ var AggregationService = class {
|
|
|
1278
1720
|
}
|
|
1279
1721
|
/**
|
|
1280
1722
|
* Start a signing session by creating auth requests for all participants.
|
|
1281
|
-
* The caller provides the transaction data
|
|
1723
|
+
* The caller provides the transaction data - typically built via
|
|
1282
1724
|
* `buildBeaconTransaction()` against a Bitcoin connection.
|
|
1283
1725
|
*/
|
|
1284
1726
|
startSigning(cohortId, txData) {
|
|
@@ -1317,7 +1759,7 @@ var AggregationService = class {
|
|
|
1317
1759
|
cohortId,
|
|
1318
1760
|
sessionId: session.id,
|
|
1319
1761
|
pendingTx: txData.tx.hex,
|
|
1320
|
-
prevOutScriptHex: (0,
|
|
1762
|
+
prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
|
|
1321
1763
|
prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
|
|
1322
1764
|
}));
|
|
1323
1765
|
}
|
|
@@ -1385,11 +1827,144 @@ var AggregationService = class {
|
|
|
1385
1827
|
state.result = {
|
|
1386
1828
|
cohortId,
|
|
1387
1829
|
signature,
|
|
1388
|
-
signedTx: state.signingSession.pendingTx
|
|
1830
|
+
signedTx: state.signingSession.pendingTx,
|
|
1831
|
+
path: "key-path"
|
|
1389
1832
|
};
|
|
1390
1833
|
state.phase = "Complete" /* Complete */;
|
|
1391
1834
|
}
|
|
1392
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Abandon the optimistic n-of-n key path and ask members to authorize the
|
|
1838
|
+
* k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
|
|
1839
|
+
* liveness, ADR 042). Reuses the in-flight signing session's transaction and
|
|
1840
|
+
* spent output, so the announcement and its outputs are unchanged: only the
|
|
1841
|
+
* witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
|
|
1842
|
+
* participant.
|
|
1843
|
+
*
|
|
1844
|
+
* Callable once optimistic signing has started (the session and its tx exist)
|
|
1845
|
+
* and before it completes. A cohort can take exactly one of the two paths: the
|
|
1846
|
+
* caller (runner) must commit to fallback and stop driving the optimistic path.
|
|
1847
|
+
*/
|
|
1848
|
+
startFallbackSigning(cohortId) {
|
|
1849
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1850
|
+
if (!state) {
|
|
1851
|
+
throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
|
|
1852
|
+
}
|
|
1853
|
+
if (!state.signingSession) {
|
|
1854
|
+
throw new AggregationServiceError(
|
|
1855
|
+
`Cannot start fallback for cohort ${cohortId}: no signing session.`,
|
|
1856
|
+
"NO_SIGNING_SESSION",
|
|
1857
|
+
{ cohortId }
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
const signingPhases = [
|
|
1861
|
+
"SigningStarted" /* SigningStarted */,
|
|
1862
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
1863
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
1864
|
+
];
|
|
1865
|
+
if (!signingPhases.includes(state.phase)) {
|
|
1866
|
+
throw new AggregationServiceError(
|
|
1867
|
+
`Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
|
|
1868
|
+
"INVALID_PHASE",
|
|
1869
|
+
{ cohortId, phase: state.phase }
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
const session = state.signingSession;
|
|
1873
|
+
const prevOutScript = session.prevOutScripts[0];
|
|
1874
|
+
const prevOutValue = session.prevOutValues[0];
|
|
1875
|
+
if (!prevOutScript || prevOutValue === void 0) {
|
|
1876
|
+
throw new AggregationServiceError(
|
|
1877
|
+
`Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
|
|
1878
|
+
"MISSING_PREV_OUT",
|
|
1879
|
+
{ cohortId }
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
1883
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1884
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
1885
|
+
});
|
|
1886
|
+
state.fallbackSignatures = /* @__PURE__ */ new Map();
|
|
1887
|
+
state.phase = "FallbackRequested" /* FallbackRequested */;
|
|
1888
|
+
const messages = [];
|
|
1889
|
+
for (const participantDid of state.cohort.participants) {
|
|
1890
|
+
messages.push(createFallbackAuthorizationRequestMessage({
|
|
1891
|
+
from: this.did,
|
|
1892
|
+
to: participantDid,
|
|
1893
|
+
cohortId,
|
|
1894
|
+
sessionId: session.id,
|
|
1895
|
+
pendingTx: session.pendingTx.hex,
|
|
1896
|
+
prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
|
|
1897
|
+
prevOutValue: prevOutValue.toString(),
|
|
1898
|
+
fallbackLeafScriptHex: (0, import_utils3.bytesToHex)(fallbackLeaf)
|
|
1899
|
+
}));
|
|
1900
|
+
}
|
|
1901
|
+
return messages;
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
|
|
1905
|
+
* signature over the fallback script-path sighash. The signature is
|
|
1906
|
+
* authenticated to the sender (its `signerPk` must be the sender's own cohort
|
|
1907
|
+
* key and the signature must verify against the sighash) and collected. Once k
|
|
1908
|
+
* valid signatures are in, the k-of-n fallback spend is assembled and the
|
|
1909
|
+
* cohort completes via the script path.
|
|
1910
|
+
*/
|
|
1911
|
+
#handleFallbackSignature(message) {
|
|
1912
|
+
const cohortId = message.body?.cohortId;
|
|
1913
|
+
if (!cohortId) return;
|
|
1914
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1915
|
+
if (!state || !state.signingSession || !state.fallbackSignatures) return;
|
|
1916
|
+
if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
|
|
1917
|
+
const sessionId = message.body?.sessionId;
|
|
1918
|
+
if (sessionId !== state.signingSession.id) return;
|
|
1919
|
+
const signerPk = message.body?.signerPk;
|
|
1920
|
+
const fallbackSignature = message.body?.fallbackSignature;
|
|
1921
|
+
if (!signerPk || !fallbackSignature) return;
|
|
1922
|
+
const prevOutScript = state.signingSession.prevOutScripts[0];
|
|
1923
|
+
const prevOutValue = state.signingSession.prevOutValues[0];
|
|
1924
|
+
if (!prevOutScript || prevOutValue === void 0) return;
|
|
1925
|
+
const memberKey = state.cohort.participantKeys.get(message.from);
|
|
1926
|
+
if (!memberKey) {
|
|
1927
|
+
state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
const memberXOnly = memberKey.slice(1);
|
|
1931
|
+
if (signerPk.length !== 32 || !memberXOnly.every((b, i) => b === signerPk[i])) {
|
|
1932
|
+
state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
1936
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1937
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
1938
|
+
});
|
|
1939
|
+
const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
1940
|
+
let valid = false;
|
|
1941
|
+
try {
|
|
1942
|
+
valid = fallbackSignature.length === 64 && import_secp256k14.schnorr.verify(fallbackSignature, sighash, signerPk);
|
|
1943
|
+
} catch {
|
|
1944
|
+
valid = false;
|
|
1945
|
+
}
|
|
1946
|
+
if (!valid) {
|
|
1947
|
+
state.rejections.push({ from: message.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
state.fallbackSignatures.set(message.from, { pubKey: signerPk, signature: fallbackSignature });
|
|
1951
|
+
if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
|
|
1952
|
+
const signedTx = buildFallbackSpend({
|
|
1953
|
+
pendingTx: state.signingSession.pendingTx,
|
|
1954
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1955
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold,
|
|
1956
|
+
recoveryKey: state.cohort.recoveryKey,
|
|
1957
|
+
recoverySequence: state.cohort.recoverySequence,
|
|
1958
|
+
fundingModel: state.cohort.fundingModel,
|
|
1959
|
+
network: state.cohort.network,
|
|
1960
|
+
prevOutScript,
|
|
1961
|
+
prevOutValue,
|
|
1962
|
+
signatures: [...state.fallbackSignatures.values()]
|
|
1963
|
+
});
|
|
1964
|
+
state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
|
|
1965
|
+
state.phase = "Complete" /* Complete */;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1393
1968
|
getResult(cohortId) {
|
|
1394
1969
|
return this.#cohortStates.get(cohortId)?.result;
|
|
1395
1970
|
}
|
|
@@ -1421,8 +1996,9 @@ var AggregationService = class {
|
|
|
1421
1996
|
|
|
1422
1997
|
// src/core/aggregation/participant.ts
|
|
1423
1998
|
var import_common5 = require("@did-btcr2/common");
|
|
1424
|
-
var
|
|
1425
|
-
var
|
|
1999
|
+
var import_secp256k15 = require("@noble/curves/secp256k1.js");
|
|
2000
|
+
var import_utils4 = require("@noble/hashes/utils");
|
|
2001
|
+
var import_btc_signer5 = require("@scure/btc-signer");
|
|
1426
2002
|
|
|
1427
2003
|
// src/core/aggregation/messages/bodies.ts
|
|
1428
2004
|
var hasStr = (b, k) => !!b && typeof b[k] === "string";
|
|
@@ -1434,6 +2010,14 @@ var optIntMin = (b, k, min) => {
|
|
|
1434
2010
|
const v = b ? b[k] : void 0;
|
|
1435
2011
|
return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
|
|
1436
2012
|
};
|
|
2013
|
+
var intInRange = (b, k, min, max) => {
|
|
2014
|
+
const v = b ? b[k] : void 0;
|
|
2015
|
+
return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
2016
|
+
};
|
|
2017
|
+
var optStrOneOf = (b, k, allowed) => {
|
|
2018
|
+
const v = b ? b[k] : void 0;
|
|
2019
|
+
return v === void 0 || typeof v === "string" && allowed.includes(v);
|
|
2020
|
+
};
|
|
1437
2021
|
var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
|
|
1438
2022
|
var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
|
|
1439
2023
|
var hasBytesArray = (b, k) => {
|
|
@@ -1441,7 +2025,7 @@ var hasBytesArray = (b, k) => {
|
|
|
1441
2025
|
return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
|
|
1442
2026
|
};
|
|
1443
2027
|
function isCohortAdvertMessage(m) {
|
|
1444
|
-
return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasIntMin(m.body, "minParticipants", 1) && optIntMin(m.body, "maxParticipants", 1) && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasBytes(m.body, "communicationPk");
|
|
2028
|
+
return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasIntMin(m.body, "minParticipants", 1) && optIntMin(m.body, "maxParticipants", 1) && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasStr(m.body, "recoveryKey") && intInRange(m.body, "recoverySequence", 1, MAX_RECOVERY_SEQUENCE) && optStrOneOf(m.body, "fundingModel", KNOWN_FUNDING_MODELS) && optIntMin(m.body, "fallbackThreshold", 1) && hasBytes(m.body, "communicationPk");
|
|
1445
2029
|
}
|
|
1446
2030
|
function isCohortOptInMessage(m) {
|
|
1447
2031
|
return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
|
|
@@ -1455,6 +2039,9 @@ function isCohortReadyMessage(m) {
|
|
|
1455
2039
|
function isSubmitUpdateMessage(m) {
|
|
1456
2040
|
return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
|
|
1457
2041
|
}
|
|
2042
|
+
function isSubmitNonIncludedMessage(m) {
|
|
2043
|
+
return m.type === SUBMIT_NONINCLUDED && hasStr(m.body, "cohortId");
|
|
2044
|
+
}
|
|
1458
2045
|
function isDistributeAggregatedDataMessage(m) {
|
|
1459
2046
|
return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
|
|
1460
2047
|
}
|
|
@@ -1473,8 +2060,38 @@ function isAggregatedNonceMessage(m) {
|
|
|
1473
2060
|
function isSignatureAuthorizationMessage(m) {
|
|
1474
2061
|
return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
|
|
1475
2062
|
}
|
|
2063
|
+
function isFallbackAuthorizationRequestMessage(m) {
|
|
2064
|
+
return m.type === FALLBACK_AUTHORIZATION_REQUEST && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasStr(m.body, "pendingTx") && hasStr(m.body, "prevOutScriptHex") && hasStr(m.body, "prevOutValue") && hasStr(m.body, "fallbackLeafScriptHex");
|
|
2065
|
+
}
|
|
2066
|
+
function isFallbackSignatureMessage(m) {
|
|
2067
|
+
return m.type === FALLBACK_SIGNATURE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "signerPk") && hasBytes(m.body, "fallbackSignature");
|
|
2068
|
+
}
|
|
1476
2069
|
|
|
1477
2070
|
// src/core/aggregation/participant.ts
|
|
2071
|
+
function txEmbedsSignal(tx, signalHex) {
|
|
2072
|
+
let expected;
|
|
2073
|
+
try {
|
|
2074
|
+
expected = (0, import_utils4.hexToBytes)(signalHex);
|
|
2075
|
+
} catch {
|
|
2076
|
+
return false;
|
|
2077
|
+
}
|
|
2078
|
+
if (expected.length === 0) return false;
|
|
2079
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
2080
|
+
const script = tx.getOutput(i)?.script;
|
|
2081
|
+
if (!script) continue;
|
|
2082
|
+
let decoded;
|
|
2083
|
+
try {
|
|
2084
|
+
decoded = import_btc_signer5.Script.decode(script);
|
|
2085
|
+
} catch {
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
|
|
2089
|
+
const payload = decoded[1];
|
|
2090
|
+
if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
1478
2095
|
var AggregationParticipant = class {
|
|
1479
2096
|
did;
|
|
1480
2097
|
/** MuSig2 signing capability. The raw secret never lives as a field here. */
|
|
@@ -1491,7 +2108,7 @@ var AggregationParticipant = class {
|
|
|
1491
2108
|
}
|
|
1492
2109
|
/**
|
|
1493
2110
|
* Process an incoming message. Updates internal state but never produces
|
|
1494
|
-
* outgoing messages
|
|
2111
|
+
* outgoing messages: those come exclusively from action methods.
|
|
1495
2112
|
*/
|
|
1496
2113
|
receive(message) {
|
|
1497
2114
|
if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
|
|
@@ -1517,6 +2134,9 @@ var AggregationParticipant = class {
|
|
|
1517
2134
|
case AGGREGATED_NONCE:
|
|
1518
2135
|
this.#handleAggregatedNonce(message);
|
|
1519
2136
|
break;
|
|
2137
|
+
case FALLBACK_AUTHORIZATION_REQUEST:
|
|
2138
|
+
this.#handleFallbackAuthorizationRequest(message);
|
|
2139
|
+
break;
|
|
1520
2140
|
default:
|
|
1521
2141
|
break;
|
|
1522
2142
|
}
|
|
@@ -1562,12 +2182,17 @@ var AggregationParticipant = class {
|
|
|
1562
2182
|
{ cohortId, phase: state?.phase }
|
|
1563
2183
|
);
|
|
1564
2184
|
}
|
|
2185
|
+
const advert = state.advert;
|
|
1565
2186
|
const cohort = new AggregationCohort({
|
|
1566
2187
|
id: cohortId,
|
|
1567
2188
|
serviceDid: state.serviceDid,
|
|
1568
|
-
minParticipants:
|
|
1569
|
-
network:
|
|
1570
|
-
beaconType:
|
|
2189
|
+
minParticipants: advert.minParticipants,
|
|
2190
|
+
network: advert.network,
|
|
2191
|
+
beaconType: advert.beaconType,
|
|
2192
|
+
recoveryKey: advert.recoveryKey ? (0, import_utils4.hexToBytes)(advert.recoveryKey) : void 0,
|
|
2193
|
+
recoverySequence: advert.recoverySequence,
|
|
2194
|
+
fundingModel: advert.fundingModel,
|
|
2195
|
+
fallbackThreshold: advert.fallbackThreshold
|
|
1571
2196
|
});
|
|
1572
2197
|
state.cohort = cohort;
|
|
1573
2198
|
state.phase = "OptedIn" /* OptedIn */;
|
|
@@ -1583,7 +2208,7 @@ var AggregationParticipant = class {
|
|
|
1583
2208
|
#handleOptInAccept(message) {
|
|
1584
2209
|
void message;
|
|
1585
2210
|
}
|
|
1586
|
-
/** Cohorts that have been finalized
|
|
2211
|
+
/** Cohorts that have been finalized: beacon address available. */
|
|
1587
2212
|
get joinedCohorts() {
|
|
1588
2213
|
const map = /* @__PURE__ */ new Map();
|
|
1589
2214
|
for (const [id, state] of this.#cohortStates) {
|
|
@@ -1607,8 +2232,8 @@ var AggregationParticipant = class {
|
|
|
1607
2232
|
const beaconAddress = message.body?.beaconAddress;
|
|
1608
2233
|
const cohortKeys = message.body?.cohortKeys;
|
|
1609
2234
|
if (!beaconAddress || !cohortKeys) return;
|
|
1610
|
-
const participantPkHex = (0,
|
|
1611
|
-
const cohortKeysHex = cohortKeys.map((k) => (0,
|
|
2235
|
+
const participantPkHex = (0, import_utils4.bytesToHex)(this.publicKey);
|
|
2236
|
+
const cohortKeysHex = cohortKeys.map((k) => (0, import_utils4.bytesToHex)(new Uint8Array(k)));
|
|
1612
2237
|
state.cohort.validateMembership(participantPkHex, cohortKeysHex, beaconAddress);
|
|
1613
2238
|
state.phase = "CohortReady" /* CohortReady */;
|
|
1614
2239
|
}
|
|
@@ -1626,6 +2251,7 @@ var AggregationParticipant = class {
|
|
|
1626
2251
|
);
|
|
1627
2252
|
}
|
|
1628
2253
|
state.submittedUpdate = signedUpdate;
|
|
2254
|
+
state.included = true;
|
|
1629
2255
|
state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
|
|
1630
2256
|
const message = createSubmitUpdateMessage({
|
|
1631
2257
|
from: this.did,
|
|
@@ -1635,6 +2261,30 @@ var AggregationParticipant = class {
|
|
|
1635
2261
|
});
|
|
1636
2262
|
return [message];
|
|
1637
2263
|
}
|
|
2264
|
+
/**
|
|
2265
|
+
* User action: decline to submit an update this round (cooperative
|
|
2266
|
+
* non-inclusion). The member stays in the cohort and still signs; it will be
|
|
2267
|
+
* absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
|
|
2268
|
+
* SMT. Returns the SUBMIT_NONINCLUDED message to send.
|
|
2269
|
+
*/
|
|
2270
|
+
declineUpdate(cohortId) {
|
|
2271
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2272
|
+
if (!state || state.phase !== "CohortReady" /* CohortReady */) {
|
|
2273
|
+
throw new AggregationParticipantError(
|
|
2274
|
+
`Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
|
|
2275
|
+
"INVALID_PHASE",
|
|
2276
|
+
{ cohortId, phase: state?.phase }
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
state.included = false;
|
|
2280
|
+
state.phase = "NonIncluded" /* NonIncluded */;
|
|
2281
|
+
const message = createSubmitNonIncludedMessage({
|
|
2282
|
+
from: this.did,
|
|
2283
|
+
to: state.serviceDid,
|
|
2284
|
+
cohortId
|
|
2285
|
+
});
|
|
2286
|
+
return [message];
|
|
2287
|
+
}
|
|
1638
2288
|
/** Aggregated data awaiting user validation. */
|
|
1639
2289
|
get pendingValidations() {
|
|
1640
2290
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1648,9 +2298,9 @@ var AggregationParticipant = class {
|
|
|
1648
2298
|
/**
|
|
1649
2299
|
* The validated aggregated data retained for a cohort, regardless of phase.
|
|
1650
2300
|
* Unlike {@link pendingValidations} (which lists only cohorts still awaiting
|
|
1651
|
-
* the validate decision), this returns the stored validation
|
|
1652
|
-
* participant's sidecar (the CAS Announcement map or its SMT inclusion proof)
|
|
1653
|
-
*
|
|
2301
|
+
* the validate decision), this returns the stored validation, including the
|
|
2302
|
+
* participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
|
|
2303
|
+
* so it is still readable once the cohort reaches Complete. Returns
|
|
1654
2304
|
* undefined before aggregated data has been received.
|
|
1655
2305
|
*/
|
|
1656
2306
|
getValidation(cohortId) {
|
|
@@ -1660,18 +2310,20 @@ var AggregationParticipant = class {
|
|
|
1660
2310
|
const cohortId = message.body?.cohortId;
|
|
1661
2311
|
if (!cohortId) return;
|
|
1662
2312
|
const state = this.#cohortStates.get(cohortId);
|
|
1663
|
-
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
|
|
1664
|
-
|
|
2313
|
+
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
|
|
2314
|
+
const declined = state.included === false;
|
|
2315
|
+
if (!declined && !state.submittedUpdate) return;
|
|
1665
2316
|
const beaconType = message.body?.beaconType;
|
|
1666
2317
|
if (!beaconType) return;
|
|
1667
2318
|
const strategy = getBeaconStrategy(beaconType);
|
|
1668
2319
|
if (!strategy) return;
|
|
1669
2320
|
const signalBytesHex = message.body?.signalBytesHex ?? "";
|
|
1670
|
-
const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
|
|
2321
|
+
const expectedHash = declined ? "" : (0, import_common5.canonicalHash)(state.submittedUpdate);
|
|
1671
2322
|
const result = strategy.validateParticipantView({
|
|
1672
2323
|
participantDid: this.did,
|
|
1673
|
-
|
|
1674
|
-
|
|
2324
|
+
included: !declined,
|
|
2325
|
+
submittedUpdate: declined ? void 0 : state.submittedUpdate,
|
|
2326
|
+
expectedHash: declined ? void 0 : expectedHash,
|
|
1675
2327
|
body: message.body
|
|
1676
2328
|
});
|
|
1677
2329
|
state.validation = {
|
|
@@ -1681,7 +2333,8 @@ var AggregationParticipant = class {
|
|
|
1681
2333
|
expectedHash,
|
|
1682
2334
|
matches: result.matches,
|
|
1683
2335
|
casAnnouncement: result.casAnnouncement,
|
|
1684
|
-
smtProof: result.smtProof
|
|
2336
|
+
smtProof: result.smtProof,
|
|
2337
|
+
included: !declined
|
|
1685
2338
|
};
|
|
1686
2339
|
state.phase = "AwaitingValidation" /* AwaitingValidation */;
|
|
1687
2340
|
}
|
|
@@ -1755,6 +2408,31 @@ var AggregationParticipant = class {
|
|
|
1755
2408
|
};
|
|
1756
2409
|
state.phase = "AwaitingSigning" /* AwaitingSigning */;
|
|
1757
2410
|
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Bind a signing approval to the announcement the member validated: a beacon
|
|
2413
|
+
* transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
|
|
2414
|
+
* the aggregated data was distributed. Both the optimistic nonce approval and
|
|
2415
|
+
* the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
|
|
2416
|
+
* while the coordinator drives output selection, so without this check a
|
|
2417
|
+
* coordinator could anchor a different signal under the member's signature.
|
|
2418
|
+
*/
|
|
2419
|
+
#assertTxAnchorsValidatedSignal(cohortId, state, tx) {
|
|
2420
|
+
const signalHex = state.validation?.signalBytesHex;
|
|
2421
|
+
if (!signalHex) {
|
|
2422
|
+
throw new AggregationParticipantError(
|
|
2423
|
+
`Cohort ${cohortId} has no validated signal to bind the signature to.`,
|
|
2424
|
+
"MISSING_STATE",
|
|
2425
|
+
{ cohortId }
|
|
2426
|
+
);
|
|
2427
|
+
}
|
|
2428
|
+
if (!txEmbedsSignal(tx, signalHex)) {
|
|
2429
|
+
throw new AggregationParticipantError(
|
|
2430
|
+
`Transaction for cohort ${cohortId} does not anchor the validated signal.`,
|
|
2431
|
+
"SIGNAL_MISMATCH",
|
|
2432
|
+
{ cohortId }
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
1758
2436
|
/**
|
|
1759
2437
|
* User action: approve signing and generate nonce contribution.
|
|
1760
2438
|
*/
|
|
@@ -1774,8 +2452,9 @@ var AggregationParticipant = class {
|
|
|
1774
2452
|
{ cohortId }
|
|
1775
2453
|
);
|
|
1776
2454
|
}
|
|
1777
|
-
const tx =
|
|
1778
|
-
|
|
2455
|
+
const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
|
|
2456
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
2457
|
+
const prevOutScripts = [(0, import_utils4.hexToBytes)(state.signingRequest.prevOutScriptHex)];
|
|
1779
2458
|
const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
|
|
1780
2459
|
const session = new BeaconSigningSession({
|
|
1781
2460
|
id: state.signingRequest.sessionId,
|
|
@@ -1810,7 +2489,7 @@ var AggregationParticipant = class {
|
|
|
1810
2489
|
}
|
|
1811
2490
|
/**
|
|
1812
2491
|
* User action: generate and return the partial signature.
|
|
1813
|
-
* In most UIs this is automatic after AwaitingPartialSig
|
|
2492
|
+
* In most UIs this is automatic after AwaitingPartialSig, but exposing it
|
|
1814
2493
|
* as an explicit action lets the client UI confirm before signing if desired.
|
|
1815
2494
|
*/
|
|
1816
2495
|
generatePartialSignature(cohortId) {
|
|
@@ -1842,6 +2521,83 @@ var AggregationParticipant = class {
|
|
|
1842
2521
|
partialSignature: partialSig
|
|
1843
2522
|
})];
|
|
1844
2523
|
}
|
|
2524
|
+
/** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
|
|
2525
|
+
get pendingFallbackRequests() {
|
|
2526
|
+
const map = /* @__PURE__ */ new Map();
|
|
2527
|
+
for (const [id, state] of this.#cohortStates) {
|
|
2528
|
+
if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
|
|
2529
|
+
map.set(id, state.fallbackRequest);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
return map;
|
|
2533
|
+
}
|
|
2534
|
+
#handleFallbackAuthorizationRequest(message) {
|
|
2535
|
+
const cohortId = message.body?.cohortId;
|
|
2536
|
+
if (!cohortId) return;
|
|
2537
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2538
|
+
if (!state || !state.cohort) return;
|
|
2539
|
+
const acceptFrom = [
|
|
2540
|
+
"ValidationSent" /* ValidationSent */,
|
|
2541
|
+
"AwaitingSigning" /* AwaitingSigning */,
|
|
2542
|
+
"NonceSent" /* NonceSent */,
|
|
2543
|
+
"AwaitingPartialSig" /* AwaitingPartialSig */,
|
|
2544
|
+
"Complete" /* Complete */
|
|
2545
|
+
];
|
|
2546
|
+
if (!acceptFrom.includes(state.phase)) return;
|
|
2547
|
+
const sessionId = message.body?.sessionId;
|
|
2548
|
+
const pendingTxHex = message.body?.pendingTx;
|
|
2549
|
+
const prevOutScriptHex = message.body?.prevOutScriptHex;
|
|
2550
|
+
const prevOutValue = message.body?.prevOutValue;
|
|
2551
|
+
const fallbackLeafScriptHex = message.body?.fallbackLeafScriptHex;
|
|
2552
|
+
if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
|
|
2553
|
+
state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
|
|
2554
|
+
state.signingSession?.clearSecrets();
|
|
2555
|
+
state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* User action: authorize the fallback spend. Recomputes the k-of-n fallback
|
|
2559
|
+
* leaf from the member's OWN cohort state (not the service-provided script),
|
|
2560
|
+
* computes the BIP-341 script-path sighash over the requested transaction, and
|
|
2561
|
+
* returns a standalone BIP-340 signature (no nonce round). The member completes
|
|
2562
|
+
* once it has contributed; the service needs only k of these.
|
|
2563
|
+
*/
|
|
2564
|
+
approveFallback(cohortId) {
|
|
2565
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2566
|
+
if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
|
|
2567
|
+
throw new AggregationParticipantError(
|
|
2568
|
+
`Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
|
|
2569
|
+
"INVALID_PHASE",
|
|
2570
|
+
{ cohortId, phase: state?.phase }
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
if (!state.fallbackRequest || !state.cohort) {
|
|
2574
|
+
throw new AggregationParticipantError(
|
|
2575
|
+
`Cohort ${cohortId} missing fallback request or cohort state.`,
|
|
2576
|
+
"MISSING_STATE",
|
|
2577
|
+
{ cohortId }
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
const req = state.fallbackRequest;
|
|
2581
|
+
const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(req.pendingTxHex), { allowUnknownOutputs: true });
|
|
2582
|
+
const prevOutScript = (0, import_utils4.hexToBytes)(req.prevOutScriptHex);
|
|
2583
|
+
const prevOutValue = BigInt(req.prevOutValue);
|
|
2584
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
2585
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
2586
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
2587
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
2588
|
+
});
|
|
2589
|
+
const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
2590
|
+
const signature = this.#signer.withSecret((secretKey) => import_secp256k15.schnorr.sign(sighash, secretKey));
|
|
2591
|
+
state.phase = "Complete" /* Complete */;
|
|
2592
|
+
return [createFallbackSignatureMessage({
|
|
2593
|
+
from: this.did,
|
|
2594
|
+
to: state.serviceDid,
|
|
2595
|
+
cohortId,
|
|
2596
|
+
sessionId: req.sessionId,
|
|
2597
|
+
signerPk: this.publicKey.slice(1),
|
|
2598
|
+
fallbackSignature: signature
|
|
2599
|
+
})];
|
|
2600
|
+
}
|
|
1845
2601
|
getCohortPhase(cohortId) {
|
|
1846
2602
|
return this.#cohortStates.get(cohortId)?.phase;
|
|
1847
2603
|
}
|
|
@@ -1878,6 +2634,87 @@ var KeyPairAggregationSigner = class {
|
|
|
1878
2634
|
}
|
|
1879
2635
|
};
|
|
1880
2636
|
|
|
2637
|
+
// src/core/aggregation/recovery-spend.ts
|
|
2638
|
+
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
2639
|
+
var import_secp256k16 = require("@noble/curves/secp256k1.js");
|
|
2640
|
+
var import_btc_signer6 = require("@scure/btc-signer");
|
|
2641
|
+
var import_musig24 = require("@scure/btc-signer/musig2");
|
|
2642
|
+
var DUST_LIMIT_SATS = 546n;
|
|
2643
|
+
function bytesEqual2(a, b) {
|
|
2644
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
2645
|
+
}
|
|
2646
|
+
function buildRecoverySpend(params) {
|
|
2647
|
+
const {
|
|
2648
|
+
cohortKeys,
|
|
2649
|
+
recoverySecretKey,
|
|
2650
|
+
recoveryKey,
|
|
2651
|
+
recoverySequence,
|
|
2652
|
+
fallbackThreshold,
|
|
2653
|
+
fundingModel,
|
|
2654
|
+
network,
|
|
2655
|
+
utxo,
|
|
2656
|
+
destinationAddress,
|
|
2657
|
+
fee,
|
|
2658
|
+
beaconAddress
|
|
2659
|
+
} = params;
|
|
2660
|
+
if (cohortKeys.length === 0) {
|
|
2661
|
+
throw new AggregationCohortError(
|
|
2662
|
+
"Cannot build recovery spend: no cohort keys.",
|
|
2663
|
+
"NO_COHORT_KEYS"
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
const derivedPub = import_secp256k16.schnorr.getPublicKey(recoverySecretKey);
|
|
2667
|
+
if (!bytesEqual2(derivedPub, recoveryKey)) {
|
|
2668
|
+
throw new AggregationCohortError(
|
|
2669
|
+
"Recovery secret key does not correspond to the committed recovery key.",
|
|
2670
|
+
"RECOVERY_KEY_MISMATCH"
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
const out = utxo.value - fee;
|
|
2674
|
+
if (out <= 0n) {
|
|
2675
|
+
throw new AggregationCohortError(
|
|
2676
|
+
`Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
|
|
2677
|
+
"FEE_EXCEEDS_VALUE",
|
|
2678
|
+
{ fee: fee.toString(), value: utxo.value.toString() }
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
if (out < DUST_LIMIT_SATS) {
|
|
2682
|
+
throw new AggregationCohortError(
|
|
2683
|
+
`Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
|
|
2684
|
+
"DUST_OUTPUT",
|
|
2685
|
+
{ output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
const internalKey = (0, import_musig24.keyAggExport)((0, import_musig24.keyAggregate)((0, import_musig24.sortKeys)(cohortKeys)));
|
|
2689
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
2690
|
+
recoveryKey,
|
|
2691
|
+
recoverySequence,
|
|
2692
|
+
cohortKeys,
|
|
2693
|
+
fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
|
|
2694
|
+
});
|
|
2695
|
+
const net = (0, import_bitcoin3.getNetwork)(network);
|
|
2696
|
+
const payment = (0, import_btc_signer6.p2tr)(internalKey, leaves, net, true);
|
|
2697
|
+
if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
|
|
2698
|
+
throw new AggregationCohortError(
|
|
2699
|
+
`Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
|
|
2700
|
+
"BEACON_ADDRESS_MISMATCH",
|
|
2701
|
+
{ reconstructed: payment.address, expected: beaconAddress }
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
const tx = new import_btc_signer6.Transaction({ version: 2, allowUnknownInputs: true });
|
|
2705
|
+
tx.addInput({
|
|
2706
|
+
txid: utxo.txid,
|
|
2707
|
+
index: utxo.vout,
|
|
2708
|
+
witnessUtxo: { script: payment.script, amount: utxo.value },
|
|
2709
|
+
tapLeafScript: payment.tapLeafScript,
|
|
2710
|
+
sequence: recoverySequence
|
|
2711
|
+
});
|
|
2712
|
+
tx.addOutputAddress(destinationAddress, out, net);
|
|
2713
|
+
tx.signIdx(recoverySecretKey, 0);
|
|
2714
|
+
tx.finalize();
|
|
2715
|
+
return tx;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
1881
2718
|
// src/core/aggregation/logger.ts
|
|
1882
2719
|
var CONSOLE_LOGGER = {
|
|
1883
2720
|
debug: (msg, ...args) => console.debug(msg, ...args),
|
|
@@ -1905,6 +2742,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
|
|
|
1905
2742
|
]);
|
|
1906
2743
|
var UPDATE_VALUES = /* @__PURE__ */ new Set([
|
|
1907
2744
|
SUBMIT_UPDATE,
|
|
2745
|
+
SUBMIT_NONINCLUDED,
|
|
1908
2746
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
1909
2747
|
VALIDATION_ACK
|
|
1910
2748
|
]);
|
|
@@ -1912,7 +2750,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
|
|
|
1912
2750
|
AUTHORIZATION_REQUEST,
|
|
1913
2751
|
NONCE_CONTRIBUTION,
|
|
1914
2752
|
AGGREGATED_NONCE,
|
|
1915
|
-
SIGNATURE_AUTHORIZATION
|
|
2753
|
+
SIGNATURE_AUTHORIZATION,
|
|
2754
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
2755
|
+
FALLBACK_SIGNATURE
|
|
1916
2756
|
]);
|
|
1917
2757
|
function isAggregationMessageType(type) {
|
|
1918
2758
|
return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
|
|
@@ -2140,7 +2980,7 @@ var Identifier = class _Identifier {
|
|
|
2140
2980
|
|
|
2141
2981
|
// src/core/aggregation/transport/http/envelope.ts
|
|
2142
2982
|
var import_common8 = require("@did-btcr2/common");
|
|
2143
|
-
var
|
|
2983
|
+
var import_utils5 = require("@noble/hashes/utils");
|
|
2144
2984
|
|
|
2145
2985
|
// src/core/aggregation/transport/http/errors.ts
|
|
2146
2986
|
var HttpTransportError = class extends TransportAdapterError {
|
|
@@ -2168,7 +3008,7 @@ var DEFAULT_NONCE_LEN_BYTES = 16;
|
|
|
2168
3008
|
// src/core/aggregation/transport/http/envelope.ts
|
|
2169
3009
|
function signEnvelope(message, sender, opts = {}) {
|
|
2170
3010
|
const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2171
|
-
const nonce = opts.nonce ?? (0,
|
|
3011
|
+
const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2172
3012
|
const messageJson = normalizeForWire(normalizeMessage(message));
|
|
2173
3013
|
const unsigned = {
|
|
2174
3014
|
v: HTTP_ENVELOPE_VERSION,
|
|
@@ -2180,7 +3020,7 @@ function signEnvelope(message, sender, opts = {}) {
|
|
|
2180
3020
|
};
|
|
2181
3021
|
const digest = (0, import_common8.canonicalHashBytes)(unsigned);
|
|
2182
3022
|
const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2183
|
-
return { ...unsigned, sig: (0,
|
|
3023
|
+
return { ...unsigned, sig: (0, import_utils5.bytesToHex)(sig) };
|
|
2184
3024
|
}
|
|
2185
3025
|
function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
2186
3026
|
if (envelope.v !== HTTP_ENVELOPE_VERSION) {
|
|
@@ -2217,7 +3057,7 @@ function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
|
2217
3057
|
}
|
|
2218
3058
|
let sigBytes;
|
|
2219
3059
|
try {
|
|
2220
|
-
sigBytes = (0,
|
|
3060
|
+
sigBytes = (0, import_utils5.hexToBytes)(envelope.sig);
|
|
2221
3061
|
} catch {
|
|
2222
3062
|
throw new HttpTransportError(
|
|
2223
3063
|
"Envelope signature is not valid hex",
|
|
@@ -2250,7 +3090,7 @@ function normalizeMessage(message) {
|
|
|
2250
3090
|
}
|
|
2251
3091
|
function normalizeForWire(value) {
|
|
2252
3092
|
if (value instanceof Uint8Array) {
|
|
2253
|
-
return { __bytes: (0,
|
|
3093
|
+
return { __bytes: (0, import_utils5.bytesToHex)(value) };
|
|
2254
3094
|
}
|
|
2255
3095
|
if (Array.isArray(value)) {
|
|
2256
3096
|
return value.map((v) => normalizeForWire(v));
|
|
@@ -2269,7 +3109,7 @@ function reviveFromWire(value) {
|
|
|
2269
3109
|
const rec = value;
|
|
2270
3110
|
const keys = Object.keys(rec);
|
|
2271
3111
|
if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
|
|
2272
|
-
return (0,
|
|
3112
|
+
return (0, import_utils5.hexToBytes)(rec.__bytes);
|
|
2273
3113
|
}
|
|
2274
3114
|
const out = {};
|
|
2275
3115
|
for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
|
|
@@ -2283,11 +3123,11 @@ function reviveFromWire(value) {
|
|
|
2283
3123
|
|
|
2284
3124
|
// src/core/aggregation/transport/http/request-auth.ts
|
|
2285
3125
|
var import_common9 = require("@did-btcr2/common");
|
|
2286
|
-
var
|
|
3126
|
+
var import_utils6 = require("@noble/hashes/utils");
|
|
2287
3127
|
var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
|
|
2288
3128
|
function buildRequestAuth(did, keys, path, opts = {}) {
|
|
2289
3129
|
const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2290
|
-
const nonce = opts.nonce ?? (0,
|
|
3130
|
+
const nonce = opts.nonce ?? (0, import_utils6.bytesToHex)((0, import_utils6.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2291
3131
|
const digest = (0, import_common9.canonicalHashBytes)({
|
|
2292
3132
|
v: HTTP_ENVELOPE_VERSION,
|
|
2293
3133
|
did,
|
|
@@ -2296,7 +3136,7 @@ function buildRequestAuth(did, keys, path, opts = {}) {
|
|
|
2296
3136
|
path
|
|
2297
3137
|
});
|
|
2298
3138
|
const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2299
|
-
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0,
|
|
3139
|
+
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils6.bytesToHex)(sig)}`;
|
|
2300
3140
|
}
|
|
2301
3141
|
function parseRequestAuth(headerValue) {
|
|
2302
3142
|
const prefix = `${REQUEST_AUTH_SCHEME} `;
|
|
@@ -2347,7 +3187,7 @@ function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
|
|
|
2347
3187
|
}
|
|
2348
3188
|
let sigBytes;
|
|
2349
3189
|
try {
|
|
2350
|
-
sigBytes = (0,
|
|
3190
|
+
sigBytes = (0, import_utils6.hexToBytes)(parsed.sig);
|
|
2351
3191
|
} catch {
|
|
2352
3192
|
throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
|
|
2353
3193
|
}
|
|
@@ -3326,7 +4166,7 @@ function flattenMessage2(msg) {
|
|
|
3326
4166
|
|
|
3327
4167
|
// src/core/aggregation/transport/nostr.ts
|
|
3328
4168
|
var import_keypair6 = require("@did-btcr2/keypair");
|
|
3329
|
-
var
|
|
4169
|
+
var import_utils7 = require("@noble/hashes/utils");
|
|
3330
4170
|
var import_nostr_tools = require("nostr-tools");
|
|
3331
4171
|
var import_pool = require("nostr-tools/pool");
|
|
3332
4172
|
var DEFAULT_NOSTR_RELAYS = [
|
|
@@ -3524,14 +4364,14 @@ var NostrTransport = class _NostrTransport {
|
|
|
3524
4364
|
}
|
|
3525
4365
|
const senderKeys = actor.keys;
|
|
3526
4366
|
const tags = [
|
|
3527
|
-
["p", (0,
|
|
4367
|
+
["p", (0, import_utils7.bytesToHex)(senderKeys.publicKey.x)],
|
|
3528
4368
|
["t", type]
|
|
3529
4369
|
];
|
|
3530
4370
|
if (to) {
|
|
3531
4371
|
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
3532
4372
|
if (recipientPkBytes) {
|
|
3533
4373
|
const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3534
|
-
tags.push(["p", (0,
|
|
4374
|
+
tags.push(["p", (0, import_utils7.bytesToHex)(recipientPk.x)]);
|
|
3535
4375
|
}
|
|
3536
4376
|
}
|
|
3537
4377
|
if (isKeygenMessageType(type)) {
|
|
@@ -3564,7 +4404,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3564
4404
|
const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3565
4405
|
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
3566
4406
|
senderKeys.secretKey.bytes,
|
|
3567
|
-
(0,
|
|
4407
|
+
(0, import_utils7.bytesToHex)(recipientPk.x)
|
|
3568
4408
|
);
|
|
3569
4409
|
const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
|
|
3570
4410
|
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
@@ -3587,7 +4427,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3587
4427
|
* backfill historical events to late subscribers: republishing gives late
|
|
3588
4428
|
* joiners a window to discover the message without requiring protocol
|
|
3589
4429
|
* changes. Relay rate-limit / publish failures inside the interval are
|
|
3590
|
-
* caught and logged rather than propagated
|
|
4430
|
+
* caught and logged rather than propagated - the caller should stop the
|
|
3591
4431
|
* repeater once the protocol condition is satisfied.
|
|
3592
4432
|
*/
|
|
3593
4433
|
publishRepeating(message, sender, intervalMs, recipient) {
|
|
@@ -3618,7 +4458,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3618
4458
|
*/
|
|
3619
4459
|
#subscribeDirected(did, entry) {
|
|
3620
4460
|
if (!this.pool) return;
|
|
3621
|
-
const pkHex = (0,
|
|
4461
|
+
const pkHex = (0, import_utils7.bytesToHex)(entry.keys.publicKey.x);
|
|
3622
4462
|
const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
|
|
3623
4463
|
onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
3624
4464
|
onevent: this.#makeActorEventHandler(did)
|
|
@@ -3636,7 +4476,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3636
4476
|
return async (event) => {
|
|
3637
4477
|
const actor = this.#actors.get(actorDid);
|
|
3638
4478
|
if (!actor) return;
|
|
3639
|
-
if (event.pubkey === (0,
|
|
4479
|
+
if (event.pubkey === (0, import_utils7.bytesToHex)(actor.keys.publicKey.x)) return;
|
|
3640
4480
|
let message;
|
|
3641
4481
|
try {
|
|
3642
4482
|
if (event.kind === 1) {
|
|
@@ -3749,7 +4589,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3749
4589
|
*/
|
|
3750
4590
|
static #jsonReplacer(_key, value) {
|
|
3751
4591
|
if (value instanceof Uint8Array) {
|
|
3752
|
-
return { __bytes: (0,
|
|
4592
|
+
return { __bytes: (0, import_utils7.bytesToHex)(value) };
|
|
3753
4593
|
}
|
|
3754
4594
|
return value;
|
|
3755
4595
|
}
|
|
@@ -3767,7 +4607,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3767
4607
|
*/
|
|
3768
4608
|
static #jsonReviver(_key, value) {
|
|
3769
4609
|
if (value && typeof value === "object" && "__bytes" in value) {
|
|
3770
|
-
return (0,
|
|
4610
|
+
return (0, import_utils7.hexToBytes)(value.__bytes);
|
|
3771
4611
|
}
|
|
3772
4612
|
return value;
|
|
3773
4613
|
}
|
|
@@ -3802,7 +4642,7 @@ var TransportFactory = class {
|
|
|
3802
4642
|
};
|
|
3803
4643
|
|
|
3804
4644
|
// src/core/aggregation/transport/in-memory.ts
|
|
3805
|
-
var
|
|
4645
|
+
var import_utils8 = require("@noble/hashes/utils");
|
|
3806
4646
|
var InMemoryBus = class {
|
|
3807
4647
|
#transports = /* @__PURE__ */ new Set();
|
|
3808
4648
|
/** Attach a transport to this bus. Called by the transport's constructor. */
|
|
@@ -3821,8 +4661,8 @@ var InMemoryBus = class {
|
|
|
3821
4661
|
async deliver(message, _sender, recipient) {
|
|
3822
4662
|
const type = message.type;
|
|
3823
4663
|
if (!type) return;
|
|
3824
|
-
const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0,
|
|
3825
|
-
const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0,
|
|
4664
|
+
const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils8.bytesToHex)(v) } : v;
|
|
4665
|
+
const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils8.hexToBytes)(v.__bytes) : v;
|
|
3826
4666
|
const raw = JSON.parse(JSON.stringify(message, replacer), reviver);
|
|
3827
4667
|
const serialized = { ...raw, ...raw.body ?? {} };
|
|
3828
4668
|
if (!recipient) {
|
|
@@ -4021,6 +4861,27 @@ var TypedEventEmitter = class {
|
|
|
4021
4861
|
}
|
|
4022
4862
|
};
|
|
4023
4863
|
|
|
4864
|
+
// src/core/beacon/fee-estimator.ts
|
|
4865
|
+
var StaticFeeEstimator = class {
|
|
4866
|
+
satsPerVbyte;
|
|
4867
|
+
/**
|
|
4868
|
+
* @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
|
|
4869
|
+
*/
|
|
4870
|
+
constructor(satsPerVbyte = 5) {
|
|
4871
|
+
if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
|
|
4872
|
+
throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
|
|
4873
|
+
}
|
|
4874
|
+
this.satsPerVbyte = satsPerVbyte;
|
|
4875
|
+
}
|
|
4876
|
+
async estimateFee(vsize) {
|
|
4877
|
+
if (vsize < 0 || !Number.isFinite(vsize)) {
|
|
4878
|
+
throw new Error(`Invalid vsize: ${vsize}`);
|
|
4879
|
+
}
|
|
4880
|
+
return BigInt(Math.ceil(vsize * this.satsPerVbyte));
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
|
|
4884
|
+
|
|
4024
4885
|
// src/core/aggregation/runner/service-runner.ts
|
|
4025
4886
|
var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
|
|
4026
4887
|
var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
|
|
@@ -4032,9 +4893,17 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4032
4893
|
#onOptInReceived;
|
|
4033
4894
|
#onReadyToFinalize;
|
|
4034
4895
|
#onProvideTxData;
|
|
4896
|
+
#feeEstimator;
|
|
4035
4897
|
#cohortTtlMs;
|
|
4036
4898
|
#phaseTimeoutMs;
|
|
4037
4899
|
#advertRepeatIntervalMs;
|
|
4900
|
+
#autoFallbackOnStall;
|
|
4901
|
+
/** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
|
|
4902
|
+
static #SIGNING_PHASES = [
|
|
4903
|
+
"SigningStarted" /* SigningStarted */,
|
|
4904
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
4905
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
4906
|
+
];
|
|
4038
4907
|
/** Per-cohort run state, keyed by cohortId. */
|
|
4039
4908
|
#contexts = /* @__PURE__ */ new Map();
|
|
4040
4909
|
#handlersRegistered = false;
|
|
@@ -4049,9 +4918,11 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4049
4918
|
finalize: acceptedCount >= minRequired
|
|
4050
4919
|
}));
|
|
4051
4920
|
this.#onProvideTxData = options.onProvideTxData;
|
|
4921
|
+
this.#feeEstimator = options.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
4052
4922
|
this.#cohortTtlMs = options.cohortTtlMs;
|
|
4053
4923
|
this.#phaseTimeoutMs = options.phaseTimeoutMs;
|
|
4054
4924
|
this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
|
|
4925
|
+
this.#autoFallbackOnStall = options.autoFallbackOnStall ?? false;
|
|
4055
4926
|
this.session = new AggregationService({
|
|
4056
4927
|
// The coordinator never signs, so the state machine receives only the
|
|
4057
4928
|
// public half of the operator's keypair (see ADR 038). The full keypair
|
|
@@ -4213,11 +5084,42 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4213
5084
|
ctx.phaseTimer = void 0;
|
|
4214
5085
|
if (this.#phaseTimeoutMs === void 0) return;
|
|
4215
5086
|
ctx.phaseTimer = setTimeout(() => {
|
|
5087
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
5088
|
+
const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
|
|
5089
|
+
if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
|
|
5090
|
+
this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
|
|
5091
|
+
return;
|
|
5092
|
+
}
|
|
4216
5093
|
const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
|
|
4217
5094
|
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
4218
5095
|
this.#failCohort(ctx, new Error(reason));
|
|
4219
5096
|
}, this.#phaseTimeoutMs);
|
|
4220
5097
|
}
|
|
5098
|
+
/**
|
|
5099
|
+
* Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
|
|
5100
|
+
* fallback (script-path) signatures instead (ADR 042). Idempotent and safe
|
|
5101
|
+
* against the optimistic completion: it commits the cohort to the fallback
|
|
5102
|
+
* path synchronously (the `committedPath` latch) before sending anything, so a
|
|
5103
|
+
* late optimistic signature can no longer complete-and-broadcast a competing
|
|
5104
|
+
* spend of the same UTXO. No-op if the cohort is unknown, already settled, or
|
|
5105
|
+
* already committed to a path.
|
|
5106
|
+
*
|
|
5107
|
+
* Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
|
|
5108
|
+
* set; otherwise call it from an operator decision (a UI "fall back now"
|
|
5109
|
+
* action). Throws only if the underlying state machine rejects the transition
|
|
5110
|
+
* (e.g. signing has not started).
|
|
5111
|
+
*/
|
|
5112
|
+
async triggerFallback(cohortId) {
|
|
5113
|
+
const ctx = this.#contexts.get(cohortId);
|
|
5114
|
+
if (!ctx || ctx.settled || ctx.committedPath) return;
|
|
5115
|
+
const messages = this.session.startFallbackSigning(cohortId);
|
|
5116
|
+
ctx.committedPath = "fallback";
|
|
5117
|
+
this.#stopAdvertRepeating(ctx);
|
|
5118
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5119
|
+
const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
|
|
5120
|
+
this.emit("fallback-started", { cohortId, sessionId });
|
|
5121
|
+
await this.#sendAll(messages);
|
|
5122
|
+
}
|
|
4221
5123
|
/** Detect a phase change for a cohort since the last observation and reset its phase timer. */
|
|
4222
5124
|
#onPhaseMaybeChanged(ctx) {
|
|
4223
5125
|
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
@@ -4306,13 +5208,15 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4306
5208
|
static #HANDLED_MESSAGE_TYPES = [
|
|
4307
5209
|
COHORT_OPT_IN,
|
|
4308
5210
|
SUBMIT_UPDATE,
|
|
5211
|
+
SUBMIT_NONINCLUDED,
|
|
4309
5212
|
VALIDATION_ACK,
|
|
4310
5213
|
NONCE_CONTRIBUTION,
|
|
4311
|
-
SIGNATURE_AUTHORIZATION
|
|
5214
|
+
SIGNATURE_AUTHORIZATION,
|
|
5215
|
+
FALLBACK_SIGNATURE
|
|
4312
5216
|
];
|
|
4313
5217
|
/**
|
|
4314
5218
|
* Internal: handler registration with the transport. Idempotent. Handlers
|
|
4315
|
-
* are DID-scoped and cohort-agnostic
|
|
5219
|
+
* are DID-scoped and cohort-agnostic - one registration serves every cohort
|
|
4316
5220
|
* this runner drives; demux to the right {@link RunContext} happens in each
|
|
4317
5221
|
* handler via the inbound message's cohortId.
|
|
4318
5222
|
*/
|
|
@@ -4321,9 +5225,11 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4321
5225
|
this.#handlersRegistered = true;
|
|
4322
5226
|
this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
|
|
4323
5227
|
this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
|
|
5228
|
+
this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
|
|
4324
5229
|
this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
|
|
4325
5230
|
this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
|
|
4326
5231
|
this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
|
|
5232
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
|
|
4327
5233
|
}
|
|
4328
5234
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
4329
5235
|
#unregisterHandlers() {
|
|
@@ -4443,7 +5349,8 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4443
5349
|
const txData = await this.#onProvideTxData({
|
|
4444
5350
|
cohortId: ctx.cohortId,
|
|
4445
5351
|
beaconAddress: cohort.beaconAddress,
|
|
4446
|
-
signalBytes: cohort.signalBytes
|
|
5352
|
+
signalBytes: cohort.signalBytes,
|
|
5353
|
+
feeEstimator: this.#feeEstimator
|
|
4447
5354
|
});
|
|
4448
5355
|
const authMsgs = this.session.startSigning(ctx.cohortId, txData);
|
|
4449
5356
|
const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
|
|
@@ -4484,6 +5391,32 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4484
5391
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4485
5392
|
*/
|
|
4486
5393
|
async #handleSignatureAuthorization(msg) {
|
|
5394
|
+
if (this.#stopped) return;
|
|
5395
|
+
const ctx = this.#contextFor(msg);
|
|
5396
|
+
if (!ctx) return;
|
|
5397
|
+
try {
|
|
5398
|
+
this.session.receive(msg);
|
|
5399
|
+
this.#drainRejections(ctx);
|
|
5400
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5401
|
+
if (ctx.committedPath === "fallback") return;
|
|
5402
|
+
const result = this.session.getResult(ctx.cohortId);
|
|
5403
|
+
if (result) {
|
|
5404
|
+
ctx.committedPath = "optimistic";
|
|
5405
|
+
this.#completeCohort(ctx, result);
|
|
5406
|
+
}
|
|
5407
|
+
} catch (err) {
|
|
5408
|
+
this.#failCohort(ctx, err);
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
/**
|
|
5412
|
+
* Handler for receiving fallback (k-of-n script-path) signatures. The state
|
|
5413
|
+
* machine assembles and finalizes the fallback spend once k valid signatures
|
|
5414
|
+
* are in; the result is then emitted and the cohort's completion resolves. The
|
|
5415
|
+
* cohort is already committed to the fallback path (via {@link triggerFallback}).
|
|
5416
|
+
* @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
|
|
5417
|
+
* @returns {Promise<void>} Resolves when handling is complete.
|
|
5418
|
+
*/
|
|
5419
|
+
async #handleFallbackSignature(msg) {
|
|
4487
5420
|
if (this.#stopped) return;
|
|
4488
5421
|
const ctx = this.#contextFor(msg);
|
|
4489
5422
|
if (!ctx) return;
|
|
@@ -4556,7 +5489,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4556
5489
|
COHORT_READY,
|
|
4557
5490
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
4558
5491
|
AUTHORIZATION_REQUEST,
|
|
4559
|
-
AGGREGATED_NONCE
|
|
5492
|
+
AGGREGATED_NONCE,
|
|
5493
|
+
FALLBACK_AUTHORIZATION_REQUEST
|
|
4560
5494
|
];
|
|
4561
5495
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
4562
5496
|
#unregisterHandlers() {
|
|
@@ -4591,7 +5525,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4591
5525
|
*
|
|
4592
5526
|
* For an open-ended, long-lived subscriber (no fixed count), construct an
|
|
4593
5527
|
* {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
|
|
4594
|
-
* `start()`, and listen for `cohort-complete`
|
|
5528
|
+
* `start()`, and listen for `cohort-complete` - the runner already drives
|
|
4595
5529
|
* any number of cohorts concurrently.
|
|
4596
5530
|
*
|
|
4597
5531
|
* @param options Participant runner options (set `shouldJoin` to select cohorts).
|
|
@@ -4626,6 +5560,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4626
5560
|
this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
|
|
4627
5561
|
this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
|
|
4628
5562
|
this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
|
|
5563
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
|
|
4629
5564
|
}
|
|
4630
5565
|
/**
|
|
4631
5566
|
* Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
|
|
@@ -4681,8 +5616,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4681
5616
|
cohortId,
|
|
4682
5617
|
beaconAddress: info.beaconAddress
|
|
4683
5618
|
});
|
|
4684
|
-
|
|
4685
|
-
|
|
5619
|
+
if (signedUpdate === null) {
|
|
5620
|
+
await this.#sendAll(this.session.declineUpdate(cohortId));
|
|
5621
|
+
this.emit("update-declined", { cohortId });
|
|
5622
|
+
} else {
|
|
5623
|
+
await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
|
|
5624
|
+
this.emit("update-submitted", { cohortId });
|
|
5625
|
+
}
|
|
4686
5626
|
} catch (err) {
|
|
4687
5627
|
this.emit("error", err);
|
|
4688
5628
|
}
|
|
@@ -4758,23 +5698,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4758
5698
|
const cohortId = msg.body?.cohortId;
|
|
4759
5699
|
if (!cohortId) return;
|
|
4760
5700
|
await this.#sendAll(this.session.generatePartialSignature(cohortId));
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
5701
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
5702
|
+
} catch (err) {
|
|
5703
|
+
this.emit("error", err);
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
/**
|
|
5707
|
+
* Internal: handler for fallback authorization requests. The service abandoned
|
|
5708
|
+
* the optimistic key path; the member authorizes the k-of-n script-path spend
|
|
5709
|
+
* of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
|
|
5710
|
+
* the decision, signs the fallback, and completes once it has contributed (the
|
|
5711
|
+
* service needs only k members).
|
|
5712
|
+
* @param {BaseMessage} msg - The received fallback authorization request message.
|
|
5713
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
5714
|
+
*/
|
|
5715
|
+
async #handleFallbackAuthorizationRequest(msg) {
|
|
5716
|
+
if (this.#stopped) return;
|
|
5717
|
+
try {
|
|
5718
|
+
this.session.receive(msg);
|
|
5719
|
+
const cohortId = msg.body?.cohortId;
|
|
5720
|
+
if (!cohortId) return;
|
|
5721
|
+
const req = this.session.pendingFallbackRequests.get(cohortId);
|
|
5722
|
+
if (!req) return;
|
|
5723
|
+
this.emit("fallback-requested", req);
|
|
5724
|
+
const decision = await this.#onApproveSigning(req);
|
|
5725
|
+
if (!decision.approved) {
|
|
5726
|
+
this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
|
|
5727
|
+
return;
|
|
4773
5728
|
}
|
|
5729
|
+
await this.#sendAll(this.session.approveFallback(cohortId));
|
|
5730
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
4774
5731
|
} catch (err) {
|
|
4775
5732
|
this.emit("error", err);
|
|
4776
5733
|
}
|
|
4777
5734
|
}
|
|
5735
|
+
/**
|
|
5736
|
+
* Internal: emit `cohort-complete` with the participant's retained sidecar once
|
|
5737
|
+
* the cohort has reached the Complete phase. Surfaces the CAS Announcement map
|
|
5738
|
+
* (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
|
|
5739
|
+
* DID resolution. Read via getValidation (not pendingValidations, which lists
|
|
5740
|
+
* only the AwaitingValidation phase) so the sidecar is still available at
|
|
5741
|
+
* Complete. Shared by the optimistic and fallback completion paths.
|
|
5742
|
+
*/
|
|
5743
|
+
#emitCohortCompleteIfDone(cohortId) {
|
|
5744
|
+
if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
|
|
5745
|
+
const info = this.session.joinedCohorts.get(cohortId);
|
|
5746
|
+
if (!info) return;
|
|
5747
|
+
const validation = this.session.getValidation(cohortId);
|
|
5748
|
+
this.emit("cohort-complete", {
|
|
5749
|
+
cohortId,
|
|
5750
|
+
beaconAddress: info.beaconAddress,
|
|
5751
|
+
beaconType: validation?.beaconType ?? "",
|
|
5752
|
+
included: validation?.included ?? true,
|
|
5753
|
+
casAnnouncement: validation?.casAnnouncement,
|
|
5754
|
+
smtProof: validation?.smtProof
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
4778
5757
|
/**
|
|
4779
5758
|
* Internal: send helper to ensure messages are sent sequentially. This is important for protocol
|
|
4780
5759
|
* correctness, as some transports may not guarantee message order if sent in parallel.
|
|
@@ -4790,6 +5769,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4790
5769
|
};
|
|
4791
5770
|
|
|
4792
5771
|
// src/core/aggregation/runner/aggregation-runner.ts
|
|
5772
|
+
var import_utils9 = require("@noble/hashes/utils");
|
|
4793
5773
|
var AggregationRunner = class {
|
|
4794
5774
|
/**
|
|
4795
5775
|
* Run a cohort of ONE participant entirely in-process and return the
|
|
@@ -4797,8 +5777,8 @@ var AggregationRunner = class {
|
|
|
4797
5777
|
*
|
|
4798
5778
|
* One party plays both the coordinating service and the lone participant,
|
|
4799
5779
|
* connected over an {@link InMemoryTransport} (no relay or HTTP server). This
|
|
4800
|
-
* makes the single-participant aggregate-beacon path
|
|
4801
|
-
* two-axis beacon matrix
|
|
5780
|
+
* makes the single-participant aggregate-beacon path (the N=1 corner of the
|
|
5781
|
+
* two-axis beacon matrix, see ADR 037) first-class, useful for generating
|
|
4802
5782
|
* and reproducing single-participant aggregate test vectors.
|
|
4803
5783
|
*
|
|
4804
5784
|
* The service advertises a cohort with `minParticipants: 1`; the participant
|
|
@@ -4816,12 +5796,15 @@ var AggregationRunner = class {
|
|
|
4816
5796
|
transport.registerPeer(options.participant.did, options.participant.keys.publicKey.compressed);
|
|
4817
5797
|
transport.registerPeer(options.service.did, options.service.keys.publicKey.compressed);
|
|
4818
5798
|
transport.start();
|
|
5799
|
+
const recoveryKey = options.config.recoveryKey ?? (0, import_utils9.bytesToHex)(options.service.keys.publicKey.compressed.slice(1));
|
|
5800
|
+
const recoverySequence = options.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
|
|
4819
5801
|
const service = new AggregationServiceRunner({
|
|
4820
5802
|
transport,
|
|
4821
5803
|
did: options.service.did,
|
|
4822
5804
|
keys: options.service.keys,
|
|
4823
|
-
config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType },
|
|
5805
|
+
config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
|
|
4824
5806
|
onProvideTxData: options.onProvideTxData,
|
|
5807
|
+
feeEstimator: options.feeEstimator,
|
|
4825
5808
|
cohortTtlMs: options.cohortTtlMs,
|
|
4826
5809
|
phaseTimeoutMs: options.phaseTimeoutMs,
|
|
4827
5810
|
// In-process bus with the participant already listening: a single advert
|
|
@@ -4846,8 +5829,8 @@ var AggregationRunner = class {
|
|
|
4846
5829
|
};
|
|
4847
5830
|
|
|
4848
5831
|
// src/core/beacon/beacon.ts
|
|
4849
|
-
var
|
|
4850
|
-
var
|
|
5832
|
+
var import_utils10 = require("@noble/hashes/utils.js");
|
|
5833
|
+
var import_btc_signer7 = require("@scure/btc-signer");
|
|
4851
5834
|
|
|
4852
5835
|
// src/core/beacon/error.ts
|
|
4853
5836
|
var import_common12 = require("@did-btcr2/common");
|
|
@@ -4877,28 +5860,7 @@ var SMTBeaconError = class extends import_common12.MethodError {
|
|
|
4877
5860
|
}
|
|
4878
5861
|
};
|
|
4879
5862
|
|
|
4880
|
-
// src/core/beacon/fee-estimator.ts
|
|
4881
|
-
var StaticFeeEstimator = class {
|
|
4882
|
-
satsPerVbyte;
|
|
4883
|
-
/**
|
|
4884
|
-
* @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
|
|
4885
|
-
*/
|
|
4886
|
-
constructor(satsPerVbyte = 5) {
|
|
4887
|
-
if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
|
|
4888
|
-
throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
|
|
4889
|
-
}
|
|
4890
|
-
this.satsPerVbyte = satsPerVbyte;
|
|
4891
|
-
}
|
|
4892
|
-
async estimateFee(vsize) {
|
|
4893
|
-
if (vsize < 0 || !Number.isFinite(vsize)) {
|
|
4894
|
-
throw new Error(`Invalid vsize: ${vsize}`);
|
|
4895
|
-
}
|
|
4896
|
-
return BigInt(Math.ceil(vsize * this.satsPerVbyte));
|
|
4897
|
-
}
|
|
4898
|
-
};
|
|
4899
|
-
|
|
4900
5863
|
// src/core/beacon/beacon.ts
|
|
4901
|
-
var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
|
|
4902
5864
|
var P2TR_BEACON_TX_VSIZE = 160;
|
|
4903
5865
|
var P2WPKH_BEACON_TX_VSIZE = 155;
|
|
4904
5866
|
var P2PKH_BEACON_TX_VSIZE = 240;
|
|
@@ -4907,8 +5869,22 @@ var SINGLETON_BEACON_TX_VSIZE = {
|
|
|
4907
5869
|
p2wpkh: P2WPKH_BEACON_TX_VSIZE,
|
|
4908
5870
|
p2tr: P2TR_BEACON_TX_VSIZE
|
|
4909
5871
|
};
|
|
5872
|
+
var CHANGE_OUTPUT_VBYTES = {
|
|
5873
|
+
p2pkh: 34,
|
|
5874
|
+
p2wpkh: 31,
|
|
5875
|
+
p2tr: 43
|
|
5876
|
+
};
|
|
5877
|
+
var DUST_LIMIT_SATS2 = {
|
|
5878
|
+
p2pkh: 546,
|
|
5879
|
+
p2wpkh: 294,
|
|
5880
|
+
p2tr: 330
|
|
5881
|
+
};
|
|
5882
|
+
function beaconTxVsize(beaconKind, changeKind) {
|
|
5883
|
+
const base2 = SINGLETON_BEACON_TX_VSIZE[beaconKind] - CHANGE_OUTPUT_VBYTES[beaconKind];
|
|
5884
|
+
return base2 + CHANGE_OUTPUT_VBYTES[changeKind];
|
|
5885
|
+
}
|
|
4910
5886
|
function detectSingletonScriptKind(bitcoinAddress, network) {
|
|
4911
|
-
const decoded = (0,
|
|
5887
|
+
const decoded = (0, import_btc_signer7.Address)(network).decode(bitcoinAddress);
|
|
4912
5888
|
if (decoded.type === "pkh") return "p2pkh";
|
|
4913
5889
|
if (decoded.type === "wpkh") return "p2wpkh";
|
|
4914
5890
|
if (decoded.type === "tr") return "p2tr";
|
|
@@ -4919,12 +5895,32 @@ function detectSingletonScriptKind(bitcoinAddress, network) {
|
|
|
4919
5895
|
);
|
|
4920
5896
|
}
|
|
4921
5897
|
function deriveSingletonAddress(kind, pubkey, network) {
|
|
4922
|
-
if (kind === "p2pkh") return (0,
|
|
4923
|
-
if (kind === "p2wpkh") return (0,
|
|
4924
|
-
return (0,
|
|
5898
|
+
if (kind === "p2pkh") return (0, import_btc_signer7.p2pkh)(pubkey, network).address;
|
|
5899
|
+
if (kind === "p2wpkh") return (0, import_btc_signer7.p2wpkh)(pubkey, network).address;
|
|
5900
|
+
return (0, import_btc_signer7.p2tr)(pubkey.slice(1, 33), void 0, network).address;
|
|
5901
|
+
}
|
|
5902
|
+
function resolveChangeAddress(beaconAddress, network, changeAddress) {
|
|
5903
|
+
if (!changeAddress || changeAddress === beaconAddress) return beaconAddress;
|
|
5904
|
+
try {
|
|
5905
|
+
(0, import_btc_signer7.Address)(network).decode(changeAddress);
|
|
5906
|
+
} catch {
|
|
5907
|
+
throw new BeaconError(
|
|
5908
|
+
`Invalid change address "${changeAddress}" for network "${network}".`,
|
|
5909
|
+
"INVALID_CHANGE_ADDRESS",
|
|
5910
|
+
{ changeAddress, network }
|
|
5911
|
+
);
|
|
5912
|
+
}
|
|
5913
|
+
return changeAddress;
|
|
5914
|
+
}
|
|
5915
|
+
function changeOutputKind(changeAddress, network) {
|
|
5916
|
+
try {
|
|
5917
|
+
return detectSingletonScriptKind(changeAddress, network);
|
|
5918
|
+
} catch {
|
|
5919
|
+
return "p2tr";
|
|
5920
|
+
}
|
|
4925
5921
|
}
|
|
4926
5922
|
function opReturnScript(signalBytes) {
|
|
4927
|
-
return
|
|
5923
|
+
return import_btc_signer7.Script.encode(["RETURN", signalBytes]);
|
|
4928
5924
|
}
|
|
4929
5925
|
async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
4930
5926
|
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
@@ -4944,14 +5940,15 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
|
4944
5940
|
);
|
|
4945
5941
|
}
|
|
4946
5942
|
const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
4947
|
-
return { utxo, prevTxBytes: (0,
|
|
5943
|
+
return { utxo, prevTxBytes: (0, import_utils10.hexToBytes)(prevTxHex) };
|
|
4948
5944
|
}
|
|
4949
5945
|
async function buildAggregationBeaconTx(opts) {
|
|
4950
5946
|
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
4951
5947
|
const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
|
|
4952
|
-
const
|
|
4953
|
-
const witnessScript =
|
|
4954
|
-
const
|
|
5948
|
+
const changeAddress = resolveChangeAddress(opts.beaconAddress, opts.network, opts.changeAddress);
|
|
5949
|
+
const witnessScript = import_btc_signer7.OutScript.encode((0, import_btc_signer7.Address)(opts.network).decode(opts.beaconAddress));
|
|
5950
|
+
const changeKind = changeOutputKind(changeAddress, opts.network);
|
|
5951
|
+
const feeSats = await feeEstimator.estimateFee(beaconTxVsize("p2tr", changeKind));
|
|
4955
5952
|
if (BigInt(utxo.value) <= feeSats) {
|
|
4956
5953
|
throw new BeaconError(
|
|
4957
5954
|
`UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
@@ -4959,7 +5956,7 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
4959
5956
|
{ address: opts.beaconAddress, valueSats: utxo.value, feeSats }
|
|
4960
5957
|
);
|
|
4961
5958
|
}
|
|
4962
|
-
const tx = new
|
|
5959
|
+
const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
|
|
4963
5960
|
tx.addInput({
|
|
4964
5961
|
txid: utxo.txid,
|
|
4965
5962
|
index: utxo.vout,
|
|
@@ -4967,13 +5964,17 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
4967
5964
|
witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
|
|
4968
5965
|
tapInternalKey: opts.internalPubkey
|
|
4969
5966
|
});
|
|
4970
|
-
|
|
5967
|
+
const changeValue = BigInt(utxo.value) - feeSats;
|
|
5968
|
+
if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
|
|
5969
|
+
tx.addOutputAddress(changeAddress, changeValue, opts.network);
|
|
5970
|
+
}
|
|
4971
5971
|
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
4972
5972
|
return {
|
|
4973
5973
|
tx,
|
|
4974
5974
|
prevOutScripts: [witnessScript],
|
|
4975
5975
|
prevOutValues: [BigInt(utxo.value)],
|
|
4976
5976
|
beaconAddress: opts.beaconAddress,
|
|
5977
|
+
changeAddress,
|
|
4977
5978
|
utxo,
|
|
4978
5979
|
feeSats,
|
|
4979
5980
|
scriptKind: "p2tr"
|
|
@@ -4982,16 +5983,16 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
4982
5983
|
async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
|
|
4983
5984
|
const pubkey = signer.publicKey;
|
|
4984
5985
|
if (kind === "p2pkh") {
|
|
4985
|
-
const sighashType =
|
|
5986
|
+
const sighashType = import_btc_signer7.SigHash.ALL;
|
|
4986
5987
|
const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
|
|
4987
5988
|
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
4988
|
-
const sigWithType = (0,
|
|
5989
|
+
const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
4989
5990
|
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
4990
5991
|
tx.finalize();
|
|
4991
5992
|
return tx.hex;
|
|
4992
5993
|
}
|
|
4993
5994
|
if (kind === "p2wpkh") {
|
|
4994
|
-
const decoded =
|
|
5995
|
+
const decoded = import_btc_signer7.OutScript.decode(prevOutScript);
|
|
4995
5996
|
if (decoded.type !== "wpkh") {
|
|
4996
5997
|
throw new BeaconError(
|
|
4997
5998
|
`Expected P2WPKH prev-output script, got "${decoded.type}".`,
|
|
@@ -4999,16 +6000,16 @@ async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amo
|
|
|
4999
6000
|
{ kind, observedScriptType: decoded.type }
|
|
5000
6001
|
);
|
|
5001
6002
|
}
|
|
5002
|
-
const sighashScript =
|
|
5003
|
-
const sighashType =
|
|
6003
|
+
const sighashScript = import_btc_signer7.OutScript.encode({ type: "pkh", hash: decoded.hash });
|
|
6004
|
+
const sighashType = import_btc_signer7.SigHash.ALL;
|
|
5004
6005
|
const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
|
|
5005
6006
|
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
5006
|
-
const sigWithType = (0,
|
|
6007
|
+
const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
5007
6008
|
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
5008
6009
|
tx.finalize();
|
|
5009
6010
|
return tx.hex;
|
|
5010
6011
|
}
|
|
5011
|
-
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript],
|
|
6012
|
+
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer7.SigHash.DEFAULT, [amount]);
|
|
5012
6013
|
const sig = signer.sign(sighash, "bip341");
|
|
5013
6014
|
tx.updateInput(inputIdx, { tapKeySig: sig });
|
|
5014
6015
|
tx.finalize();
|
|
@@ -5029,7 +6030,7 @@ var SinglePartyBeacon = class {
|
|
|
5029
6030
|
*
|
|
5030
6031
|
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
5031
6032
|
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
5032
|
-
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
|
|
6033
|
+
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
|
|
5033
6034
|
* the multi-party path can't share the signing phase, but the tx-construction
|
|
5034
6035
|
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
5035
6036
|
*
|
|
@@ -5051,7 +6052,8 @@ var SinglePartyBeacon = class {
|
|
|
5051
6052
|
prevTxBytes,
|
|
5052
6053
|
signer,
|
|
5053
6054
|
bitcoin,
|
|
5054
|
-
feeEstimator
|
|
6055
|
+
feeEstimator,
|
|
6056
|
+
changeAddress: options?.changeAddress
|
|
5055
6057
|
});
|
|
5056
6058
|
const signedHex = await this.signSinglePartyTx(plan, signer);
|
|
5057
6059
|
return this.broadcastRawTx(bitcoin, signedHex);
|
|
@@ -5061,15 +6063,17 @@ var SinglePartyBeacon = class {
|
|
|
5061
6063
|
*
|
|
5062
6064
|
* Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
|
|
5063
6065
|
* the input accordingly. Validates that the signer's pubkey produces the beacon
|
|
5064
|
-
* address under that script kind
|
|
6066
|
+
* address under that script kind: without this check, a misconfigured caller
|
|
5065
6067
|
* would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
|
|
5066
|
-
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant
|
|
5067
|
-
* round-trip
|
|
6068
|
+
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant (via {@link beaconTxVsize}),
|
|
6069
|
+
* avoiding any probe-sign round-trip; a change address of a different kind re-sizes
|
|
6070
|
+
* the fee by the change output's size delta so it stays a valid upper bound.
|
|
5068
6071
|
*/
|
|
5069
6072
|
async buildSinglePartyTx(opts) {
|
|
5070
6073
|
const network = opts.bitcoin.data;
|
|
5071
6074
|
const pubkey = opts.signer.publicKey;
|
|
5072
6075
|
const kind = detectSingletonScriptKind(opts.beaconAddress, network);
|
|
6076
|
+
const changeAddress = resolveChangeAddress(opts.beaconAddress, network, opts.changeAddress);
|
|
5073
6077
|
const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
|
|
5074
6078
|
if (derivedAddress !== opts.beaconAddress) {
|
|
5075
6079
|
throw new BeaconError(
|
|
@@ -5078,7 +6082,8 @@ var SinglePartyBeacon = class {
|
|
|
5078
6082
|
{ kind, address: opts.beaconAddress, derivedAddress }
|
|
5079
6083
|
);
|
|
5080
6084
|
}
|
|
5081
|
-
const
|
|
6085
|
+
const changeKind = changeOutputKind(changeAddress, network);
|
|
6086
|
+
const feeSats = await opts.feeEstimator.estimateFee(beaconTxVsize(kind, changeKind));
|
|
5082
6087
|
const amount = BigInt(opts.utxo.value);
|
|
5083
6088
|
if (amount <= feeSats) {
|
|
5084
6089
|
throw new BeaconError(
|
|
@@ -5087,17 +6092,17 @@ var SinglePartyBeacon = class {
|
|
|
5087
6092
|
{ address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
|
|
5088
6093
|
);
|
|
5089
6094
|
}
|
|
5090
|
-
const tx = new
|
|
6095
|
+
const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
|
|
5091
6096
|
let prevOutScript;
|
|
5092
6097
|
if (kind === "p2pkh") {
|
|
5093
|
-
prevOutScript = (0,
|
|
6098
|
+
prevOutScript = (0, import_btc_signer7.p2pkh)(pubkey, network).script;
|
|
5094
6099
|
tx.addInput({
|
|
5095
6100
|
txid: opts.utxo.txid,
|
|
5096
6101
|
index: opts.utxo.vout,
|
|
5097
6102
|
nonWitnessUtxo: opts.prevTxBytes
|
|
5098
6103
|
});
|
|
5099
6104
|
} else if (kind === "p2wpkh") {
|
|
5100
|
-
prevOutScript = (0,
|
|
6105
|
+
prevOutScript = (0, import_btc_signer7.p2wpkh)(pubkey, network).script;
|
|
5101
6106
|
tx.addInput({
|
|
5102
6107
|
txid: opts.utxo.txid,
|
|
5103
6108
|
index: opts.utxo.vout,
|
|
@@ -5106,7 +6111,7 @@ var SinglePartyBeacon = class {
|
|
|
5106
6111
|
});
|
|
5107
6112
|
} else {
|
|
5108
6113
|
const internalKey = pubkey.slice(1, 33);
|
|
5109
|
-
prevOutScript = (0,
|
|
6114
|
+
prevOutScript = (0, import_btc_signer7.p2tr)(internalKey, void 0, network).script;
|
|
5110
6115
|
tx.addInput({
|
|
5111
6116
|
txid: opts.utxo.txid,
|
|
5112
6117
|
index: opts.utxo.vout,
|
|
@@ -5115,13 +6120,17 @@ var SinglePartyBeacon = class {
|
|
|
5115
6120
|
tapInternalKey: internalKey
|
|
5116
6121
|
});
|
|
5117
6122
|
}
|
|
5118
|
-
|
|
6123
|
+
const changeValue = amount - feeSats;
|
|
6124
|
+
if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
|
|
6125
|
+
tx.addOutputAddress(changeAddress, changeValue, network);
|
|
6126
|
+
}
|
|
5119
6127
|
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
5120
6128
|
return {
|
|
5121
6129
|
tx,
|
|
5122
6130
|
prevOutScripts: [prevOutScript],
|
|
5123
6131
|
prevOutValues: [amount],
|
|
5124
6132
|
beaconAddress: opts.beaconAddress,
|
|
6133
|
+
changeAddress,
|
|
5125
6134
|
utxo: opts.utxo,
|
|
5126
6135
|
feeSats,
|
|
5127
6136
|
scriptKind: kind
|
|
@@ -5295,7 +6304,7 @@ var SingletonBeacon = class extends SinglePartyBeacon {
|
|
|
5295
6304
|
// src/core/beacon/smt-beacon.ts
|
|
5296
6305
|
var import_common15 = require("@did-btcr2/common");
|
|
5297
6306
|
var import_smt3 = require("@did-btcr2/smt");
|
|
5298
|
-
var
|
|
6307
|
+
var import_utils11 = require("@noble/hashes/utils");
|
|
5299
6308
|
var SMTBeacon = class extends SinglePartyBeacon {
|
|
5300
6309
|
/**
|
|
5301
6310
|
* Creates an instance of SMTBeacon.
|
|
@@ -5384,7 +6393,7 @@ var SMTBeacon = class extends SinglePartyBeacon {
|
|
|
5384
6393
|
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
5385
6394
|
const did = this.service.id.split("#")[0];
|
|
5386
6395
|
const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
|
|
5387
|
-
const nonce = (0,
|
|
6396
|
+
const nonce = (0, import_utils11.randomBytes)(32);
|
|
5388
6397
|
const tree = new import_smt3.BTCR2MerkleTree();
|
|
5389
6398
|
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
5390
6399
|
tree.finalize();
|
|
@@ -5415,13 +6424,13 @@ var BeaconFactory = class {
|
|
|
5415
6424
|
};
|
|
5416
6425
|
|
|
5417
6426
|
// src/core/beacon/signal-discovery.ts
|
|
5418
|
-
var
|
|
6427
|
+
var import_bitcoin5 = require("@did-btcr2/bitcoin");
|
|
5419
6428
|
var import_common18 = require("@did-btcr2/common");
|
|
5420
6429
|
|
|
5421
6430
|
// src/core/beacon/utils.ts
|
|
5422
|
-
var
|
|
6431
|
+
var import_bitcoin4 = require("@did-btcr2/bitcoin");
|
|
5423
6432
|
var import_common17 = require("@did-btcr2/common");
|
|
5424
|
-
var
|
|
6433
|
+
var import_btc_signer8 = require("@scure/btc-signer");
|
|
5425
6434
|
|
|
5426
6435
|
// src/utils/appendix.ts
|
|
5427
6436
|
var import_dids = require("@web5/dids");
|
|
@@ -6526,10 +7535,10 @@ var BeaconUtils = class {
|
|
|
6526
7535
|
static createBeaconService(did, addressType, beaconType) {
|
|
6527
7536
|
try {
|
|
6528
7537
|
const components = Identifier.decode(did);
|
|
6529
|
-
const network = (0,
|
|
7538
|
+
const network = (0, import_bitcoin4.getNetwork)(components.network);
|
|
6530
7539
|
const pubkey = components.genesisBytes;
|
|
6531
7540
|
const id = `${did}#initial${addressType.toUpperCase()}`;
|
|
6532
|
-
const address = addressType === "p2tr" ? (0,
|
|
7541
|
+
const address = addressType === "p2tr" ? (0, import_btc_signer8.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer8.p2wpkh)(pubkey, network).address : (0, import_btc_signer8.p2pkh)(pubkey, network).address;
|
|
6533
7542
|
const serviceEndpoint = `bitcoin:${address}`;
|
|
6534
7543
|
return { id, type: beaconType, serviceEndpoint };
|
|
6535
7544
|
} catch (error) {
|
|
@@ -6548,9 +7557,9 @@ var BeaconUtils = class {
|
|
|
6548
7557
|
*/
|
|
6549
7558
|
static generateBeaconServices({ id, publicKey, network, beaconType }) {
|
|
6550
7559
|
try {
|
|
6551
|
-
const p2pkhAddr = (0,
|
|
6552
|
-
const p2wpkhAddr = (0,
|
|
6553
|
-
const p2trAddr = (0,
|
|
7560
|
+
const p2pkhAddr = (0, import_btc_signer8.p2pkh)(publicKey, network).address;
|
|
7561
|
+
const p2wpkhAddr = (0, import_btc_signer8.p2wpkh)(publicKey, network).address;
|
|
7562
|
+
const p2trAddr = (0, import_btc_signer8.p2tr)(publicKey.slice(1, 33), void 0, network).address;
|
|
6554
7563
|
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
6555
7564
|
throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
|
|
6556
7565
|
}
|
|
@@ -6677,14 +7686,14 @@ var BeaconSignalDiscovery = class {
|
|
|
6677
7686
|
console.info(`Searching for beacon signals, please wait ...`);
|
|
6678
7687
|
while (block.height <= targetHeight) {
|
|
6679
7688
|
for (const tx of block.tx) {
|
|
6680
|
-
if (tx.txid ===
|
|
7689
|
+
if (tx.txid === import_bitcoin5.GENESIS_TX_ID) {
|
|
6681
7690
|
continue;
|
|
6682
7691
|
}
|
|
6683
7692
|
for (const vin of tx.vin) {
|
|
6684
7693
|
if (vin.coinbase) {
|
|
6685
7694
|
continue;
|
|
6686
7695
|
}
|
|
6687
|
-
if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] ===
|
|
7696
|
+
if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin5.TXIN_WITNESS_COINBASE) {
|
|
6688
7697
|
continue;
|
|
6689
7698
|
}
|
|
6690
7699
|
if (!vin.txid) {
|
|
@@ -6738,7 +7747,7 @@ var BeaconSignalDiscovery = class {
|
|
|
6738
7747
|
};
|
|
6739
7748
|
|
|
6740
7749
|
// src/core/resolver.ts
|
|
6741
|
-
var
|
|
7750
|
+
var import_bitcoin7 = require("@did-btcr2/bitcoin");
|
|
6742
7751
|
var import_common22 = require("@did-btcr2/common");
|
|
6743
7752
|
var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
|
|
6744
7753
|
var import_keypair8 = require("@did-btcr2/keypair");
|
|
@@ -6752,11 +7761,11 @@ var import_common20 = require("@did-btcr2/common");
|
|
|
6752
7761
|
var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
|
|
6753
7762
|
|
|
6754
7763
|
// src/utils/did-document.ts
|
|
6755
|
-
var
|
|
7764
|
+
var import_bitcoin6 = require("@did-btcr2/bitcoin");
|
|
6756
7765
|
var import_common19 = require("@did-btcr2/common");
|
|
6757
7766
|
var import_keypair7 = require("@did-btcr2/keypair");
|
|
6758
|
-
var
|
|
6759
|
-
var
|
|
7767
|
+
var import_utils13 = require("@web5/dids/utils");
|
|
7768
|
+
var import_btc_signer9 = require("@scure/btc-signer");
|
|
6760
7769
|
var BTCR2_DID_DOCUMENT_CONTEXT = [
|
|
6761
7770
|
"https://www.w3.org/ns/did/v1.1",
|
|
6762
7771
|
"https://btcr2.dev/context/v1"
|
|
@@ -6965,7 +7974,7 @@ var DidDocument = class _DidDocument {
|
|
|
6965
7974
|
* @returns {boolean} True if the services are valid.
|
|
6966
7975
|
*/
|
|
6967
7976
|
static isValidServices(service) {
|
|
6968
|
-
return Array.isArray(service) && service.every(
|
|
7977
|
+
return Array.isArray(service) && service.every(import_utils13.isDidService);
|
|
6969
7978
|
}
|
|
6970
7979
|
/**
|
|
6971
7980
|
* Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
|
|
@@ -7078,7 +8087,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
7078
8087
|
static fromPublicKey(publicKey, network) {
|
|
7079
8088
|
const pk = new import_keypair7.CompressedSecp256k1PublicKey(publicKey);
|
|
7080
8089
|
const id = ID_PLACEHOLDER_VALUE;
|
|
7081
|
-
const address = (0,
|
|
8090
|
+
const address = (0, import_btc_signer9.p2pkh)(pk.compressed, (0, import_bitcoin6.getNetwork)(network)).address;
|
|
7082
8091
|
const services = [{
|
|
7083
8092
|
id: `${id}#service-0`,
|
|
7084
8093
|
serviceEndpoint: `bitcoin:${address}`,
|
|
@@ -7127,7 +8136,7 @@ var Updater = class _Updater {
|
|
|
7127
8136
|
#verificationMethod;
|
|
7128
8137
|
#beaconService;
|
|
7129
8138
|
/**
|
|
7130
|
-
* @internal
|
|
8139
|
+
* @internal Use {@link DidBtcr2.update} to create instances.
|
|
7131
8140
|
*/
|
|
7132
8141
|
constructor(params) {
|
|
7133
8142
|
this.#sourceDocument = params.sourceDocument;
|
|
@@ -7164,7 +8173,7 @@ var Updater = class _Updater {
|
|
|
7164
8173
|
const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
|
|
7165
8174
|
if (targetDocument.id !== sourceDocument.id) {
|
|
7166
8175
|
throw new import_common20.UpdateError(
|
|
7167
|
-
`Patches must not change the DID document id (source "${sourceDocument.id}"
|
|
8176
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
|
|
7168
8177
|
import_common20.INVALID_DID_UPDATE,
|
|
7169
8178
|
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
7170
8179
|
);
|
|
@@ -7248,8 +8257,8 @@ var Updater = class _Updater {
|
|
|
7248
8257
|
}
|
|
7249
8258
|
/**
|
|
7250
8259
|
* Advance the state machine. Returns either:
|
|
7251
|
-
* - `{ status: 'action-required', needs }`
|
|
7252
|
-
* - `{ status: 'complete', result }`
|
|
8260
|
+
* - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
|
|
8261
|
+
* - `{ status: 'complete', result }` update is signed and broadcast
|
|
7253
8262
|
*/
|
|
7254
8263
|
advance() {
|
|
7255
8264
|
while (true) {
|
|
@@ -7262,7 +8271,7 @@ var Updater = class _Updater {
|
|
|
7262
8271
|
continue;
|
|
7263
8272
|
}
|
|
7264
8273
|
// Phase: Sign
|
|
7265
|
-
// Emit NeedSigningKey
|
|
8274
|
+
// Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
|
|
7266
8275
|
case "Sign": {
|
|
7267
8276
|
return {
|
|
7268
8277
|
status: "action-required",
|
|
@@ -7445,8 +8454,8 @@ var DidBtcr2 = class {
|
|
|
7445
8454
|
*
|
|
7446
8455
|
* Factory method that validates the update parameters and returns a sans-I/O
|
|
7447
8456
|
* {@link Updater} state machine. The caller drives the updater through its
|
|
7448
|
-
* phases (Construct
|
|
7449
|
-
* `provide()`. The method package performs **zero I/O
|
|
8457
|
+
* phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
|
|
8458
|
+
* `provide()`. The method package performs **zero I/O**: signing key retrieval
|
|
7450
8459
|
* (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
|
|
7451
8460
|
*
|
|
7452
8461
|
* For a fully-wired version with Bitcoin broadcast and key handling, see
|
|
@@ -7545,7 +8554,7 @@ var DidBtcr2 = class {
|
|
|
7545
8554
|
};
|
|
7546
8555
|
|
|
7547
8556
|
// src/core/resolver.ts
|
|
7548
|
-
var
|
|
8557
|
+
var import_utils15 = require("@noble/curves/utils.js");
|
|
7549
8558
|
var Resolver = class _Resolver {
|
|
7550
8559
|
// --- Immutable inputs ---
|
|
7551
8560
|
#didComponents;
|
|
@@ -7564,7 +8573,7 @@ var Resolver = class _Resolver {
|
|
|
7564
8573
|
#unsortedUpdates = [];
|
|
7565
8574
|
#resolvedResponse = null;
|
|
7566
8575
|
/**
|
|
7567
|
-
* @internal
|
|
8576
|
+
* @internal Use {@link DidBtcr2.resolve} to create instances.
|
|
7568
8577
|
*/
|
|
7569
8578
|
constructor(didComponents, sidecarData, currentDocument, options) {
|
|
7570
8579
|
this.#didComponents = didComponents;
|
|
@@ -7589,7 +8598,7 @@ var Resolver = class _Resolver {
|
|
|
7589
8598
|
const service = BeaconUtils.generateBeaconServices({
|
|
7590
8599
|
id: did,
|
|
7591
8600
|
publicKey: genesisBytes,
|
|
7592
|
-
network: (0,
|
|
8601
|
+
network: (0, import_bitcoin7.getNetwork)(didComponents.network),
|
|
7593
8602
|
beaconType: "SingletonBeacon"
|
|
7594
8603
|
});
|
|
7595
8604
|
return new DidDocument({
|
|
@@ -7612,7 +8621,7 @@ var Resolver = class _Resolver {
|
|
|
7612
8621
|
*/
|
|
7613
8622
|
static external(didComponents, genesisDocument) {
|
|
7614
8623
|
const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
|
|
7615
|
-
if (!(0,
|
|
8624
|
+
if (!(0, import_utils15.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
7616
8625
|
throw new import_common22.ResolveError(
|
|
7617
8626
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
7618
8627
|
import_common22.INVALID_DID_DOCUMENT,
|
|
@@ -7689,7 +8698,7 @@ var Resolver = class _Resolver {
|
|
|
7689
8698
|
this.confirmDuplicate(update, updateHashHistory);
|
|
7690
8699
|
} else if (update.targetVersionId === currentVersionId + 1) {
|
|
7691
8700
|
const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
|
|
7692
|
-
if (!(0,
|
|
8701
|
+
if (!(0, import_utils15.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
7693
8702
|
throw new import_common22.ResolveError(
|
|
7694
8703
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
7695
8704
|
import_common22.INVALID_DID_UPDATE,
|
|
@@ -7736,7 +8745,7 @@ var Resolver = class _Resolver {
|
|
|
7736
8745
|
const { proof: _, ...unsignedUpdate } = update;
|
|
7737
8746
|
const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
|
|
7738
8747
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
7739
|
-
if (!(0,
|
|
8748
|
+
if (!(0, import_utils15.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
|
|
7740
8749
|
throw new import_common22.ResolveError(
|
|
7741
8750
|
`Invalid duplicate: unsigned update hash does not match historical hash`,
|
|
7742
8751
|
import_common22.LATE_PUBLISHING_ERROR,
|
|
@@ -7789,7 +8798,7 @@ var Resolver = class _Resolver {
|
|
|
7789
8798
|
DidDocument.validate(updatedDocument);
|
|
7790
8799
|
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
|
|
7791
8800
|
const updateTargetHash = (0, import_common22.decode)(update.targetHash);
|
|
7792
|
-
if (!(0,
|
|
8801
|
+
if (!(0, import_utils15.equalBytes)(updateTargetHash, currentDocumentHash)) {
|
|
7793
8802
|
throw new import_common22.ResolveError(
|
|
7794
8803
|
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
7795
8804
|
import_common22.INVALID_DID_UPDATE,
|
|
@@ -7800,8 +8809,8 @@ var Resolver = class _Resolver {
|
|
|
7800
8809
|
}
|
|
7801
8810
|
/**
|
|
7802
8811
|
* Advance the state machine. Returns either:
|
|
7803
|
-
* - `{ status: 'action-required', needs }`
|
|
7804
|
-
* - `{ status: 'resolved', result }`
|
|
8812
|
+
* - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
|
|
8813
|
+
* - `{ status: 'resolved', result }` - resolution complete
|
|
7805
8814
|
*
|
|
7806
8815
|
* Analogous to Rust's `Resolver::resolve()`.
|
|
7807
8816
|
*/
|
|
@@ -8031,6 +9040,7 @@ var DidDocumentBuilder = class {
|
|
|
8031
9040
|
BeaconUtils,
|
|
8032
9041
|
CASBeacon,
|
|
8033
9042
|
CASBeaconError,
|
|
9043
|
+
CHANGE_OUTPUT_VBYTES,
|
|
8034
9044
|
COHORT_ADVERT,
|
|
8035
9045
|
COHORT_OPT_IN,
|
|
8036
9046
|
COHORT_OPT_IN_ACCEPT,
|
|
@@ -8039,17 +9049,23 @@ var DidDocumentBuilder = class {
|
|
|
8039
9049
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
8040
9050
|
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
8041
9051
|
DEFAULT_CLOCK_SKEW_SEC,
|
|
9052
|
+
DEFAULT_FEE_ESTIMATOR,
|
|
9053
|
+
DEFAULT_FUNDING_MODEL,
|
|
8042
9054
|
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
8043
9055
|
DEFAULT_NONCE_LEN_BYTES,
|
|
8044
9056
|
DEFAULT_NOSTR_RELAYS,
|
|
9057
|
+
DEFAULT_RECOVERY_SEQUENCE,
|
|
8045
9058
|
DID_REGEX,
|
|
8046
9059
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
9060
|
+
DUST_LIMIT_SATS,
|
|
8047
9061
|
DidBtcr2,
|
|
8048
9062
|
DidCommTransport,
|
|
8049
9063
|
DidDocument,
|
|
8050
9064
|
DidDocumentBuilder,
|
|
8051
9065
|
DidVerificationMethod,
|
|
8052
9066
|
Document,
|
|
9067
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
9068
|
+
FALLBACK_SIGNATURE,
|
|
8053
9069
|
GenesisDocument,
|
|
8054
9070
|
HTTP_ENVELOPE_VERSION,
|
|
8055
9071
|
HTTP_ROUTE,
|
|
@@ -8063,7 +9079,9 @@ var DidDocumentBuilder = class {
|
|
|
8063
9079
|
InMemoryTransport,
|
|
8064
9080
|
InboxBuffer,
|
|
8065
9081
|
KNOWN_BEACON_TYPES,
|
|
9082
|
+
KNOWN_FUNDING_MODELS,
|
|
8066
9083
|
KeyPairAggregationSigner,
|
|
9084
|
+
MAX_RECOVERY_SEQUENCE,
|
|
8067
9085
|
NONCE_CONTRIBUTION,
|
|
8068
9086
|
NonceCache,
|
|
8069
9087
|
NostrTransport,
|
|
@@ -8080,6 +9098,7 @@ var DidDocumentBuilder = class {
|
|
|
8080
9098
|
SMTBeacon,
|
|
8081
9099
|
SMTBeaconError,
|
|
8082
9100
|
SSE_EVENT,
|
|
9101
|
+
SUBMIT_NONINCLUDED,
|
|
8083
9102
|
SUBMIT_UPDATE,
|
|
8084
9103
|
ServiceCohortPhase,
|
|
8085
9104
|
SigningSessionError,
|
|
@@ -8088,13 +9107,20 @@ var DidDocumentBuilder = class {
|
|
|
8088
9107
|
SingletonBeacon,
|
|
8089
9108
|
SingletonBeaconError,
|
|
8090
9109
|
StaticFeeEstimator,
|
|
9110
|
+
TAPROOT_LEAF_VERSION,
|
|
8091
9111
|
TransportAdapterError,
|
|
8092
9112
|
TransportError,
|
|
8093
9113
|
TransportFactory,
|
|
8094
9114
|
TypedEventEmitter,
|
|
8095
9115
|
Updater,
|
|
8096
9116
|
VALIDATION_ACK,
|
|
9117
|
+
beaconTxVsize,
|
|
8097
9118
|
buildAggregationBeaconTx,
|
|
9119
|
+
buildFallbackLeaf,
|
|
9120
|
+
buildFallbackSpend,
|
|
9121
|
+
buildRecoveryLeaves,
|
|
9122
|
+
buildRecoveryScript,
|
|
9123
|
+
buildRecoverySpend,
|
|
8098
9124
|
buildRequestAuth,
|
|
8099
9125
|
createAggregatedNonceMessage,
|
|
8100
9126
|
createAuthorizationRequestMessage,
|
|
@@ -8103,13 +9129,17 @@ var DidDocumentBuilder = class {
|
|
|
8103
9129
|
createCohortOptInMessage,
|
|
8104
9130
|
createCohortReadyMessage,
|
|
8105
9131
|
createDistributeAggregatedDataMessage,
|
|
9132
|
+
createFallbackAuthorizationRequestMessage,
|
|
9133
|
+
createFallbackSignatureMessage,
|
|
8106
9134
|
createNonceContributionMessage,
|
|
8107
9135
|
createSignatureAuthorizationMessage,
|
|
9136
|
+
createSubmitNonIncludedMessage,
|
|
8108
9137
|
createSubmitUpdateMessage,
|
|
8109
9138
|
createValidationAckMessage,
|
|
8110
9139
|
defaultReconnectBackoff,
|
|
8111
9140
|
deriveSingletonAddress,
|
|
8112
9141
|
detectSingletonScriptKind,
|
|
9142
|
+
fallbackSighash,
|
|
8113
9143
|
formatSseComment,
|
|
8114
9144
|
formatSseEvent,
|
|
8115
9145
|
getBeaconStrategy,
|
|
@@ -8121,10 +9151,13 @@ var DidDocumentBuilder = class {
|
|
|
8121
9151
|
isCohortOptInMessage,
|
|
8122
9152
|
isCohortReadyMessage,
|
|
8123
9153
|
isDistributeAggregatedDataMessage,
|
|
9154
|
+
isFallbackAuthorizationRequestMessage,
|
|
9155
|
+
isFallbackSignatureMessage,
|
|
8124
9156
|
isKeygenMessageType,
|
|
8125
9157
|
isNonceContributionMessage,
|
|
8126
9158
|
isSignMessageType,
|
|
8127
9159
|
isSignatureAuthorizationMessage,
|
|
9160
|
+
isSubmitNonIncludedMessage,
|
|
8128
9161
|
isSubmitUpdateMessage,
|
|
8129
9162
|
isUpdateMessageType,
|
|
8130
9163
|
isValidationAckMessage,
|
|
@@ -8133,8 +9166,11 @@ var DidDocumentBuilder = class {
|
|
|
8133
9166
|
parseRequestAuth,
|
|
8134
9167
|
parseSseStream,
|
|
8135
9168
|
registerBeaconStrategy,
|
|
9169
|
+
resolveChangeAddress,
|
|
9170
|
+
resolveFallbackThreshold,
|
|
8136
9171
|
reviveFromWire,
|
|
8137
9172
|
signEnvelope,
|
|
9173
|
+
tapLeafHash,
|
|
8138
9174
|
validateCohortConditions,
|
|
8139
9175
|
verifyEnvelope,
|
|
8140
9176
|
verifyRequestAuth
|