@did-btcr2/method 0.36.0 → 0.37.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 +1371 -260
- package/dist/browser.mjs +1371 -260
- package/dist/cjs/index.js +1464 -343
- 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 +218 -18
- 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 +11 -3
- package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +106 -19
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +409 -195
- 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 +15 -10
- 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/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 +47 -4
- 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 +13 -3
- package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +48 -16
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +23 -14
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +102 -23
- 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 +7 -7
- 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/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 +264 -20
- 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 +27 -5
- package/src/core/aggregation/runner/events.ts +47 -19
- package/src/core/aggregation/runner/participant-runner.ts +113 -31
- package/src/core/aggregation/runner/service-runner.ts +484 -203
- 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 +16 -11
- package/src/core/beacon/cas-beacon.ts +7 -7
- 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
|
@@ -63,9 +63,11 @@ __export(index_exports, {
|
|
|
63
63
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
64
64
|
DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
65
65
|
DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
|
|
66
|
+
DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
|
|
66
67
|
DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
67
68
|
DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
|
|
68
69
|
DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
|
|
70
|
+
DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
|
|
69
71
|
DID_REGEX: () => DID_REGEX,
|
|
70
72
|
DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
|
|
71
73
|
DidBtcr2: () => DidBtcr2,
|
|
@@ -74,6 +76,8 @@ __export(index_exports, {
|
|
|
74
76
|
DidDocumentBuilder: () => DidDocumentBuilder,
|
|
75
77
|
DidVerificationMethod: () => DidVerificationMethod,
|
|
76
78
|
Document: () => Document,
|
|
79
|
+
FALLBACK_AUTHORIZATION_REQUEST: () => FALLBACK_AUTHORIZATION_REQUEST,
|
|
80
|
+
FALLBACK_SIGNATURE: () => FALLBACK_SIGNATURE,
|
|
77
81
|
GenesisDocument: () => GenesisDocument,
|
|
78
82
|
HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
|
|
79
83
|
HTTP_ROUTE: () => HTTP_ROUTE,
|
|
@@ -87,7 +91,9 @@ __export(index_exports, {
|
|
|
87
91
|
InMemoryTransport: () => InMemoryTransport,
|
|
88
92
|
InboxBuffer: () => InboxBuffer,
|
|
89
93
|
KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
|
|
94
|
+
KNOWN_FUNDING_MODELS: () => KNOWN_FUNDING_MODELS,
|
|
90
95
|
KeyPairAggregationSigner: () => KeyPairAggregationSigner,
|
|
96
|
+
MAX_RECOVERY_SEQUENCE: () => MAX_RECOVERY_SEQUENCE,
|
|
91
97
|
NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
|
|
92
98
|
NonceCache: () => NonceCache,
|
|
93
99
|
NostrTransport: () => NostrTransport,
|
|
@@ -104,6 +110,7 @@ __export(index_exports, {
|
|
|
104
110
|
SMTBeacon: () => SMTBeacon,
|
|
105
111
|
SMTBeaconError: () => SMTBeaconError,
|
|
106
112
|
SSE_EVENT: () => SSE_EVENT,
|
|
113
|
+
SUBMIT_NONINCLUDED: () => SUBMIT_NONINCLUDED,
|
|
107
114
|
SUBMIT_UPDATE: () => SUBMIT_UPDATE,
|
|
108
115
|
ServiceCohortPhase: () => ServiceCohortPhase,
|
|
109
116
|
SigningSessionError: () => SigningSessionError,
|
|
@@ -112,6 +119,7 @@ __export(index_exports, {
|
|
|
112
119
|
SingletonBeacon: () => SingletonBeacon,
|
|
113
120
|
SingletonBeaconError: () => SingletonBeaconError,
|
|
114
121
|
StaticFeeEstimator: () => StaticFeeEstimator,
|
|
122
|
+
TAPROOT_LEAF_VERSION: () => TAPROOT_LEAF_VERSION,
|
|
115
123
|
TransportAdapterError: () => TransportAdapterError,
|
|
116
124
|
TransportError: () => TransportError,
|
|
117
125
|
TransportFactory: () => TransportFactory,
|
|
@@ -119,6 +127,11 @@ __export(index_exports, {
|
|
|
119
127
|
Updater: () => Updater,
|
|
120
128
|
VALIDATION_ACK: () => VALIDATION_ACK,
|
|
121
129
|
buildAggregationBeaconTx: () => buildAggregationBeaconTx,
|
|
130
|
+
buildFallbackLeaf: () => buildFallbackLeaf,
|
|
131
|
+
buildFallbackSpend: () => buildFallbackSpend,
|
|
132
|
+
buildRecoveryLeaves: () => buildRecoveryLeaves,
|
|
133
|
+
buildRecoveryScript: () => buildRecoveryScript,
|
|
134
|
+
buildRecoverySpend: () => buildRecoverySpend,
|
|
122
135
|
buildRequestAuth: () => buildRequestAuth,
|
|
123
136
|
createAggregatedNonceMessage: () => createAggregatedNonceMessage,
|
|
124
137
|
createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
|
|
@@ -127,13 +140,17 @@ __export(index_exports, {
|
|
|
127
140
|
createCohortOptInMessage: () => createCohortOptInMessage,
|
|
128
141
|
createCohortReadyMessage: () => createCohortReadyMessage,
|
|
129
142
|
createDistributeAggregatedDataMessage: () => createDistributeAggregatedDataMessage,
|
|
143
|
+
createFallbackAuthorizationRequestMessage: () => createFallbackAuthorizationRequestMessage,
|
|
144
|
+
createFallbackSignatureMessage: () => createFallbackSignatureMessage,
|
|
130
145
|
createNonceContributionMessage: () => createNonceContributionMessage,
|
|
131
146
|
createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
|
|
147
|
+
createSubmitNonIncludedMessage: () => createSubmitNonIncludedMessage,
|
|
132
148
|
createSubmitUpdateMessage: () => createSubmitUpdateMessage,
|
|
133
149
|
createValidationAckMessage: () => createValidationAckMessage,
|
|
134
150
|
defaultReconnectBackoff: () => defaultReconnectBackoff,
|
|
135
151
|
deriveSingletonAddress: () => deriveSingletonAddress,
|
|
136
152
|
detectSingletonScriptKind: () => detectSingletonScriptKind,
|
|
153
|
+
fallbackSighash: () => fallbackSighash,
|
|
137
154
|
formatSseComment: () => formatSseComment,
|
|
138
155
|
formatSseEvent: () => formatSseEvent,
|
|
139
156
|
getBeaconStrategy: () => getBeaconStrategy,
|
|
@@ -145,10 +162,13 @@ __export(index_exports, {
|
|
|
145
162
|
isCohortOptInMessage: () => isCohortOptInMessage,
|
|
146
163
|
isCohortReadyMessage: () => isCohortReadyMessage,
|
|
147
164
|
isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
|
|
165
|
+
isFallbackAuthorizationRequestMessage: () => isFallbackAuthorizationRequestMessage,
|
|
166
|
+
isFallbackSignatureMessage: () => isFallbackSignatureMessage,
|
|
148
167
|
isKeygenMessageType: () => isKeygenMessageType,
|
|
149
168
|
isNonceContributionMessage: () => isNonceContributionMessage,
|
|
150
169
|
isSignMessageType: () => isSignMessageType,
|
|
151
170
|
isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
|
|
171
|
+
isSubmitNonIncludedMessage: () => isSubmitNonIncludedMessage,
|
|
152
172
|
isSubmitUpdateMessage: () => isSubmitUpdateMessage,
|
|
153
173
|
isUpdateMessageType: () => isUpdateMessageType,
|
|
154
174
|
isValidationAckMessage: () => isValidationAckMessage,
|
|
@@ -157,8 +177,10 @@ __export(index_exports, {
|
|
|
157
177
|
parseRequestAuth: () => parseRequestAuth,
|
|
158
178
|
parseSseStream: () => parseSseStream,
|
|
159
179
|
registerBeaconStrategy: () => registerBeaconStrategy,
|
|
180
|
+
resolveFallbackThreshold: () => resolveFallbackThreshold,
|
|
160
181
|
reviveFromWire: () => reviveFromWire,
|
|
161
182
|
signEnvelope: () => signEnvelope,
|
|
183
|
+
tapLeafHash: () => tapLeafHash,
|
|
162
184
|
validateCohortConditions: () => validateCohortConditions,
|
|
163
185
|
verifyEnvelope: () => verifyEnvelope,
|
|
164
186
|
verifyRequestAuth: () => verifyRequestAuth
|
|
@@ -168,7 +190,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
168
190
|
// src/core/aggregation/service.ts
|
|
169
191
|
var import_common4 = require("@did-btcr2/common");
|
|
170
192
|
var import_cryptosuite = require("@did-btcr2/cryptosuite");
|
|
171
|
-
var
|
|
193
|
+
var import_secp256k14 = require("@noble/curves/secp256k1.js");
|
|
194
|
+
var import_utils3 = require("@noble/hashes/utils");
|
|
172
195
|
|
|
173
196
|
// src/core/aggregation/beacon-strategy.ts
|
|
174
197
|
var import_common = require("@did-btcr2/common");
|
|
@@ -181,9 +204,12 @@ var CAS_STRATEGY = {
|
|
|
181
204
|
getDistributePayload(cohort) {
|
|
182
205
|
return { casAnnouncement: cohort.casAnnouncement };
|
|
183
206
|
},
|
|
184
|
-
validateParticipantView({ participantDid, expectedHash, body }) {
|
|
207
|
+
validateParticipantView({ participantDid, included, expectedHash, body }) {
|
|
185
208
|
const casAnnouncement = body.casAnnouncement;
|
|
186
209
|
if (!casAnnouncement) return { matches: false };
|
|
210
|
+
if (!included) {
|
|
211
|
+
return { matches: !(participantDid in casAnnouncement), casAnnouncement };
|
|
212
|
+
}
|
|
187
213
|
return {
|
|
188
214
|
matches: casAnnouncement[participantDid] === expectedHash,
|
|
189
215
|
casAnnouncement
|
|
@@ -199,15 +225,20 @@ var SMT_STRATEGY = {
|
|
|
199
225
|
const proof = cohort.smtProofs?.get(participantDid);
|
|
200
226
|
return { smtProof: proof };
|
|
201
227
|
},
|
|
202
|
-
validateParticipantView({ participantDid, submittedUpdate, body }) {
|
|
228
|
+
validateParticipantView({ participantDid, included, submittedUpdate, body }) {
|
|
203
229
|
const smtProof = body.smtProof;
|
|
204
|
-
|
|
230
|
+
const index = (0, import_smt.didToIndex)(participantDid);
|
|
231
|
+
if (!included) {
|
|
232
|
+
if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
|
|
233
|
+
const candidateHash2 = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)));
|
|
234
|
+
return { matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash2), smtProof };
|
|
235
|
+
}
|
|
236
|
+
if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
|
|
205
237
|
const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
|
|
206
238
|
const expectedUpdateId = (0, import_smt.hashToBase64Url)((0, import_smt.blockHash)(canonicalBytes));
|
|
207
239
|
if (smtProof.updateId !== expectedUpdateId) {
|
|
208
240
|
return { matches: false, smtProof };
|
|
209
241
|
}
|
|
210
|
-
const index = (0, import_smt.didToIndex)(participantDid);
|
|
211
242
|
const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)), (0, import_smt.base64UrlToHash)(smtProof.updateId));
|
|
212
243
|
return {
|
|
213
244
|
matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
|
|
@@ -230,10 +261,10 @@ function getBeaconStrategy(type) {
|
|
|
230
261
|
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
231
262
|
var import_common3 = require("@did-btcr2/common");
|
|
232
263
|
var import_smt2 = require("@did-btcr2/smt");
|
|
233
|
-
var
|
|
234
|
-
var
|
|
235
|
-
var
|
|
236
|
-
var
|
|
264
|
+
var import_secp256k12 = require("@noble/curves/secp256k1.js");
|
|
265
|
+
var import_utils2 = require("@noble/hashes/utils");
|
|
266
|
+
var import_btc_signer2 = require("@scure/btc-signer");
|
|
267
|
+
var import_musig22 = require("@scure/btc-signer/musig2");
|
|
237
268
|
|
|
238
269
|
// src/core/aggregation/errors.ts
|
|
239
270
|
var import_common2 = require("@did-btcr2/common");
|
|
@@ -258,6 +289,105 @@ var SigningSessionError = class extends import_common2.MethodError {
|
|
|
258
289
|
}
|
|
259
290
|
};
|
|
260
291
|
|
|
292
|
+
// src/core/aggregation/recovery-policy.ts
|
|
293
|
+
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
294
|
+
var import_utils = require("@noble/hashes/utils");
|
|
295
|
+
var import_btc_signer = require("@scure/btc-signer");
|
|
296
|
+
var import_musig2 = require("@scure/btc-signer/musig2");
|
|
297
|
+
var DEFAULT_FUNDING_MODEL = "operator-funded";
|
|
298
|
+
var DEFAULT_RECOVERY_SEQUENCE = 144;
|
|
299
|
+
var MAX_RECOVERY_SEQUENCE = 65535;
|
|
300
|
+
var TAPROOT_LEAF_VERSION = 192;
|
|
301
|
+
function compactSize(n) {
|
|
302
|
+
if (n < 253) return new Uint8Array([n]);
|
|
303
|
+
if (n <= 65535) return new Uint8Array([253, n & 255, n >> 8 & 255]);
|
|
304
|
+
return new Uint8Array([254, n & 255, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255]);
|
|
305
|
+
}
|
|
306
|
+
function tapLeafHash(script, leafVersion = TAPROOT_LEAF_VERSION) {
|
|
307
|
+
return import_secp256k1.schnorr.utils.taggedHash(
|
|
308
|
+
"TapLeaf",
|
|
309
|
+
(0, import_utils.concatBytes)(new Uint8Array([leafVersion]), compactSize(script.length), script)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
function resolveFallbackThreshold(advertised, n) {
|
|
313
|
+
return advertised ?? Math.max(1, n - 1);
|
|
314
|
+
}
|
|
315
|
+
function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
|
|
316
|
+
const n = cohortKeys.length;
|
|
317
|
+
if (n === 0) {
|
|
318
|
+
throw new AggregationCohortError(
|
|
319
|
+
"Cannot build fallback leaf: no cohort keys.",
|
|
320
|
+
"NO_COHORT_KEYS"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
for (const key of cohortKeys) {
|
|
324
|
+
if (key.length !== 33) {
|
|
325
|
+
throw new AggregationCohortError(
|
|
326
|
+
`Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
|
|
327
|
+
"INVALID_COHORT_KEY",
|
|
328
|
+
{ length: key.length }
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n) {
|
|
333
|
+
throw new AggregationCohortError(
|
|
334
|
+
`Fallback threshold must be an integer in [1, ${n}] (k-of-n), got ${fallbackThreshold}.`,
|
|
335
|
+
"INVALID_FALLBACK_THRESHOLD",
|
|
336
|
+
{ fallbackThreshold, n }
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function buildFallbackLeaf(params) {
|
|
341
|
+
assertFallbackParams(params);
|
|
342
|
+
const xOnlyKeys = (0, import_musig2.sortKeys)(params.cohortKeys).map((k) => k.slice(1));
|
|
343
|
+
return (0, import_btc_signer.p2tr_ms)(params.fallbackThreshold, xOnlyKeys).script;
|
|
344
|
+
}
|
|
345
|
+
function assertRecoveryParams({ recoveryKey, recoverySequence }) {
|
|
346
|
+
if (recoveryKey.length !== 32) {
|
|
347
|
+
throw new AggregationCohortError(
|
|
348
|
+
`Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
|
|
349
|
+
"INVALID_RECOVERY_KEY"
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
353
|
+
throw new AggregationCohortError(
|
|
354
|
+
`Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
|
|
355
|
+
"INVALID_RECOVERY_SEQUENCE"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function buildRecoveryScript(params) {
|
|
360
|
+
assertRecoveryParams(params);
|
|
361
|
+
return import_btc_signer.Script.encode([
|
|
362
|
+
params.recoverySequence,
|
|
363
|
+
"CHECKSEQUENCEVERIFY",
|
|
364
|
+
"DROP",
|
|
365
|
+
params.recoveryKey,
|
|
366
|
+
"CHECKSIG"
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
function buildRecoveryLeaves(fundingModel, params) {
|
|
370
|
+
switch (fundingModel) {
|
|
371
|
+
case "operator-funded":
|
|
372
|
+
return [
|
|
373
|
+
{ script: buildFallbackLeaf(params) },
|
|
374
|
+
{ script: buildRecoveryScript(params) }
|
|
375
|
+
];
|
|
376
|
+
case "participant-funded":
|
|
377
|
+
throw new AggregationCohortError(
|
|
378
|
+
"Funding model 'participant-funded' is reserved and not yet implemented.",
|
|
379
|
+
"UNSUPPORTED_FUNDING_MODEL",
|
|
380
|
+
{ fundingModel }
|
|
381
|
+
);
|
|
382
|
+
default:
|
|
383
|
+
throw new AggregationCohortError(
|
|
384
|
+
`Unknown funding model: ${fundingModel}.`,
|
|
385
|
+
"UNKNOWN_FUNDING_MODEL",
|
|
386
|
+
{ fundingModel }
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
261
391
|
// src/core/aggregation/cohort.ts
|
|
262
392
|
var AggregationCohort = class {
|
|
263
393
|
/** Unique identifier for the cohort. */
|
|
@@ -273,8 +403,8 @@ var AggregationCohort = class {
|
|
|
273
403
|
/** List of participant DIDs that have been accepted into the cohort. */
|
|
274
404
|
participants = [];
|
|
275
405
|
/**
|
|
276
|
-
* Mapping from participant DID
|
|
277
|
-
* Distinct from {@link cohortKeys} (which is sorted per BIP-327)
|
|
406
|
+
* Mapping from participant DID to their compressed secp256k1 public key.
|
|
407
|
+
* Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
|
|
278
408
|
* callers look up a participant's key without knowing their position in the
|
|
279
409
|
* sorted array. Populated by the service at `acceptParticipant` time.
|
|
280
410
|
*/
|
|
@@ -282,16 +412,41 @@ var AggregationCohort = class {
|
|
|
282
412
|
/** Sorted list of cohort participants' compressed public keys. */
|
|
283
413
|
#cohortKeys = [];
|
|
284
414
|
/**
|
|
285
|
-
* BIP-341 TapTweak
|
|
286
|
-
*
|
|
287
|
-
*
|
|
415
|
+
* BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
|
|
416
|
+
* The beacon output is an internal key (the MuSig2 aggregate) plus a script
|
|
417
|
+
* tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
|
|
418
|
+
* The MuSig2 signing session applies this as an x-only tweak; a value that does
|
|
419
|
+
* not match the root the funded address was derived from silently yields an
|
|
420
|
+
* invalid key-path signature.
|
|
288
421
|
*/
|
|
289
422
|
tapTweak = new Uint8Array();
|
|
290
|
-
/** The n-of-n MuSig2
|
|
423
|
+
/** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
|
|
424
|
+
internalKey = new Uint8Array();
|
|
425
|
+
/** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
|
|
426
|
+
tapMerkleRoot = new Uint8Array();
|
|
427
|
+
/** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
|
|
428
|
+
recoveryKey;
|
|
429
|
+
/** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
|
|
430
|
+
recoverySequence;
|
|
431
|
+
/** Funding model governing the recovery leaves (default 'operator-funded'). */
|
|
432
|
+
fundingModel;
|
|
433
|
+
/**
|
|
434
|
+
* Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
|
|
435
|
+
* address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
|
|
436
|
+
*/
|
|
437
|
+
fallbackThreshold;
|
|
438
|
+
/** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
|
|
291
439
|
beaconAddress = "";
|
|
292
440
|
/** Pending DID updates submitted by participants, keyed by DID. */
|
|
293
441
|
pendingUpdates = /* @__PURE__ */ new Map();
|
|
294
|
-
/**
|
|
442
|
+
/**
|
|
443
|
+
* Participant DIDs that explicitly declined to submit an update this round
|
|
444
|
+
* (cooperative non-inclusion). A decliner is absent from the CAS Announcement
|
|
445
|
+
* Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
|
|
446
|
+
* disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
|
|
447
|
+
*/
|
|
448
|
+
nonIncluded = /* @__PURE__ */ new Set();
|
|
449
|
+
/** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
|
|
295
450
|
casAnnouncement;
|
|
296
451
|
/** Per-participant SMT proofs, set by buildSMTTree(). */
|
|
297
452
|
smtProofs;
|
|
@@ -301,23 +456,45 @@ var AggregationCohort = class {
|
|
|
301
456
|
validationAcks = /* @__PURE__ */ new Set();
|
|
302
457
|
/** Set of participant DIDs that have rejected the aggregated data. */
|
|
303
458
|
validationRejections = /* @__PURE__ */ new Set();
|
|
304
|
-
constructor({ id, minParticipants, serviceDid, network, beaconType }) {
|
|
459
|
+
constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
|
|
305
460
|
this.id = id || crypto.randomUUID();
|
|
306
461
|
this.minParticipants = minParticipants ?? 2;
|
|
307
462
|
this.serviceDid = serviceDid || "";
|
|
308
463
|
this.network = network;
|
|
309
464
|
this.beaconType = beaconType || "CASBeacon";
|
|
465
|
+
this.recoveryKey = recoveryKey;
|
|
466
|
+
this.recoverySequence = recoverySequence;
|
|
467
|
+
this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
|
|
468
|
+
this.fallbackThreshold = fallbackThreshold;
|
|
310
469
|
}
|
|
311
470
|
/** Sorted cohort keys (sorted on assignment per BIP-327). */
|
|
312
471
|
get cohortKeys() {
|
|
313
472
|
return this.#cohortKeys;
|
|
314
473
|
}
|
|
315
474
|
set cohortKeys(keys) {
|
|
316
|
-
this.#cohortKeys = (0,
|
|
475
|
+
this.#cohortKeys = (0, import_musig22.sortKeys)(keys);
|
|
317
476
|
}
|
|
318
477
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
478
|
+
* The resolved k of the k-of-n fallback leaf: the advertised
|
|
479
|
+
* {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
|
|
480
|
+
* the current cohort size. This is the value the beacon address commits to and
|
|
481
|
+
* the spend builders must reproduce. Returns 0 before any cohort keys are set.
|
|
482
|
+
*/
|
|
483
|
+
get effectiveFallbackThreshold() {
|
|
484
|
+
if (this.#cohortKeys.length === 0) return 0;
|
|
485
|
+
return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Computes the Taproot beacon address from the cohort keys and recovery params.
|
|
489
|
+
*
|
|
490
|
+
* The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
|
|
491
|
+
* script path is the recovery tree (a CSV recovery leaf so a missing signer
|
|
492
|
+
* cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
|
|
493
|
+
* the `tapTweak` the MuSig2 session must apply for the key-path spend.
|
|
494
|
+
*
|
|
495
|
+
* The tweak is derived from the Merkle root the address was built with (read
|
|
496
|
+
* back from the payment), never recomputed by hand, so the MuSig2 key-path
|
|
497
|
+
* signature is guaranteed to validate against the funded address.
|
|
321
498
|
*/
|
|
322
499
|
computeBeaconAddress() {
|
|
323
500
|
if (this.#cohortKeys.length === 0) {
|
|
@@ -327,10 +504,25 @@ var AggregationCohort = class {
|
|
|
327
504
|
{ cohortId: this.id }
|
|
328
505
|
);
|
|
329
506
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
507
|
+
if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
|
|
508
|
+
throw new AggregationCohortError(
|
|
509
|
+
"Cannot compute beacon address: missing recovery key or sequence.",
|
|
510
|
+
"NO_RECOVERY_PARAMS",
|
|
511
|
+
{ cohortId: this.id }
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const keyAggContext = (0, import_musig22.keyAggregate)(this.#cohortKeys);
|
|
515
|
+
const aggPubkey = (0, import_musig22.keyAggExport)(keyAggContext);
|
|
516
|
+
const leaves = buildRecoveryLeaves(this.fundingModel, {
|
|
517
|
+
recoveryKey: this.recoveryKey,
|
|
518
|
+
recoverySequence: this.recoverySequence,
|
|
519
|
+
cohortKeys: this.#cohortKeys,
|
|
520
|
+
fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
|
|
521
|
+
});
|
|
522
|
+
const payment = (0, import_btc_signer2.p2tr)(aggPubkey, leaves, (0, import_bitcoin.getNetwork)(this.network), true);
|
|
523
|
+
this.internalKey = aggPubkey;
|
|
524
|
+
this.tapMerkleRoot = payment.tapMerkleRoot;
|
|
525
|
+
this.tapTweak = import_secp256k12.schnorr.utils.taggedHash("TapTweak", (0, import_utils2.concatBytes)(aggPubkey, payment.tapMerkleRoot));
|
|
334
526
|
if (!payment.address) {
|
|
335
527
|
throw new AggregationCohortError(
|
|
336
528
|
"Failed to compute Taproot address",
|
|
@@ -354,7 +546,7 @@ var AggregationCohort = class {
|
|
|
354
546
|
{ cohortId: this.id, participantPkHex }
|
|
355
547
|
);
|
|
356
548
|
}
|
|
357
|
-
this.cohortKeys = cohortKeysHex.map((k) => (0,
|
|
549
|
+
this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils2.hexToBytes)(k));
|
|
358
550
|
const computed = this.computeBeaconAddress();
|
|
359
551
|
if (computed !== expectedBeaconAddress) {
|
|
360
552
|
throw new AggregationCohortError(
|
|
@@ -386,20 +578,54 @@ var AggregationCohort = class {
|
|
|
386
578
|
}
|
|
387
579
|
this.pendingUpdates.set(participantDid, signedUpdate);
|
|
388
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Record that a participant declined to submit an update this round
|
|
583
|
+
* (cooperative non-inclusion). The member stays in the cohort and still signs.
|
|
584
|
+
*/
|
|
585
|
+
addNonInclusion(participantDid) {
|
|
586
|
+
if (!this.participants.includes(participantDid)) {
|
|
587
|
+
throw new AggregationCohortError(
|
|
588
|
+
`Participant ${participantDid} is not in cohort ${this.id}.`,
|
|
589
|
+
"UNKNOWN_PARTICIPANT",
|
|
590
|
+
{ cohortId: this.id, participantDid }
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
if (this.pendingUpdates.has(participantDid)) {
|
|
594
|
+
throw new AggregationCohortError(
|
|
595
|
+
`Participant ${participantDid} already submitted an update; cannot also decline.`,
|
|
596
|
+
"CONFLICTING_RESPONSE",
|
|
597
|
+
{ cohortId: this.id, participantDid }
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
this.nonIncluded.add(participantDid);
|
|
601
|
+
}
|
|
389
602
|
hasAllUpdates() {
|
|
390
603
|
return this.pendingUpdates.size === this.participants.length;
|
|
391
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* True when every participant has responded for this round, either with an
|
|
607
|
+
* update or with an explicit non-inclusion. This is the aggregation gate when
|
|
608
|
+
* non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
|
|
609
|
+
* {@link hasAllValidationResponses} generalizes a unanimous ack.
|
|
610
|
+
*/
|
|
611
|
+
hasAllResponses() {
|
|
612
|
+
return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
|
|
613
|
+
}
|
|
392
614
|
/**
|
|
393
615
|
* Builds a CAS Announcement Map from collected updates.
|
|
394
|
-
* Maps each participant DID
|
|
616
|
+
* Maps each participant DID to base64url canonical hash of their signed update.
|
|
395
617
|
* Computes signal bytes as SHA-256 of canonicalized announcement.
|
|
618
|
+
*
|
|
619
|
+
* Members who declined (cooperative non-inclusion) are naturally absent from
|
|
620
|
+
* the map: the body iterates {@link pendingUpdates}, which never holds a
|
|
621
|
+
* decliner. Absence from the map is exactly the CAS non-inclusion signal.
|
|
396
622
|
*/
|
|
397
623
|
buildCASAnnouncement() {
|
|
398
|
-
if (!this.
|
|
624
|
+
if (!this.hasAllResponses()) {
|
|
399
625
|
throw new AggregationCohortError(
|
|
400
|
-
"Cannot build CAS Announcement: not all
|
|
401
|
-
"
|
|
402
|
-
{ cohortId: this.id,
|
|
626
|
+
"Cannot build CAS Announcement: not all participants have responded.",
|
|
627
|
+
"INCOMPLETE_RESPONSES",
|
|
628
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
403
629
|
);
|
|
404
630
|
}
|
|
405
631
|
const announcement = {};
|
|
@@ -411,31 +637,41 @@ var AggregationCohort = class {
|
|
|
411
637
|
return announcement;
|
|
412
638
|
}
|
|
413
639
|
/**
|
|
414
|
-
* Builds an SMT tree
|
|
415
|
-
*
|
|
416
|
-
*
|
|
640
|
+
* Builds an SMT tree with one leaf per participant.
|
|
641
|
+
*
|
|
642
|
+
* A member who submitted an update gets an inclusion leaf
|
|
643
|
+
* (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
|
|
644
|
+
* non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
|
|
645
|
+
* omitted). The cohort mints each member's nonce and returns it inside that
|
|
646
|
+
* member's serialized proof, so a decliner can self-validate its own
|
|
647
|
+
* non-inclusion slot and the resolver can recompute the leaf. Stores
|
|
648
|
+
* per-participant proofs and the SMT root as signalBytes.
|
|
417
649
|
*/
|
|
418
650
|
buildSMTTree() {
|
|
419
|
-
if (!this.
|
|
651
|
+
if (!this.hasAllResponses()) {
|
|
420
652
|
throw new AggregationCohortError(
|
|
421
|
-
"Cannot build SMT tree: not all
|
|
422
|
-
"
|
|
423
|
-
{ cohortId: this.id }
|
|
653
|
+
"Cannot build SMT tree: not all participants have responded.",
|
|
654
|
+
"INCOMPLETE_RESPONSES",
|
|
655
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
424
656
|
);
|
|
425
657
|
}
|
|
426
658
|
const tree = new import_smt2.BTCR2MerkleTree();
|
|
427
659
|
const entries = [];
|
|
428
660
|
const encoder = new TextEncoder();
|
|
429
|
-
for (const
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
661
|
+
for (const did of this.participants) {
|
|
662
|
+
const nonce = (0, import_utils2.randomBytes)(32);
|
|
663
|
+
const signedUpdate = this.pendingUpdates.get(did);
|
|
664
|
+
if (signedUpdate) {
|
|
665
|
+
entries.push({ did, nonce, signedUpdate: encoder.encode((0, import_common3.canonicalize)(signedUpdate)) });
|
|
666
|
+
} else {
|
|
667
|
+
entries.push({ did, nonce });
|
|
668
|
+
}
|
|
433
669
|
}
|
|
434
670
|
tree.addEntries(entries);
|
|
435
671
|
tree.finalize();
|
|
436
672
|
this.signalBytes = tree.rootHash;
|
|
437
673
|
this.smtProofs = /* @__PURE__ */ new Map();
|
|
438
|
-
for (const
|
|
674
|
+
for (const did of this.participants) {
|
|
439
675
|
this.smtProofs.set(did, tree.proof(did));
|
|
440
676
|
}
|
|
441
677
|
return this.smtProofs;
|
|
@@ -461,7 +697,7 @@ var AggregationCohort = class {
|
|
|
461
697
|
return this.validationAcks.size + this.validationRejections.size === this.participants.length;
|
|
462
698
|
}
|
|
463
699
|
/**
|
|
464
|
-
* True when all participants approved. Note: differs from {@link hasAllValidationResponses}
|
|
700
|
+
* True when all participants approved. Note: differs from {@link hasAllValidationResponses},
|
|
465
701
|
* this returns false if any participant rejected, even if all responses are in.
|
|
466
702
|
*/
|
|
467
703
|
isFullyValidated() {
|
|
@@ -471,6 +707,7 @@ var AggregationCohort = class {
|
|
|
471
707
|
|
|
472
708
|
// src/core/aggregation/conditions.ts
|
|
473
709
|
var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
|
|
710
|
+
var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
|
|
474
711
|
function checkPair(problems, label, min, max) {
|
|
475
712
|
if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
|
|
476
713
|
problems.push(`min${label} must be an integer >= 0`);
|
|
@@ -514,11 +751,130 @@ function validateCohortConditions(c) {
|
|
|
514
751
|
if (c.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
|
|
515
752
|
problems.push("pendingUpdateTrigger must be an integer >= 1");
|
|
516
753
|
}
|
|
754
|
+
if (c.fallbackThreshold !== void 0) {
|
|
755
|
+
if (!Number.isInteger(c.fallbackThreshold) || c.fallbackThreshold < 1) {
|
|
756
|
+
problems.push("fallbackThreshold must be an integer >= 1");
|
|
757
|
+
} else if (c.maxParticipants !== void 0 && Number.isInteger(c.maxParticipants) && c.fallbackThreshold > c.maxParticipants) {
|
|
758
|
+
problems.push("fallbackThreshold must be <= maxParticipants");
|
|
759
|
+
}
|
|
760
|
+
}
|
|
517
761
|
checkCost(problems, "costOfEnrollment", c.costOfEnrollment);
|
|
518
762
|
checkCost(problems, "costPerAnnouncement", c.costPerAnnouncement);
|
|
763
|
+
if (typeof c.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c.recoveryKey)) {
|
|
764
|
+
problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
|
|
765
|
+
}
|
|
766
|
+
if (!Number.isInteger(c.recoverySequence) || c.recoverySequence < 1 || c.recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
767
|
+
problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
|
|
768
|
+
}
|
|
769
|
+
if (c.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c.fundingModel)) {
|
|
770
|
+
problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
|
|
771
|
+
}
|
|
519
772
|
return problems;
|
|
520
773
|
}
|
|
521
774
|
|
|
775
|
+
// src/core/aggregation/fallback-spend.ts
|
|
776
|
+
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
777
|
+
var import_secp256k13 = require("@noble/curves/secp256k1.js");
|
|
778
|
+
var import_btc_signer3 = require("@scure/btc-signer");
|
|
779
|
+
var import_musig23 = require("@scure/btc-signer/musig2");
|
|
780
|
+
function bytesEqual(a, b) {
|
|
781
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
782
|
+
}
|
|
783
|
+
function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
|
|
784
|
+
return tx.preimageWitnessV1(
|
|
785
|
+
inputIndex,
|
|
786
|
+
[prevOutScript],
|
|
787
|
+
import_btc_signer3.SigHash.DEFAULT,
|
|
788
|
+
[prevOutValue],
|
|
789
|
+
void 0,
|
|
790
|
+
fallbackLeafScript,
|
|
791
|
+
TAPROOT_LEAF_VERSION
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function buildFallbackSpend(params) {
|
|
795
|
+
const {
|
|
796
|
+
pendingTx,
|
|
797
|
+
cohortKeys,
|
|
798
|
+
recoveryKey,
|
|
799
|
+
recoverySequence,
|
|
800
|
+
fundingModel,
|
|
801
|
+
network,
|
|
802
|
+
prevOutScript,
|
|
803
|
+
prevOutValue,
|
|
804
|
+
signatures
|
|
805
|
+
} = params;
|
|
806
|
+
const inputIndex = params.inputIndex ?? 0;
|
|
807
|
+
if (cohortKeys.length === 0) {
|
|
808
|
+
throw new AggregationCohortError(
|
|
809
|
+
"Cannot build fallback spend: no cohort keys.",
|
|
810
|
+
"NO_COHORT_KEYS"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
|
|
814
|
+
const internalKey = (0, import_musig23.keyAggExport)((0, import_musig23.keyAggregate)((0, import_musig23.sortKeys)(cohortKeys)));
|
|
815
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
816
|
+
recoveryKey,
|
|
817
|
+
recoverySequence,
|
|
818
|
+
cohortKeys,
|
|
819
|
+
fallbackThreshold
|
|
820
|
+
});
|
|
821
|
+
const net = (0, import_bitcoin2.getNetwork)(network);
|
|
822
|
+
const payment = (0, import_btc_signer3.p2tr)(internalKey, leaves, net, true);
|
|
823
|
+
if (!bytesEqual(payment.script, prevOutScript)) {
|
|
824
|
+
throw new AggregationCohortError(
|
|
825
|
+
"Reconstructed beacon output script does not match the spent prevout script.",
|
|
826
|
+
"PREVOUT_SCRIPT_MISMATCH"
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
|
|
830
|
+
const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
|
|
831
|
+
const script = scriptVer.slice(0, scriptVer.length - 1);
|
|
832
|
+
return bytesEqual(script, fallbackLeaf);
|
|
833
|
+
});
|
|
834
|
+
if (!leafEntry) {
|
|
835
|
+
throw new AggregationCohortError(
|
|
836
|
+
"Could not locate the fallback leaf in the reconstructed beacon output.",
|
|
837
|
+
"FALLBACK_LEAF_NOT_FOUND"
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
const cohortXOnly = (0, import_musig23.sortKeys)(cohortKeys).map((k) => k.slice(1));
|
|
841
|
+
const tx = pendingTx;
|
|
842
|
+
const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
|
|
843
|
+
const leafHash = tapLeafHash(fallbackLeaf);
|
|
844
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
845
|
+
for (const { pubKey, signature } of signatures) {
|
|
846
|
+
if (pubKey.length !== 32 || signature.length !== 64) continue;
|
|
847
|
+
const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
|
|
848
|
+
if (!isCohortKey) continue;
|
|
849
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
850
|
+
if (accepted.has(hex)) continue;
|
|
851
|
+
let ok = false;
|
|
852
|
+
try {
|
|
853
|
+
ok = import_secp256k13.schnorr.verify(signature, sighash, pubKey);
|
|
854
|
+
} catch {
|
|
855
|
+
ok = false;
|
|
856
|
+
}
|
|
857
|
+
if (ok) accepted.set(hex, signature);
|
|
858
|
+
}
|
|
859
|
+
if (accepted.size < fallbackThreshold) {
|
|
860
|
+
throw new AggregationCohortError(
|
|
861
|
+
`Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
|
|
862
|
+
"NOT_ENOUGH_FALLBACK_SIGNATURES",
|
|
863
|
+
{ have: accepted.size, need: fallbackThreshold }
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
|
|
867
|
+
tx.updateInput(inputIndex, {
|
|
868
|
+
tapLeafScript: [leafEntry],
|
|
869
|
+
tapScriptSig: chosen.map((pubKey) => {
|
|
870
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
871
|
+
return [{ pubKey, leafHash }, accepted.get(hex)];
|
|
872
|
+
})
|
|
873
|
+
});
|
|
874
|
+
tx.finalize();
|
|
875
|
+
return tx;
|
|
876
|
+
}
|
|
877
|
+
|
|
522
878
|
// src/core/aggregation/messages/base.ts
|
|
523
879
|
var AGGREGATION_WIRE_VERSION = 1;
|
|
524
880
|
var BaseMessage = class {
|
|
@@ -556,12 +912,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
|
556
912
|
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
557
913
|
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
558
914
|
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
915
|
+
var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
|
|
559
916
|
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
560
917
|
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
561
918
|
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
562
919
|
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
563
920
|
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
564
921
|
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
922
|
+
var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
|
|
923
|
+
var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
|
|
565
924
|
|
|
566
925
|
// src/core/aggregation/messages/factories.ts
|
|
567
926
|
function createCohortAdvertMessage(fields) {
|
|
@@ -584,6 +943,10 @@ function createSubmitUpdateMessage(fields) {
|
|
|
584
943
|
const { from: from2, to, ...body } = fields;
|
|
585
944
|
return new BaseMessage({ type: SUBMIT_UPDATE, from: from2, to, body });
|
|
586
945
|
}
|
|
946
|
+
function createSubmitNonIncludedMessage(fields) {
|
|
947
|
+
const { from: from2, to, ...body } = fields;
|
|
948
|
+
return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from2, to, body });
|
|
949
|
+
}
|
|
587
950
|
function createDistributeAggregatedDataMessage(fields) {
|
|
588
951
|
const { from: from2, to, ...body } = fields;
|
|
589
952
|
return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from2, to, body });
|
|
@@ -608,6 +971,14 @@ function createSignatureAuthorizationMessage(fields) {
|
|
|
608
971
|
const { from: from2, to, ...body } = fields;
|
|
609
972
|
return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from2, to, body });
|
|
610
973
|
}
|
|
974
|
+
function createFallbackAuthorizationRequestMessage(fields) {
|
|
975
|
+
const { from: from2, to, ...body } = fields;
|
|
976
|
+
return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from2, to, body });
|
|
977
|
+
}
|
|
978
|
+
function createFallbackSignatureMessage(fields) {
|
|
979
|
+
const { from: from2, to, ...body } = fields;
|
|
980
|
+
return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from2, to, body });
|
|
981
|
+
}
|
|
611
982
|
|
|
612
983
|
// src/core/aggregation/phases.ts
|
|
613
984
|
var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
@@ -621,6 +992,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
|
621
992
|
ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
|
|
622
993
|
ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
|
|
623
994
|
ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
|
|
995
|
+
ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
|
|
624
996
|
ServiceCohortPhase2["Complete"] = "Complete";
|
|
625
997
|
ServiceCohortPhase2["Failed"] = "Failed";
|
|
626
998
|
return ServiceCohortPhase2;
|
|
@@ -630,11 +1002,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
|
|
|
630
1002
|
ParticipantCohortPhase2["OptedIn"] = "OptedIn";
|
|
631
1003
|
ParticipantCohortPhase2["CohortReady"] = "CohortReady";
|
|
632
1004
|
ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
|
|
1005
|
+
ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
|
|
633
1006
|
ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
|
|
634
1007
|
ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
|
|
635
1008
|
ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
|
|
636
1009
|
ParticipantCohortPhase2["NonceSent"] = "NonceSent";
|
|
637
1010
|
ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
|
|
1011
|
+
ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
|
|
638
1012
|
ParticipantCohortPhase2["Complete"] = "Complete";
|
|
639
1013
|
ParticipantCohortPhase2["Failed"] = "Failed";
|
|
640
1014
|
return ParticipantCohortPhase2;
|
|
@@ -651,7 +1025,7 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
|
|
|
651
1025
|
|
|
652
1026
|
// src/core/aggregation/signing-session.ts
|
|
653
1027
|
var import_keypair = require("@did-btcr2/keypair");
|
|
654
|
-
var
|
|
1028
|
+
var import_btc_signer4 = require("@scure/btc-signer");
|
|
655
1029
|
var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
|
|
656
1030
|
var BeaconSigningSession = class {
|
|
657
1031
|
/** Unique identifier for this signing session. */
|
|
@@ -664,11 +1038,11 @@ var BeaconSigningSession = class {
|
|
|
664
1038
|
prevOutScripts;
|
|
665
1039
|
/** Previous output values for Taproot sighash computation. */
|
|
666
1040
|
prevOutValues;
|
|
667
|
-
/** Map of participant publicKey-hex
|
|
1041
|
+
/** Map of participant publicKey-hex to public nonce contribution. */
|
|
668
1042
|
nonceContributions = /* @__PURE__ */ new Map();
|
|
669
1043
|
/** Aggregated MuSig2 nonce (66 bytes). */
|
|
670
1044
|
aggregatedNonce;
|
|
671
|
-
/** Map of participant DID
|
|
1045
|
+
/** Map of participant DID to partial signature. */
|
|
672
1046
|
partialSignatures = /* @__PURE__ */ new Map();
|
|
673
1047
|
/** Final 64-byte Schnorr signature. */
|
|
674
1048
|
signature;
|
|
@@ -703,7 +1077,7 @@ var BeaconSigningSession = class {
|
|
|
703
1077
|
return this.pendingTx.preimageWitnessV1(
|
|
704
1078
|
0,
|
|
705
1079
|
this.prevOutScripts,
|
|
706
|
-
|
|
1080
|
+
import_btc_signer4.SigHash.DEFAULT,
|
|
707
1081
|
this.prevOutValues
|
|
708
1082
|
);
|
|
709
1083
|
}
|
|
@@ -851,7 +1225,7 @@ var BeaconSigningSession = class {
|
|
|
851
1225
|
throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
|
|
852
1226
|
}
|
|
853
1227
|
if (!this.#secretNonce) {
|
|
854
|
-
throw new SigningSessionError("Secret nonce not available
|
|
1228
|
+
throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
|
|
855
1229
|
}
|
|
856
1230
|
const session = new musig2.Session(
|
|
857
1231
|
this.aggregatedNonce,
|
|
@@ -921,6 +1295,9 @@ var AggregationService = class {
|
|
|
921
1295
|
case SUBMIT_UPDATE:
|
|
922
1296
|
this.#handleSubmitUpdate(message);
|
|
923
1297
|
break;
|
|
1298
|
+
case SUBMIT_NONINCLUDED:
|
|
1299
|
+
this.#handleSubmitNonInclusion(message);
|
|
1300
|
+
break;
|
|
924
1301
|
case VALIDATION_ACK:
|
|
925
1302
|
this.#handleValidationAck(message);
|
|
926
1303
|
break;
|
|
@@ -930,6 +1307,9 @@ var AggregationService = class {
|
|
|
930
1307
|
case SIGNATURE_AUTHORIZATION:
|
|
931
1308
|
this.#handleSignatureAuthorization(message);
|
|
932
1309
|
break;
|
|
1310
|
+
case FALLBACK_SIGNATURE:
|
|
1311
|
+
this.#handleFallbackSignature(message);
|
|
1312
|
+
break;
|
|
933
1313
|
default:
|
|
934
1314
|
break;
|
|
935
1315
|
}
|
|
@@ -948,7 +1328,7 @@ var AggregationService = class {
|
|
|
948
1328
|
}
|
|
949
1329
|
/**
|
|
950
1330
|
* Create a new cohort with the given config. Returns the cohort ID.
|
|
951
|
-
* Cohort starts in `Created` phase
|
|
1331
|
+
* Cohort starts in `Created` phase - call `advertise()` to broadcast.
|
|
952
1332
|
*/
|
|
953
1333
|
createCohort(config) {
|
|
954
1334
|
const problems = validateCohortConditions(config);
|
|
@@ -963,7 +1343,11 @@ var AggregationService = class {
|
|
|
963
1343
|
serviceDid: this.did,
|
|
964
1344
|
minParticipants: config.minParticipants,
|
|
965
1345
|
network: config.network,
|
|
966
|
-
beaconType: config.beaconType
|
|
1346
|
+
beaconType: config.beaconType,
|
|
1347
|
+
recoveryKey: (0, import_utils3.hexToBytes)(config.recoveryKey),
|
|
1348
|
+
recoverySequence: config.recoverySequence,
|
|
1349
|
+
fundingModel: config.fundingModel,
|
|
1350
|
+
fallbackThreshold: config.fallbackThreshold
|
|
967
1351
|
});
|
|
968
1352
|
this.#cohortStates.set(cohort.id, {
|
|
969
1353
|
phase: "Created" /* Created */,
|
|
@@ -1164,11 +1548,64 @@ var AggregationService = class {
|
|
|
1164
1548
|
});
|
|
1165
1549
|
return;
|
|
1166
1550
|
}
|
|
1551
|
+
if (state.cohort.nonIncluded.has(message.from)) {
|
|
1552
|
+
state.rejections.push({
|
|
1553
|
+
from: message.from,
|
|
1554
|
+
code: "UPDATE_MALFORMED",
|
|
1555
|
+
reason: "Participant already declined this round; cannot also submit an update"
|
|
1556
|
+
});
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
if (state.cohort.pendingUpdates.has(message.from)) {
|
|
1560
|
+
state.rejections.push({
|
|
1561
|
+
from: message.from,
|
|
1562
|
+
code: "UPDATE_MALFORMED",
|
|
1563
|
+
reason: "Participant already submitted an update this round; cannot resubmit"
|
|
1564
|
+
});
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1167
1567
|
state.cohort.addUpdate(message.from, signedUpdate);
|
|
1168
1568
|
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
1169
1569
|
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
1170
1570
|
}
|
|
1171
|
-
if (state.cohort.
|
|
1571
|
+
if (state.cohort.hasAllResponses()) {
|
|
1572
|
+
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
|
|
1577
|
+
* update this round (cooperative non-inclusion). Membership is proven by the
|
|
1578
|
+
* signed transport envelope, so the body carries only the cohortId. The member
|
|
1579
|
+
* stays in the cohort and still signs; it is absent from the CAS map and
|
|
1580
|
+
* carries a non-inclusion leaf in the SMT.
|
|
1581
|
+
*/
|
|
1582
|
+
#handleSubmitNonInclusion(message) {
|
|
1583
|
+
const cohortId = message.body?.cohortId;
|
|
1584
|
+
if (!cohortId) return;
|
|
1585
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1586
|
+
if (!state) return;
|
|
1587
|
+
if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
|
|
1588
|
+
if (!state.cohort.participants.includes(message.from)) {
|
|
1589
|
+
state.rejections.push({
|
|
1590
|
+
from: message.from,
|
|
1591
|
+
code: "UPDATE_MALFORMED",
|
|
1592
|
+
reason: "Sender is not a member of this cohort"
|
|
1593
|
+
});
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
if (state.cohort.pendingUpdates.has(message.from) || state.cohort.nonIncluded.has(message.from)) {
|
|
1597
|
+
state.rejections.push({
|
|
1598
|
+
from: message.from,
|
|
1599
|
+
code: "UPDATE_MALFORMED",
|
|
1600
|
+
reason: "Participant already responded this round"
|
|
1601
|
+
});
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
state.cohort.addNonInclusion(message.from);
|
|
1605
|
+
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
1606
|
+
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
1607
|
+
}
|
|
1608
|
+
if (state.cohort.hasAllResponses()) {
|
|
1172
1609
|
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
1173
1610
|
}
|
|
1174
1611
|
}
|
|
@@ -1227,7 +1664,7 @@ var AggregationService = class {
|
|
|
1227
1664
|
);
|
|
1228
1665
|
}
|
|
1229
1666
|
strategy.buildAggregatedData(state.cohort);
|
|
1230
|
-
const signalBytesHex = (0,
|
|
1667
|
+
const signalBytesHex = (0, import_utils3.bytesToHex)(state.cohort.signalBytes);
|
|
1231
1668
|
state.phase = "DataDistributed" /* DataDistributed */;
|
|
1232
1669
|
const messages = [];
|
|
1233
1670
|
for (const participantDid of state.cohort.participants) {
|
|
@@ -1278,7 +1715,7 @@ var AggregationService = class {
|
|
|
1278
1715
|
}
|
|
1279
1716
|
/**
|
|
1280
1717
|
* Start a signing session by creating auth requests for all participants.
|
|
1281
|
-
* The caller provides the transaction data
|
|
1718
|
+
* The caller provides the transaction data - typically built via
|
|
1282
1719
|
* `buildBeaconTransaction()` against a Bitcoin connection.
|
|
1283
1720
|
*/
|
|
1284
1721
|
startSigning(cohortId, txData) {
|
|
@@ -1317,7 +1754,7 @@ var AggregationService = class {
|
|
|
1317
1754
|
cohortId,
|
|
1318
1755
|
sessionId: session.id,
|
|
1319
1756
|
pendingTx: txData.tx.hex,
|
|
1320
|
-
prevOutScriptHex: (0,
|
|
1757
|
+
prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
|
|
1321
1758
|
prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
|
|
1322
1759
|
}));
|
|
1323
1760
|
}
|
|
@@ -1385,11 +1822,144 @@ var AggregationService = class {
|
|
|
1385
1822
|
state.result = {
|
|
1386
1823
|
cohortId,
|
|
1387
1824
|
signature,
|
|
1388
|
-
signedTx: state.signingSession.pendingTx
|
|
1825
|
+
signedTx: state.signingSession.pendingTx,
|
|
1826
|
+
path: "key-path"
|
|
1389
1827
|
};
|
|
1390
1828
|
state.phase = "Complete" /* Complete */;
|
|
1391
1829
|
}
|
|
1392
1830
|
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Abandon the optimistic n-of-n key path and ask members to authorize the
|
|
1833
|
+
* k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
|
|
1834
|
+
* liveness, ADR 042). Reuses the in-flight signing session's transaction and
|
|
1835
|
+
* spent output, so the announcement and its outputs are unchanged: only the
|
|
1836
|
+
* witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
|
|
1837
|
+
* participant.
|
|
1838
|
+
*
|
|
1839
|
+
* Callable once optimistic signing has started (the session and its tx exist)
|
|
1840
|
+
* and before it completes. A cohort can take exactly one of the two paths: the
|
|
1841
|
+
* caller (runner) must commit to fallback and stop driving the optimistic path.
|
|
1842
|
+
*/
|
|
1843
|
+
startFallbackSigning(cohortId) {
|
|
1844
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1845
|
+
if (!state) {
|
|
1846
|
+
throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
|
|
1847
|
+
}
|
|
1848
|
+
if (!state.signingSession) {
|
|
1849
|
+
throw new AggregationServiceError(
|
|
1850
|
+
`Cannot start fallback for cohort ${cohortId}: no signing session.`,
|
|
1851
|
+
"NO_SIGNING_SESSION",
|
|
1852
|
+
{ cohortId }
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
const signingPhases = [
|
|
1856
|
+
"SigningStarted" /* SigningStarted */,
|
|
1857
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
1858
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
1859
|
+
];
|
|
1860
|
+
if (!signingPhases.includes(state.phase)) {
|
|
1861
|
+
throw new AggregationServiceError(
|
|
1862
|
+
`Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
|
|
1863
|
+
"INVALID_PHASE",
|
|
1864
|
+
{ cohortId, phase: state.phase }
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
const session = state.signingSession;
|
|
1868
|
+
const prevOutScript = session.prevOutScripts[0];
|
|
1869
|
+
const prevOutValue = session.prevOutValues[0];
|
|
1870
|
+
if (!prevOutScript || prevOutValue === void 0) {
|
|
1871
|
+
throw new AggregationServiceError(
|
|
1872
|
+
`Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
|
|
1873
|
+
"MISSING_PREV_OUT",
|
|
1874
|
+
{ cohortId }
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
1878
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1879
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
1880
|
+
});
|
|
1881
|
+
state.fallbackSignatures = /* @__PURE__ */ new Map();
|
|
1882
|
+
state.phase = "FallbackRequested" /* FallbackRequested */;
|
|
1883
|
+
const messages = [];
|
|
1884
|
+
for (const participantDid of state.cohort.participants) {
|
|
1885
|
+
messages.push(createFallbackAuthorizationRequestMessage({
|
|
1886
|
+
from: this.did,
|
|
1887
|
+
to: participantDid,
|
|
1888
|
+
cohortId,
|
|
1889
|
+
sessionId: session.id,
|
|
1890
|
+
pendingTx: session.pendingTx.hex,
|
|
1891
|
+
prevOutScriptHex: (0, import_utils3.bytesToHex)(prevOutScript),
|
|
1892
|
+
prevOutValue: prevOutValue.toString(),
|
|
1893
|
+
fallbackLeafScriptHex: (0, import_utils3.bytesToHex)(fallbackLeaf)
|
|
1894
|
+
}));
|
|
1895
|
+
}
|
|
1896
|
+
return messages;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
|
|
1900
|
+
* signature over the fallback script-path sighash. The signature is
|
|
1901
|
+
* authenticated to the sender (its `signerPk` must be the sender's own cohort
|
|
1902
|
+
* key and the signature must verify against the sighash) and collected. Once k
|
|
1903
|
+
* valid signatures are in, the k-of-n fallback spend is assembled and the
|
|
1904
|
+
* cohort completes via the script path.
|
|
1905
|
+
*/
|
|
1906
|
+
#handleFallbackSignature(message) {
|
|
1907
|
+
const cohortId = message.body?.cohortId;
|
|
1908
|
+
if (!cohortId) return;
|
|
1909
|
+
const state = this.#cohortStates.get(cohortId);
|
|
1910
|
+
if (!state || !state.signingSession || !state.fallbackSignatures) return;
|
|
1911
|
+
if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
|
|
1912
|
+
const sessionId = message.body?.sessionId;
|
|
1913
|
+
if (sessionId !== state.signingSession.id) return;
|
|
1914
|
+
const signerPk = message.body?.signerPk;
|
|
1915
|
+
const fallbackSignature = message.body?.fallbackSignature;
|
|
1916
|
+
if (!signerPk || !fallbackSignature) return;
|
|
1917
|
+
const prevOutScript = state.signingSession.prevOutScripts[0];
|
|
1918
|
+
const prevOutValue = state.signingSession.prevOutValues[0];
|
|
1919
|
+
if (!prevOutScript || prevOutValue === void 0) return;
|
|
1920
|
+
const memberKey = state.cohort.participantKeys.get(message.from);
|
|
1921
|
+
if (!memberKey) {
|
|
1922
|
+
state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const memberXOnly = memberKey.slice(1);
|
|
1926
|
+
if (signerPk.length !== 32 || !memberXOnly.every((b, i) => b === signerPk[i])) {
|
|
1927
|
+
state.rejections.push({ from: message.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
1931
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1932
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
1933
|
+
});
|
|
1934
|
+
const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
1935
|
+
let valid = false;
|
|
1936
|
+
try {
|
|
1937
|
+
valid = fallbackSignature.length === 64 && import_secp256k14.schnorr.verify(fallbackSignature, sighash, signerPk);
|
|
1938
|
+
} catch {
|
|
1939
|
+
valid = false;
|
|
1940
|
+
}
|
|
1941
|
+
if (!valid) {
|
|
1942
|
+
state.rejections.push({ from: message.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
state.fallbackSignatures.set(message.from, { pubKey: signerPk, signature: fallbackSignature });
|
|
1946
|
+
if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
|
|
1947
|
+
const signedTx = buildFallbackSpend({
|
|
1948
|
+
pendingTx: state.signingSession.pendingTx,
|
|
1949
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
1950
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold,
|
|
1951
|
+
recoveryKey: state.cohort.recoveryKey,
|
|
1952
|
+
recoverySequence: state.cohort.recoverySequence,
|
|
1953
|
+
fundingModel: state.cohort.fundingModel,
|
|
1954
|
+
network: state.cohort.network,
|
|
1955
|
+
prevOutScript,
|
|
1956
|
+
prevOutValue,
|
|
1957
|
+
signatures: [...state.fallbackSignatures.values()]
|
|
1958
|
+
});
|
|
1959
|
+
state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
|
|
1960
|
+
state.phase = "Complete" /* Complete */;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1393
1963
|
getResult(cohortId) {
|
|
1394
1964
|
return this.#cohortStates.get(cohortId)?.result;
|
|
1395
1965
|
}
|
|
@@ -1421,8 +1991,9 @@ var AggregationService = class {
|
|
|
1421
1991
|
|
|
1422
1992
|
// src/core/aggregation/participant.ts
|
|
1423
1993
|
var import_common5 = require("@did-btcr2/common");
|
|
1424
|
-
var
|
|
1425
|
-
var
|
|
1994
|
+
var import_secp256k15 = require("@noble/curves/secp256k1.js");
|
|
1995
|
+
var import_utils4 = require("@noble/hashes/utils");
|
|
1996
|
+
var import_btc_signer5 = require("@scure/btc-signer");
|
|
1426
1997
|
|
|
1427
1998
|
// src/core/aggregation/messages/bodies.ts
|
|
1428
1999
|
var hasStr = (b, k) => !!b && typeof b[k] === "string";
|
|
@@ -1434,6 +2005,14 @@ var optIntMin = (b, k, min) => {
|
|
|
1434
2005
|
const v = b ? b[k] : void 0;
|
|
1435
2006
|
return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
|
|
1436
2007
|
};
|
|
2008
|
+
var intInRange = (b, k, min, max) => {
|
|
2009
|
+
const v = b ? b[k] : void 0;
|
|
2010
|
+
return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
2011
|
+
};
|
|
2012
|
+
var optStrOneOf = (b, k, allowed) => {
|
|
2013
|
+
const v = b ? b[k] : void 0;
|
|
2014
|
+
return v === void 0 || typeof v === "string" && allowed.includes(v);
|
|
2015
|
+
};
|
|
1437
2016
|
var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
|
|
1438
2017
|
var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
|
|
1439
2018
|
var hasBytesArray = (b, k) => {
|
|
@@ -1441,7 +2020,7 @@ var hasBytesArray = (b, k) => {
|
|
|
1441
2020
|
return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
|
|
1442
2021
|
};
|
|
1443
2022
|
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");
|
|
2023
|
+
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
2024
|
}
|
|
1446
2025
|
function isCohortOptInMessage(m) {
|
|
1447
2026
|
return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
|
|
@@ -1455,6 +2034,9 @@ function isCohortReadyMessage(m) {
|
|
|
1455
2034
|
function isSubmitUpdateMessage(m) {
|
|
1456
2035
|
return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
|
|
1457
2036
|
}
|
|
2037
|
+
function isSubmitNonIncludedMessage(m) {
|
|
2038
|
+
return m.type === SUBMIT_NONINCLUDED && hasStr(m.body, "cohortId");
|
|
2039
|
+
}
|
|
1458
2040
|
function isDistributeAggregatedDataMessage(m) {
|
|
1459
2041
|
return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
|
|
1460
2042
|
}
|
|
@@ -1473,8 +2055,38 @@ function isAggregatedNonceMessage(m) {
|
|
|
1473
2055
|
function isSignatureAuthorizationMessage(m) {
|
|
1474
2056
|
return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
|
|
1475
2057
|
}
|
|
2058
|
+
function isFallbackAuthorizationRequestMessage(m) {
|
|
2059
|
+
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");
|
|
2060
|
+
}
|
|
2061
|
+
function isFallbackSignatureMessage(m) {
|
|
2062
|
+
return m.type === FALLBACK_SIGNATURE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "signerPk") && hasBytes(m.body, "fallbackSignature");
|
|
2063
|
+
}
|
|
1476
2064
|
|
|
1477
2065
|
// src/core/aggregation/participant.ts
|
|
2066
|
+
function txEmbedsSignal(tx, signalHex) {
|
|
2067
|
+
let expected;
|
|
2068
|
+
try {
|
|
2069
|
+
expected = (0, import_utils4.hexToBytes)(signalHex);
|
|
2070
|
+
} catch {
|
|
2071
|
+
return false;
|
|
2072
|
+
}
|
|
2073
|
+
if (expected.length === 0) return false;
|
|
2074
|
+
for (let i = 0; i < tx.outputsLength; i++) {
|
|
2075
|
+
const script = tx.getOutput(i)?.script;
|
|
2076
|
+
if (!script) continue;
|
|
2077
|
+
let decoded;
|
|
2078
|
+
try {
|
|
2079
|
+
decoded = import_btc_signer5.Script.decode(script);
|
|
2080
|
+
} catch {
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
|
|
2084
|
+
const payload = decoded[1];
|
|
2085
|
+
if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
return false;
|
|
2089
|
+
}
|
|
1478
2090
|
var AggregationParticipant = class {
|
|
1479
2091
|
did;
|
|
1480
2092
|
/** MuSig2 signing capability. The raw secret never lives as a field here. */
|
|
@@ -1491,7 +2103,7 @@ var AggregationParticipant = class {
|
|
|
1491
2103
|
}
|
|
1492
2104
|
/**
|
|
1493
2105
|
* Process an incoming message. Updates internal state but never produces
|
|
1494
|
-
* outgoing messages
|
|
2106
|
+
* outgoing messages: those come exclusively from action methods.
|
|
1495
2107
|
*/
|
|
1496
2108
|
receive(message) {
|
|
1497
2109
|
if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
|
|
@@ -1517,6 +2129,9 @@ var AggregationParticipant = class {
|
|
|
1517
2129
|
case AGGREGATED_NONCE:
|
|
1518
2130
|
this.#handleAggregatedNonce(message);
|
|
1519
2131
|
break;
|
|
2132
|
+
case FALLBACK_AUTHORIZATION_REQUEST:
|
|
2133
|
+
this.#handleFallbackAuthorizationRequest(message);
|
|
2134
|
+
break;
|
|
1520
2135
|
default:
|
|
1521
2136
|
break;
|
|
1522
2137
|
}
|
|
@@ -1562,12 +2177,17 @@ var AggregationParticipant = class {
|
|
|
1562
2177
|
{ cohortId, phase: state?.phase }
|
|
1563
2178
|
);
|
|
1564
2179
|
}
|
|
2180
|
+
const advert = state.advert;
|
|
1565
2181
|
const cohort = new AggregationCohort({
|
|
1566
2182
|
id: cohortId,
|
|
1567
2183
|
serviceDid: state.serviceDid,
|
|
1568
|
-
minParticipants:
|
|
1569
|
-
network:
|
|
1570
|
-
beaconType:
|
|
2184
|
+
minParticipants: advert.minParticipants,
|
|
2185
|
+
network: advert.network,
|
|
2186
|
+
beaconType: advert.beaconType,
|
|
2187
|
+
recoveryKey: advert.recoveryKey ? (0, import_utils4.hexToBytes)(advert.recoveryKey) : void 0,
|
|
2188
|
+
recoverySequence: advert.recoverySequence,
|
|
2189
|
+
fundingModel: advert.fundingModel,
|
|
2190
|
+
fallbackThreshold: advert.fallbackThreshold
|
|
1571
2191
|
});
|
|
1572
2192
|
state.cohort = cohort;
|
|
1573
2193
|
state.phase = "OptedIn" /* OptedIn */;
|
|
@@ -1583,7 +2203,7 @@ var AggregationParticipant = class {
|
|
|
1583
2203
|
#handleOptInAccept(message) {
|
|
1584
2204
|
void message;
|
|
1585
2205
|
}
|
|
1586
|
-
/** Cohorts that have been finalized
|
|
2206
|
+
/** Cohorts that have been finalized: beacon address available. */
|
|
1587
2207
|
get joinedCohorts() {
|
|
1588
2208
|
const map = /* @__PURE__ */ new Map();
|
|
1589
2209
|
for (const [id, state] of this.#cohortStates) {
|
|
@@ -1607,8 +2227,8 @@ var AggregationParticipant = class {
|
|
|
1607
2227
|
const beaconAddress = message.body?.beaconAddress;
|
|
1608
2228
|
const cohortKeys = message.body?.cohortKeys;
|
|
1609
2229
|
if (!beaconAddress || !cohortKeys) return;
|
|
1610
|
-
const participantPkHex = (0,
|
|
1611
|
-
const cohortKeysHex = cohortKeys.map((k) => (0,
|
|
2230
|
+
const participantPkHex = (0, import_utils4.bytesToHex)(this.publicKey);
|
|
2231
|
+
const cohortKeysHex = cohortKeys.map((k) => (0, import_utils4.bytesToHex)(new Uint8Array(k)));
|
|
1612
2232
|
state.cohort.validateMembership(participantPkHex, cohortKeysHex, beaconAddress);
|
|
1613
2233
|
state.phase = "CohortReady" /* CohortReady */;
|
|
1614
2234
|
}
|
|
@@ -1626,6 +2246,7 @@ var AggregationParticipant = class {
|
|
|
1626
2246
|
);
|
|
1627
2247
|
}
|
|
1628
2248
|
state.submittedUpdate = signedUpdate;
|
|
2249
|
+
state.included = true;
|
|
1629
2250
|
state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
|
|
1630
2251
|
const message = createSubmitUpdateMessage({
|
|
1631
2252
|
from: this.did,
|
|
@@ -1635,6 +2256,30 @@ var AggregationParticipant = class {
|
|
|
1635
2256
|
});
|
|
1636
2257
|
return [message];
|
|
1637
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* User action: decline to submit an update this round (cooperative
|
|
2261
|
+
* non-inclusion). The member stays in the cohort and still signs; it will be
|
|
2262
|
+
* absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
|
|
2263
|
+
* SMT. Returns the SUBMIT_NONINCLUDED message to send.
|
|
2264
|
+
*/
|
|
2265
|
+
declineUpdate(cohortId) {
|
|
2266
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2267
|
+
if (!state || state.phase !== "CohortReady" /* CohortReady */) {
|
|
2268
|
+
throw new AggregationParticipantError(
|
|
2269
|
+
`Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
|
|
2270
|
+
"INVALID_PHASE",
|
|
2271
|
+
{ cohortId, phase: state?.phase }
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
state.included = false;
|
|
2275
|
+
state.phase = "NonIncluded" /* NonIncluded */;
|
|
2276
|
+
const message = createSubmitNonIncludedMessage({
|
|
2277
|
+
from: this.did,
|
|
2278
|
+
to: state.serviceDid,
|
|
2279
|
+
cohortId
|
|
2280
|
+
});
|
|
2281
|
+
return [message];
|
|
2282
|
+
}
|
|
1638
2283
|
/** Aggregated data awaiting user validation. */
|
|
1639
2284
|
get pendingValidations() {
|
|
1640
2285
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1645,22 +2290,35 @@ var AggregationParticipant = class {
|
|
|
1645
2290
|
}
|
|
1646
2291
|
return map;
|
|
1647
2292
|
}
|
|
2293
|
+
/**
|
|
2294
|
+
* The validated aggregated data retained for a cohort, regardless of phase.
|
|
2295
|
+
* Unlike {@link pendingValidations} (which lists only cohorts still awaiting
|
|
2296
|
+
* the validate decision), this returns the stored validation, including the
|
|
2297
|
+
* participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
|
|
2298
|
+
* so it is still readable once the cohort reaches Complete. Returns
|
|
2299
|
+
* undefined before aggregated data has been received.
|
|
2300
|
+
*/
|
|
2301
|
+
getValidation(cohortId) {
|
|
2302
|
+
return this.#cohortStates.get(cohortId)?.validation;
|
|
2303
|
+
}
|
|
1648
2304
|
#handleDistributeAggregatedData(message) {
|
|
1649
2305
|
const cohortId = message.body?.cohortId;
|
|
1650
2306
|
if (!cohortId) return;
|
|
1651
2307
|
const state = this.#cohortStates.get(cohortId);
|
|
1652
|
-
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
|
|
1653
|
-
|
|
2308
|
+
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
|
|
2309
|
+
const declined = state.included === false;
|
|
2310
|
+
if (!declined && !state.submittedUpdate) return;
|
|
1654
2311
|
const beaconType = message.body?.beaconType;
|
|
1655
2312
|
if (!beaconType) return;
|
|
1656
2313
|
const strategy = getBeaconStrategy(beaconType);
|
|
1657
2314
|
if (!strategy) return;
|
|
1658
2315
|
const signalBytesHex = message.body?.signalBytesHex ?? "";
|
|
1659
|
-
const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
|
|
2316
|
+
const expectedHash = declined ? "" : (0, import_common5.canonicalHash)(state.submittedUpdate);
|
|
1660
2317
|
const result = strategy.validateParticipantView({
|
|
1661
2318
|
participantDid: this.did,
|
|
1662
|
-
|
|
1663
|
-
|
|
2319
|
+
included: !declined,
|
|
2320
|
+
submittedUpdate: declined ? void 0 : state.submittedUpdate,
|
|
2321
|
+
expectedHash: declined ? void 0 : expectedHash,
|
|
1664
2322
|
body: message.body
|
|
1665
2323
|
});
|
|
1666
2324
|
state.validation = {
|
|
@@ -1670,7 +2328,8 @@ var AggregationParticipant = class {
|
|
|
1670
2328
|
expectedHash,
|
|
1671
2329
|
matches: result.matches,
|
|
1672
2330
|
casAnnouncement: result.casAnnouncement,
|
|
1673
|
-
smtProof: result.smtProof
|
|
2331
|
+
smtProof: result.smtProof,
|
|
2332
|
+
included: !declined
|
|
1674
2333
|
};
|
|
1675
2334
|
state.phase = "AwaitingValidation" /* AwaitingValidation */;
|
|
1676
2335
|
}
|
|
@@ -1744,6 +2403,31 @@ var AggregationParticipant = class {
|
|
|
1744
2403
|
};
|
|
1745
2404
|
state.phase = "AwaitingSigning" /* AwaitingSigning */;
|
|
1746
2405
|
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Bind a signing approval to the announcement the member validated: a beacon
|
|
2408
|
+
* transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
|
|
2409
|
+
* the aggregated data was distributed. Both the optimistic nonce approval and
|
|
2410
|
+
* the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
|
|
2411
|
+
* while the coordinator drives output selection, so without this check a
|
|
2412
|
+
* coordinator could anchor a different signal under the member's signature.
|
|
2413
|
+
*/
|
|
2414
|
+
#assertTxAnchorsValidatedSignal(cohortId, state, tx) {
|
|
2415
|
+
const signalHex = state.validation?.signalBytesHex;
|
|
2416
|
+
if (!signalHex) {
|
|
2417
|
+
throw new AggregationParticipantError(
|
|
2418
|
+
`Cohort ${cohortId} has no validated signal to bind the signature to.`,
|
|
2419
|
+
"MISSING_STATE",
|
|
2420
|
+
{ cohortId }
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
if (!txEmbedsSignal(tx, signalHex)) {
|
|
2424
|
+
throw new AggregationParticipantError(
|
|
2425
|
+
`Transaction for cohort ${cohortId} does not anchor the validated signal.`,
|
|
2426
|
+
"SIGNAL_MISMATCH",
|
|
2427
|
+
{ cohortId }
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
1747
2431
|
/**
|
|
1748
2432
|
* User action: approve signing and generate nonce contribution.
|
|
1749
2433
|
*/
|
|
@@ -1763,8 +2447,9 @@ var AggregationParticipant = class {
|
|
|
1763
2447
|
{ cohortId }
|
|
1764
2448
|
);
|
|
1765
2449
|
}
|
|
1766
|
-
const tx =
|
|
1767
|
-
|
|
2450
|
+
const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
|
|
2451
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
2452
|
+
const prevOutScripts = [(0, import_utils4.hexToBytes)(state.signingRequest.prevOutScriptHex)];
|
|
1768
2453
|
const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
|
|
1769
2454
|
const session = new BeaconSigningSession({
|
|
1770
2455
|
id: state.signingRequest.sessionId,
|
|
@@ -1799,7 +2484,7 @@ var AggregationParticipant = class {
|
|
|
1799
2484
|
}
|
|
1800
2485
|
/**
|
|
1801
2486
|
* User action: generate and return the partial signature.
|
|
1802
|
-
* In most UIs this is automatic after AwaitingPartialSig
|
|
2487
|
+
* In most UIs this is automatic after AwaitingPartialSig, but exposing it
|
|
1803
2488
|
* as an explicit action lets the client UI confirm before signing if desired.
|
|
1804
2489
|
*/
|
|
1805
2490
|
generatePartialSignature(cohortId) {
|
|
@@ -1831,6 +2516,83 @@ var AggregationParticipant = class {
|
|
|
1831
2516
|
partialSignature: partialSig
|
|
1832
2517
|
})];
|
|
1833
2518
|
}
|
|
2519
|
+
/** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
|
|
2520
|
+
get pendingFallbackRequests() {
|
|
2521
|
+
const map = /* @__PURE__ */ new Map();
|
|
2522
|
+
for (const [id, state] of this.#cohortStates) {
|
|
2523
|
+
if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
|
|
2524
|
+
map.set(id, state.fallbackRequest);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return map;
|
|
2528
|
+
}
|
|
2529
|
+
#handleFallbackAuthorizationRequest(message) {
|
|
2530
|
+
const cohortId = message.body?.cohortId;
|
|
2531
|
+
if (!cohortId) return;
|
|
2532
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2533
|
+
if (!state || !state.cohort) return;
|
|
2534
|
+
const acceptFrom = [
|
|
2535
|
+
"ValidationSent" /* ValidationSent */,
|
|
2536
|
+
"AwaitingSigning" /* AwaitingSigning */,
|
|
2537
|
+
"NonceSent" /* NonceSent */,
|
|
2538
|
+
"AwaitingPartialSig" /* AwaitingPartialSig */,
|
|
2539
|
+
"Complete" /* Complete */
|
|
2540
|
+
];
|
|
2541
|
+
if (!acceptFrom.includes(state.phase)) return;
|
|
2542
|
+
const sessionId = message.body?.sessionId;
|
|
2543
|
+
const pendingTxHex = message.body?.pendingTx;
|
|
2544
|
+
const prevOutScriptHex = message.body?.prevOutScriptHex;
|
|
2545
|
+
const prevOutValue = message.body?.prevOutValue;
|
|
2546
|
+
const fallbackLeafScriptHex = message.body?.fallbackLeafScriptHex;
|
|
2547
|
+
if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
|
|
2548
|
+
state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
|
|
2549
|
+
state.signingSession?.clearSecrets();
|
|
2550
|
+
state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* User action: authorize the fallback spend. Recomputes the k-of-n fallback
|
|
2554
|
+
* leaf from the member's OWN cohort state (not the service-provided script),
|
|
2555
|
+
* computes the BIP-341 script-path sighash over the requested transaction, and
|
|
2556
|
+
* returns a standalone BIP-340 signature (no nonce round). The member completes
|
|
2557
|
+
* once it has contributed; the service needs only k of these.
|
|
2558
|
+
*/
|
|
2559
|
+
approveFallback(cohortId) {
|
|
2560
|
+
const state = this.#cohortStates.get(cohortId);
|
|
2561
|
+
if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
|
|
2562
|
+
throw new AggregationParticipantError(
|
|
2563
|
+
`Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
|
|
2564
|
+
"INVALID_PHASE",
|
|
2565
|
+
{ cohortId, phase: state?.phase }
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
if (!state.fallbackRequest || !state.cohort) {
|
|
2569
|
+
throw new AggregationParticipantError(
|
|
2570
|
+
`Cohort ${cohortId} missing fallback request or cohort state.`,
|
|
2571
|
+
"MISSING_STATE",
|
|
2572
|
+
{ cohortId }
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
const req = state.fallbackRequest;
|
|
2576
|
+
const tx = import_btc_signer5.Transaction.fromRaw((0, import_utils4.hexToBytes)(req.pendingTxHex), { allowUnknownOutputs: true });
|
|
2577
|
+
const prevOutScript = (0, import_utils4.hexToBytes)(req.prevOutScriptHex);
|
|
2578
|
+
const prevOutValue = BigInt(req.prevOutValue);
|
|
2579
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
2580
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
2581
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
2582
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
2583
|
+
});
|
|
2584
|
+
const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
2585
|
+
const signature = this.#signer.withSecret((secretKey) => import_secp256k15.schnorr.sign(sighash, secretKey));
|
|
2586
|
+
state.phase = "Complete" /* Complete */;
|
|
2587
|
+
return [createFallbackSignatureMessage({
|
|
2588
|
+
from: this.did,
|
|
2589
|
+
to: state.serviceDid,
|
|
2590
|
+
cohortId,
|
|
2591
|
+
sessionId: req.sessionId,
|
|
2592
|
+
signerPk: this.publicKey.slice(1),
|
|
2593
|
+
fallbackSignature: signature
|
|
2594
|
+
})];
|
|
2595
|
+
}
|
|
1834
2596
|
getCohortPhase(cohortId) {
|
|
1835
2597
|
return this.#cohortStates.get(cohortId)?.phase;
|
|
1836
2598
|
}
|
|
@@ -1867,6 +2629,87 @@ var KeyPairAggregationSigner = class {
|
|
|
1867
2629
|
}
|
|
1868
2630
|
};
|
|
1869
2631
|
|
|
2632
|
+
// src/core/aggregation/recovery-spend.ts
|
|
2633
|
+
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
2634
|
+
var import_secp256k16 = require("@noble/curves/secp256k1.js");
|
|
2635
|
+
var import_btc_signer6 = require("@scure/btc-signer");
|
|
2636
|
+
var import_musig24 = require("@scure/btc-signer/musig2");
|
|
2637
|
+
var DUST_LIMIT_SATS = 546n;
|
|
2638
|
+
function bytesEqual2(a, b) {
|
|
2639
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
2640
|
+
}
|
|
2641
|
+
function buildRecoverySpend(params) {
|
|
2642
|
+
const {
|
|
2643
|
+
cohortKeys,
|
|
2644
|
+
recoverySecretKey,
|
|
2645
|
+
recoveryKey,
|
|
2646
|
+
recoverySequence,
|
|
2647
|
+
fallbackThreshold,
|
|
2648
|
+
fundingModel,
|
|
2649
|
+
network,
|
|
2650
|
+
utxo,
|
|
2651
|
+
destinationAddress,
|
|
2652
|
+
fee,
|
|
2653
|
+
beaconAddress
|
|
2654
|
+
} = params;
|
|
2655
|
+
if (cohortKeys.length === 0) {
|
|
2656
|
+
throw new AggregationCohortError(
|
|
2657
|
+
"Cannot build recovery spend: no cohort keys.",
|
|
2658
|
+
"NO_COHORT_KEYS"
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
const derivedPub = import_secp256k16.schnorr.getPublicKey(recoverySecretKey);
|
|
2662
|
+
if (!bytesEqual2(derivedPub, recoveryKey)) {
|
|
2663
|
+
throw new AggregationCohortError(
|
|
2664
|
+
"Recovery secret key does not correspond to the committed recovery key.",
|
|
2665
|
+
"RECOVERY_KEY_MISMATCH"
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
const out = utxo.value - fee;
|
|
2669
|
+
if (out <= 0n) {
|
|
2670
|
+
throw new AggregationCohortError(
|
|
2671
|
+
`Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
|
|
2672
|
+
"FEE_EXCEEDS_VALUE",
|
|
2673
|
+
{ fee: fee.toString(), value: utxo.value.toString() }
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
if (out < DUST_LIMIT_SATS) {
|
|
2677
|
+
throw new AggregationCohortError(
|
|
2678
|
+
`Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
|
|
2679
|
+
"DUST_OUTPUT",
|
|
2680
|
+
{ output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
const internalKey = (0, import_musig24.keyAggExport)((0, import_musig24.keyAggregate)((0, import_musig24.sortKeys)(cohortKeys)));
|
|
2684
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
2685
|
+
recoveryKey,
|
|
2686
|
+
recoverySequence,
|
|
2687
|
+
cohortKeys,
|
|
2688
|
+
fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
|
|
2689
|
+
});
|
|
2690
|
+
const net = (0, import_bitcoin3.getNetwork)(network);
|
|
2691
|
+
const payment = (0, import_btc_signer6.p2tr)(internalKey, leaves, net, true);
|
|
2692
|
+
if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
|
|
2693
|
+
throw new AggregationCohortError(
|
|
2694
|
+
`Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
|
|
2695
|
+
"BEACON_ADDRESS_MISMATCH",
|
|
2696
|
+
{ reconstructed: payment.address, expected: beaconAddress }
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
const tx = new import_btc_signer6.Transaction({ version: 2, allowUnknownInputs: true });
|
|
2700
|
+
tx.addInput({
|
|
2701
|
+
txid: utxo.txid,
|
|
2702
|
+
index: utxo.vout,
|
|
2703
|
+
witnessUtxo: { script: payment.script, amount: utxo.value },
|
|
2704
|
+
tapLeafScript: payment.tapLeafScript,
|
|
2705
|
+
sequence: recoverySequence
|
|
2706
|
+
});
|
|
2707
|
+
tx.addOutputAddress(destinationAddress, out, net);
|
|
2708
|
+
tx.signIdx(recoverySecretKey, 0);
|
|
2709
|
+
tx.finalize();
|
|
2710
|
+
return tx;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
1870
2713
|
// src/core/aggregation/logger.ts
|
|
1871
2714
|
var CONSOLE_LOGGER = {
|
|
1872
2715
|
debug: (msg, ...args) => console.debug(msg, ...args),
|
|
@@ -1894,6 +2737,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
|
|
|
1894
2737
|
]);
|
|
1895
2738
|
var UPDATE_VALUES = /* @__PURE__ */ new Set([
|
|
1896
2739
|
SUBMIT_UPDATE,
|
|
2740
|
+
SUBMIT_NONINCLUDED,
|
|
1897
2741
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
1898
2742
|
VALIDATION_ACK
|
|
1899
2743
|
]);
|
|
@@ -1901,7 +2745,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
|
|
|
1901
2745
|
AUTHORIZATION_REQUEST,
|
|
1902
2746
|
NONCE_CONTRIBUTION,
|
|
1903
2747
|
AGGREGATED_NONCE,
|
|
1904
|
-
SIGNATURE_AUTHORIZATION
|
|
2748
|
+
SIGNATURE_AUTHORIZATION,
|
|
2749
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
2750
|
+
FALLBACK_SIGNATURE
|
|
1905
2751
|
]);
|
|
1906
2752
|
function isAggregationMessageType(type) {
|
|
1907
2753
|
return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
|
|
@@ -2129,7 +2975,7 @@ var Identifier = class _Identifier {
|
|
|
2129
2975
|
|
|
2130
2976
|
// src/core/aggregation/transport/http/envelope.ts
|
|
2131
2977
|
var import_common8 = require("@did-btcr2/common");
|
|
2132
|
-
var
|
|
2978
|
+
var import_utils5 = require("@noble/hashes/utils");
|
|
2133
2979
|
|
|
2134
2980
|
// src/core/aggregation/transport/http/errors.ts
|
|
2135
2981
|
var HttpTransportError = class extends TransportAdapterError {
|
|
@@ -2157,7 +3003,7 @@ var DEFAULT_NONCE_LEN_BYTES = 16;
|
|
|
2157
3003
|
// src/core/aggregation/transport/http/envelope.ts
|
|
2158
3004
|
function signEnvelope(message, sender, opts = {}) {
|
|
2159
3005
|
const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2160
|
-
const nonce = opts.nonce ?? (0,
|
|
3006
|
+
const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2161
3007
|
const messageJson = normalizeForWire(normalizeMessage(message));
|
|
2162
3008
|
const unsigned = {
|
|
2163
3009
|
v: HTTP_ENVELOPE_VERSION,
|
|
@@ -2169,7 +3015,7 @@ function signEnvelope(message, sender, opts = {}) {
|
|
|
2169
3015
|
};
|
|
2170
3016
|
const digest = (0, import_common8.canonicalHashBytes)(unsigned);
|
|
2171
3017
|
const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2172
|
-
return { ...unsigned, sig: (0,
|
|
3018
|
+
return { ...unsigned, sig: (0, import_utils5.bytesToHex)(sig) };
|
|
2173
3019
|
}
|
|
2174
3020
|
function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
2175
3021
|
if (envelope.v !== HTTP_ENVELOPE_VERSION) {
|
|
@@ -2206,7 +3052,7 @@ function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
|
2206
3052
|
}
|
|
2207
3053
|
let sigBytes;
|
|
2208
3054
|
try {
|
|
2209
|
-
sigBytes = (0,
|
|
3055
|
+
sigBytes = (0, import_utils5.hexToBytes)(envelope.sig);
|
|
2210
3056
|
} catch {
|
|
2211
3057
|
throw new HttpTransportError(
|
|
2212
3058
|
"Envelope signature is not valid hex",
|
|
@@ -2239,7 +3085,7 @@ function normalizeMessage(message) {
|
|
|
2239
3085
|
}
|
|
2240
3086
|
function normalizeForWire(value) {
|
|
2241
3087
|
if (value instanceof Uint8Array) {
|
|
2242
|
-
return { __bytes: (0,
|
|
3088
|
+
return { __bytes: (0, import_utils5.bytesToHex)(value) };
|
|
2243
3089
|
}
|
|
2244
3090
|
if (Array.isArray(value)) {
|
|
2245
3091
|
return value.map((v) => normalizeForWire(v));
|
|
@@ -2258,7 +3104,7 @@ function reviveFromWire(value) {
|
|
|
2258
3104
|
const rec = value;
|
|
2259
3105
|
const keys = Object.keys(rec);
|
|
2260
3106
|
if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
|
|
2261
|
-
return (0,
|
|
3107
|
+
return (0, import_utils5.hexToBytes)(rec.__bytes);
|
|
2262
3108
|
}
|
|
2263
3109
|
const out = {};
|
|
2264
3110
|
for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
|
|
@@ -2272,11 +3118,11 @@ function reviveFromWire(value) {
|
|
|
2272
3118
|
|
|
2273
3119
|
// src/core/aggregation/transport/http/request-auth.ts
|
|
2274
3120
|
var import_common9 = require("@did-btcr2/common");
|
|
2275
|
-
var
|
|
3121
|
+
var import_utils6 = require("@noble/hashes/utils");
|
|
2276
3122
|
var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
|
|
2277
3123
|
function buildRequestAuth(did, keys, path, opts = {}) {
|
|
2278
3124
|
const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2279
|
-
const nonce = opts.nonce ?? (0,
|
|
3125
|
+
const nonce = opts.nonce ?? (0, import_utils6.bytesToHex)((0, import_utils6.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2280
3126
|
const digest = (0, import_common9.canonicalHashBytes)({
|
|
2281
3127
|
v: HTTP_ENVELOPE_VERSION,
|
|
2282
3128
|
did,
|
|
@@ -2285,7 +3131,7 @@ function buildRequestAuth(did, keys, path, opts = {}) {
|
|
|
2285
3131
|
path
|
|
2286
3132
|
});
|
|
2287
3133
|
const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2288
|
-
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0,
|
|
3134
|
+
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils6.bytesToHex)(sig)}`;
|
|
2289
3135
|
}
|
|
2290
3136
|
function parseRequestAuth(headerValue) {
|
|
2291
3137
|
const prefix = `${REQUEST_AUTH_SCHEME} `;
|
|
@@ -2336,7 +3182,7 @@ function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
|
|
|
2336
3182
|
}
|
|
2337
3183
|
let sigBytes;
|
|
2338
3184
|
try {
|
|
2339
|
-
sigBytes = (0,
|
|
3185
|
+
sigBytes = (0, import_utils6.hexToBytes)(parsed.sig);
|
|
2340
3186
|
} catch {
|
|
2341
3187
|
throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
|
|
2342
3188
|
}
|
|
@@ -3315,7 +4161,7 @@ function flattenMessage2(msg) {
|
|
|
3315
4161
|
|
|
3316
4162
|
// src/core/aggregation/transport/nostr.ts
|
|
3317
4163
|
var import_keypair6 = require("@did-btcr2/keypair");
|
|
3318
|
-
var
|
|
4164
|
+
var import_utils7 = require("@noble/hashes/utils");
|
|
3319
4165
|
var import_nostr_tools = require("nostr-tools");
|
|
3320
4166
|
var import_pool = require("nostr-tools/pool");
|
|
3321
4167
|
var DEFAULT_NOSTR_RELAYS = [
|
|
@@ -3513,14 +4359,14 @@ var NostrTransport = class _NostrTransport {
|
|
|
3513
4359
|
}
|
|
3514
4360
|
const senderKeys = actor.keys;
|
|
3515
4361
|
const tags = [
|
|
3516
|
-
["p", (0,
|
|
4362
|
+
["p", (0, import_utils7.bytesToHex)(senderKeys.publicKey.x)],
|
|
3517
4363
|
["t", type]
|
|
3518
4364
|
];
|
|
3519
4365
|
if (to) {
|
|
3520
4366
|
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
3521
4367
|
if (recipientPkBytes) {
|
|
3522
4368
|
const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3523
|
-
tags.push(["p", (0,
|
|
4369
|
+
tags.push(["p", (0, import_utils7.bytesToHex)(recipientPk.x)]);
|
|
3524
4370
|
}
|
|
3525
4371
|
}
|
|
3526
4372
|
if (isKeygenMessageType(type)) {
|
|
@@ -3553,7 +4399,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3553
4399
|
const recipientPk = new import_keypair6.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3554
4400
|
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
3555
4401
|
senderKeys.secretKey.bytes,
|
|
3556
|
-
(0,
|
|
4402
|
+
(0, import_utils7.bytesToHex)(recipientPk.x)
|
|
3557
4403
|
);
|
|
3558
4404
|
const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
|
|
3559
4405
|
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
@@ -3576,7 +4422,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3576
4422
|
* backfill historical events to late subscribers: republishing gives late
|
|
3577
4423
|
* joiners a window to discover the message without requiring protocol
|
|
3578
4424
|
* changes. Relay rate-limit / publish failures inside the interval are
|
|
3579
|
-
* caught and logged rather than propagated
|
|
4425
|
+
* caught and logged rather than propagated - the caller should stop the
|
|
3580
4426
|
* repeater once the protocol condition is satisfied.
|
|
3581
4427
|
*/
|
|
3582
4428
|
publishRepeating(message, sender, intervalMs, recipient) {
|
|
@@ -3607,7 +4453,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3607
4453
|
*/
|
|
3608
4454
|
#subscribeDirected(did, entry) {
|
|
3609
4455
|
if (!this.pool) return;
|
|
3610
|
-
const pkHex = (0,
|
|
4456
|
+
const pkHex = (0, import_utils7.bytesToHex)(entry.keys.publicKey.x);
|
|
3611
4457
|
const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
|
|
3612
4458
|
onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
3613
4459
|
onevent: this.#makeActorEventHandler(did)
|
|
@@ -3625,7 +4471,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3625
4471
|
return async (event) => {
|
|
3626
4472
|
const actor = this.#actors.get(actorDid);
|
|
3627
4473
|
if (!actor) return;
|
|
3628
|
-
if (event.pubkey === (0,
|
|
4474
|
+
if (event.pubkey === (0, import_utils7.bytesToHex)(actor.keys.publicKey.x)) return;
|
|
3629
4475
|
let message;
|
|
3630
4476
|
try {
|
|
3631
4477
|
if (event.kind === 1) {
|
|
@@ -3738,7 +4584,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3738
4584
|
*/
|
|
3739
4585
|
static #jsonReplacer(_key, value) {
|
|
3740
4586
|
if (value instanceof Uint8Array) {
|
|
3741
|
-
return { __bytes: (0,
|
|
4587
|
+
return { __bytes: (0, import_utils7.bytesToHex)(value) };
|
|
3742
4588
|
}
|
|
3743
4589
|
return value;
|
|
3744
4590
|
}
|
|
@@ -3756,7 +4602,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
3756
4602
|
*/
|
|
3757
4603
|
static #jsonReviver(_key, value) {
|
|
3758
4604
|
if (value && typeof value === "object" && "__bytes" in value) {
|
|
3759
|
-
return (0,
|
|
4605
|
+
return (0, import_utils7.hexToBytes)(value.__bytes);
|
|
3760
4606
|
}
|
|
3761
4607
|
return value;
|
|
3762
4608
|
}
|
|
@@ -3791,7 +4637,7 @@ var TransportFactory = class {
|
|
|
3791
4637
|
};
|
|
3792
4638
|
|
|
3793
4639
|
// src/core/aggregation/transport/in-memory.ts
|
|
3794
|
-
var
|
|
4640
|
+
var import_utils8 = require("@noble/hashes/utils");
|
|
3795
4641
|
var InMemoryBus = class {
|
|
3796
4642
|
#transports = /* @__PURE__ */ new Set();
|
|
3797
4643
|
/** Attach a transport to this bus. Called by the transport's constructor. */
|
|
@@ -3810,8 +4656,8 @@ var InMemoryBus = class {
|
|
|
3810
4656
|
async deliver(message, _sender, recipient) {
|
|
3811
4657
|
const type = message.type;
|
|
3812
4658
|
if (!type) return;
|
|
3813
|
-
const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0,
|
|
3814
|
-
const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0,
|
|
4659
|
+
const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils8.bytesToHex)(v) } : v;
|
|
4660
|
+
const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils8.hexToBytes)(v.__bytes) : v;
|
|
3815
4661
|
const raw = JSON.parse(JSON.stringify(message, replacer), reviver);
|
|
3816
4662
|
const serialized = { ...raw, ...raw.body ?? {} };
|
|
3817
4663
|
if (!recipient) {
|
|
@@ -4017,35 +4863,29 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4017
4863
|
session;
|
|
4018
4864
|
#transport;
|
|
4019
4865
|
#did;
|
|
4020
|
-
#
|
|
4866
|
+
#defaultConfig;
|
|
4021
4867
|
#onOptInReceived;
|
|
4022
4868
|
#onReadyToFinalize;
|
|
4023
4869
|
#onProvideTxData;
|
|
4024
4870
|
#cohortTtlMs;
|
|
4025
4871
|
#phaseTimeoutMs;
|
|
4026
4872
|
#advertRepeatIntervalMs;
|
|
4027
|
-
#
|
|
4873
|
+
#autoFallbackOnStall;
|
|
4874
|
+
/** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
|
|
4875
|
+
static #SIGNING_PHASES = [
|
|
4876
|
+
"SigningStarted" /* SigningStarted */,
|
|
4877
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
4878
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
4879
|
+
];
|
|
4880
|
+
/** Per-cohort run state, keyed by cohortId. */
|
|
4881
|
+
#contexts = /* @__PURE__ */ new Map();
|
|
4028
4882
|
#handlersRegistered = false;
|
|
4029
4883
|
#stopped = false;
|
|
4030
|
-
/**
|
|
4031
|
-
* Guard against the async race where two concurrent #handleOptIn invocations
|
|
4032
|
-
* both pass the `participants.length >= minParticipants` check before either
|
|
4033
|
-
* mutates the cohort phase. Set synchronously before any `await` so subsequent
|
|
4034
|
-
* handlers observe it on their next resumption.
|
|
4035
|
-
*/
|
|
4036
|
-
#finalizing = false;
|
|
4037
|
-
#resolveRun;
|
|
4038
|
-
#rejectRun;
|
|
4039
|
-
#cohortTtlTimer;
|
|
4040
|
-
#phaseTimer;
|
|
4041
|
-
#lastObservedPhase;
|
|
4042
|
-
/** Stop handle for the repeating COHORT_ADVERT publish loop. */
|
|
4043
|
-
#stopAdvertRepeat;
|
|
4044
4884
|
constructor(options) {
|
|
4045
4885
|
super();
|
|
4046
4886
|
this.#transport = options.transport;
|
|
4047
4887
|
this.#did = options.did;
|
|
4048
|
-
this.#
|
|
4888
|
+
this.#defaultConfig = options.config;
|
|
4049
4889
|
this.#onOptInReceived = options.onOptInReceived ?? (async () => ({ accepted: true }));
|
|
4050
4890
|
this.#onReadyToFinalize = options.onReadyToFinalize ?? (async ({ acceptedCount, minRequired }) => ({
|
|
4051
4891
|
finalize: acceptedCount >= minRequired
|
|
@@ -4054,6 +4894,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4054
4894
|
this.#cohortTtlMs = options.cohortTtlMs;
|
|
4055
4895
|
this.#phaseTimeoutMs = options.phaseTimeoutMs;
|
|
4056
4896
|
this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
|
|
4897
|
+
this.#autoFallbackOnStall = options.autoFallbackOnStall ?? false;
|
|
4057
4898
|
this.session = new AggregationService({
|
|
4058
4899
|
// The coordinator never signs, so the state machine receives only the
|
|
4059
4900
|
// public half of the operator's keypair (see ADR 038). The full keypair
|
|
@@ -4063,55 +4904,126 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4063
4904
|
maxUpdateSizeBytes: options.maxUpdateSizeBytes
|
|
4064
4905
|
});
|
|
4065
4906
|
}
|
|
4907
|
+
/** Resolve the {@link RunContext} an inbound message belongs to, by cohortId. */
|
|
4908
|
+
#contextFor(msg) {
|
|
4909
|
+
const cohortId = msg.body?.cohortId;
|
|
4910
|
+
if (!cohortId) return void 0;
|
|
4911
|
+
return this.#contexts.get(cohortId);
|
|
4912
|
+
}
|
|
4913
|
+
/**
|
|
4914
|
+
* Drain any silent rejections the state machine recorded for a cohort during
|
|
4915
|
+
* the most recent receive() and surface them as `message-rejected` events.
|
|
4916
|
+
*/
|
|
4917
|
+
#drainRejections(ctx) {
|
|
4918
|
+
for (const r of this.session.drainRejections(ctx.cohortId)) {
|
|
4919
|
+
this.emit("message-rejected", { cohortId: ctx.cohortId, ...r });
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4066
4922
|
/**
|
|
4067
|
-
*
|
|
4068
|
-
*
|
|
4069
|
-
*
|
|
4923
|
+
* Advertise a new cohort and begin driving it to completion. Callable many
|
|
4924
|
+
* times on one runner; each cohort runs concurrently and independently.
|
|
4925
|
+
*
|
|
4926
|
+
* @param config Per-cohort conditions + network (see {@link CohortConfig}).
|
|
4927
|
+
* @returns The new cohort's id and a `completion` promise that resolves with
|
|
4928
|
+
* that cohort's {@link AggregationResult} (or rejects if it fails/stalls).
|
|
4929
|
+
* @throws If the runner has been stopped, or the config is invalid
|
|
4930
|
+
* (fail-fast via `createCohort`).
|
|
4070
4931
|
*/
|
|
4071
|
-
|
|
4072
|
-
if (
|
|
4073
|
-
|
|
4074
|
-
|
|
4932
|
+
advertiseCohort(config) {
|
|
4933
|
+
if (this.#stopped) {
|
|
4934
|
+
throw new AggregationServiceError("Cannot advertise on a stopped runner.", "RUNNER_STOPPED", {});
|
|
4935
|
+
}
|
|
4936
|
+
this.#registerHandlers();
|
|
4937
|
+
const cohortId = this.session.createCohort(config);
|
|
4938
|
+
let resolve;
|
|
4939
|
+
let reject;
|
|
4940
|
+
const completion = new Promise((res, rej) => {
|
|
4941
|
+
resolve = res;
|
|
4942
|
+
reject = rej;
|
|
4943
|
+
});
|
|
4944
|
+
const ctx = {
|
|
4945
|
+
cohortId,
|
|
4946
|
+
config,
|
|
4947
|
+
resolve,
|
|
4948
|
+
reject,
|
|
4949
|
+
completion,
|
|
4950
|
+
finalizing: false,
|
|
4951
|
+
settled: false
|
|
4952
|
+
};
|
|
4953
|
+
this.#contexts.set(cohortId, ctx);
|
|
4954
|
+
try {
|
|
4955
|
+
this.#startTimers(ctx);
|
|
4956
|
+
const advertMsgs = this.session.advertise(cohortId);
|
|
4957
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
4958
|
+
this.emit("cohort-advertised", { cohortId });
|
|
4959
|
+
if (this.#advertRepeatIntervalMs > 0) {
|
|
4960
|
+
this.#startAdvertRepeat(ctx, advertMsgs);
|
|
4961
|
+
} else {
|
|
4962
|
+
this.#sendAll(advertMsgs).catch((err) => this.#failCohort(ctx, err));
|
|
4963
|
+
}
|
|
4964
|
+
} catch (err) {
|
|
4965
|
+
this.#failCohort(ctx, err);
|
|
4075
4966
|
}
|
|
4967
|
+
return { cohortId, completion };
|
|
4076
4968
|
}
|
|
4077
4969
|
/**
|
|
4078
|
-
* Run
|
|
4079
|
-
*
|
|
4970
|
+
* Run a single cohort to completion using the `config` supplied in the
|
|
4971
|
+
* runner options. Thin convenience over {@link advertiseCohort} for the
|
|
4972
|
+
* single-cohort case (and the path {@link AggregationRunner.solo} rides).
|
|
4080
4973
|
*
|
|
4081
4974
|
* @returns {Promise<AggregationResult>} The final result with signature and signed tx.
|
|
4082
4975
|
*/
|
|
4083
4976
|
run() {
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4977
|
+
if (!this.#defaultConfig) {
|
|
4978
|
+
return Promise.reject(new AggregationServiceError(
|
|
4979
|
+
"run() requires `config` in the runner options; use advertiseCohort(config) to drive cohorts explicitly.",
|
|
4980
|
+
"MISSING_COHORT_CONFIG",
|
|
4981
|
+
{}
|
|
4982
|
+
));
|
|
4983
|
+
}
|
|
4984
|
+
try {
|
|
4985
|
+
return this.advertiseCohort(this.#defaultConfig).completion;
|
|
4986
|
+
} catch (err) {
|
|
4987
|
+
return Promise.reject(err);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
/**
|
|
4991
|
+
* Wait for every currently-outstanding cohort to settle and return the
|
|
4992
|
+
* successful results. Dynamic drain: cohorts advertised while this is pending
|
|
4993
|
+
* are included, and it resolves only once no cohorts remain. Failed cohorts
|
|
4994
|
+
* are surfaced via `error` / `cohort-failed` events and their rejected
|
|
4995
|
+
* `completion` promises; they are omitted from the returned array (this
|
|
4996
|
+
* method does not throw). Bound long-running cohorts with `cohortTtlMs` /
|
|
4997
|
+
* `phaseTimeoutMs` or this may never resolve.
|
|
4998
|
+
*
|
|
4999
|
+
* @returns {Promise<AggregationResult[]>} Results of the cohorts that completed.
|
|
5000
|
+
*/
|
|
5001
|
+
async runAll() {
|
|
5002
|
+
const collected = /* @__PURE__ */ new Map();
|
|
5003
|
+
const onComplete = (result) => {
|
|
5004
|
+
collected.set(result.cohortId, result);
|
|
5005
|
+
};
|
|
5006
|
+
this.on("signing-complete", onComplete);
|
|
5007
|
+
try {
|
|
5008
|
+
while (this.#contexts.size > 0) {
|
|
5009
|
+
await Promise.allSettled([...this.#contexts.values()].map((c) => c.completion));
|
|
4101
5010
|
}
|
|
4102
|
-
}
|
|
5011
|
+
} finally {
|
|
5012
|
+
this.off("signing-complete", onComplete);
|
|
5013
|
+
}
|
|
5014
|
+
return [...collected.values()];
|
|
4103
5015
|
}
|
|
4104
5016
|
/**
|
|
4105
|
-
* Begin publishing
|
|
4106
|
-
* until
|
|
4107
|
-
*
|
|
5017
|
+
* Begin publishing a cohort's advert immediately and on a repeating interval
|
|
5018
|
+
* until the cohort's advert loop is stopped. Each advert is broadcast (no
|
|
5019
|
+
* recipient) via the transport's `publishRepeating` primitive.
|
|
4108
5020
|
*/
|
|
4109
|
-
#startAdvertRepeat(advertMsgs) {
|
|
5021
|
+
#startAdvertRepeat(ctx, advertMsgs) {
|
|
4110
5022
|
const stops = [];
|
|
4111
5023
|
for (const msg of advertMsgs) {
|
|
4112
5024
|
stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
|
|
4113
5025
|
}
|
|
4114
|
-
|
|
5026
|
+
ctx.stopAdvertRepeat = () => {
|
|
4115
5027
|
for (const stop of stops) {
|
|
4116
5028
|
try {
|
|
4117
5029
|
stop();
|
|
@@ -4120,81 +5032,176 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4120
5032
|
}
|
|
4121
5033
|
};
|
|
4122
5034
|
}
|
|
4123
|
-
/** Stop
|
|
4124
|
-
#stopAdvertRepeating() {
|
|
4125
|
-
if (!
|
|
4126
|
-
const stop =
|
|
4127
|
-
|
|
5035
|
+
/** Stop a cohort's advert republish loop. Idempotent. */
|
|
5036
|
+
#stopAdvertRepeating(ctx) {
|
|
5037
|
+
if (!ctx.stopAdvertRepeat) return;
|
|
5038
|
+
const stop = ctx.stopAdvertRepeat;
|
|
5039
|
+
ctx.stopAdvertRepeat = void 0;
|
|
4128
5040
|
stop();
|
|
4129
5041
|
}
|
|
4130
|
-
/** Schedule cohort TTL + phase timeout
|
|
4131
|
-
#startTimers() {
|
|
5042
|
+
/** Schedule a cohort's TTL + phase timeout when it is advertised. */
|
|
5043
|
+
#startTimers(ctx) {
|
|
4132
5044
|
if (this.#cohortTtlMs !== void 0) {
|
|
4133
|
-
|
|
4134
|
-
const reason = `Cohort ${
|
|
4135
|
-
this.emit("cohort-failed", { cohortId:
|
|
4136
|
-
this.#
|
|
5045
|
+
ctx.cohortTtlTimer = setTimeout(() => {
|
|
5046
|
+
const reason = `Cohort ${ctx.cohortId} exceeded TTL of ${this.#cohortTtlMs}ms`;
|
|
5047
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
5048
|
+
this.#failCohort(ctx, new Error(reason));
|
|
4137
5049
|
}, this.#cohortTtlMs);
|
|
4138
5050
|
}
|
|
4139
|
-
this.#resetPhaseTimer();
|
|
5051
|
+
this.#resetPhaseTimer(ctx);
|
|
4140
5052
|
}
|
|
4141
|
-
/** Reset
|
|
4142
|
-
#resetPhaseTimer() {
|
|
4143
|
-
if (
|
|
4144
|
-
|
|
5053
|
+
/** Reset a cohort's per-phase stall timer. Called when a phase transition is observed. */
|
|
5054
|
+
#resetPhaseTimer(ctx) {
|
|
5055
|
+
if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
|
|
5056
|
+
ctx.phaseTimer = void 0;
|
|
4145
5057
|
if (this.#phaseTimeoutMs === void 0) return;
|
|
4146
|
-
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
this.#
|
|
5058
|
+
ctx.phaseTimer = setTimeout(() => {
|
|
5059
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
5060
|
+
const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
|
|
5061
|
+
if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
|
|
5062
|
+
this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
|
|
5066
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
5067
|
+
this.#failCohort(ctx, new Error(reason));
|
|
4150
5068
|
}, this.#phaseTimeoutMs);
|
|
4151
5069
|
}
|
|
4152
|
-
/**
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
5070
|
+
/**
|
|
5071
|
+
* Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
|
|
5072
|
+
* fallback (script-path) signatures instead (ADR 042). Idempotent and safe
|
|
5073
|
+
* against the optimistic completion: it commits the cohort to the fallback
|
|
5074
|
+
* path synchronously (the `committedPath` latch) before sending anything, so a
|
|
5075
|
+
* late optimistic signature can no longer complete-and-broadcast a competing
|
|
5076
|
+
* spend of the same UTXO. No-op if the cohort is unknown, already settled, or
|
|
5077
|
+
* already committed to a path.
|
|
5078
|
+
*
|
|
5079
|
+
* Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
|
|
5080
|
+
* set; otherwise call it from an operator decision (a UI "fall back now"
|
|
5081
|
+
* action). Throws only if the underlying state machine rejects the transition
|
|
5082
|
+
* (e.g. signing has not started).
|
|
5083
|
+
*/
|
|
5084
|
+
async triggerFallback(cohortId) {
|
|
5085
|
+
const ctx = this.#contexts.get(cohortId);
|
|
5086
|
+
if (!ctx || ctx.settled || ctx.committedPath) return;
|
|
5087
|
+
const messages = this.session.startFallbackSigning(cohortId);
|
|
5088
|
+
ctx.committedPath = "fallback";
|
|
5089
|
+
this.#stopAdvertRepeating(ctx);
|
|
5090
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5091
|
+
const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
|
|
5092
|
+
this.emit("fallback-started", { cohortId, sessionId });
|
|
5093
|
+
await this.#sendAll(messages);
|
|
5094
|
+
}
|
|
5095
|
+
/** Detect a phase change for a cohort since the last observation and reset its phase timer. */
|
|
5096
|
+
#onPhaseMaybeChanged(ctx) {
|
|
5097
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
5098
|
+
if (phase !== ctx.lastObservedPhase) {
|
|
5099
|
+
ctx.lastObservedPhase = phase;
|
|
5100
|
+
this.#resetPhaseTimer(ctx);
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
/** Clear a cohort's timers. Called on completion, stop, and failure. */
|
|
5104
|
+
#clearTimers(ctx) {
|
|
5105
|
+
if (ctx.cohortTtlTimer) clearTimeout(ctx.cohortTtlTimer);
|
|
5106
|
+
if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
|
|
5107
|
+
ctx.cohortTtlTimer = void 0;
|
|
5108
|
+
ctx.phaseTimer = void 0;
|
|
5109
|
+
}
|
|
5110
|
+
/**
|
|
5111
|
+
* Reclaim one cohort's runner-layer bookkeeping: stop its advert loop, clear
|
|
5112
|
+
* its timers, and drop its {@link RunContext}. Does NOT touch sibling cohorts
|
|
5113
|
+
* and does NOT detach the shared transport handlers. Leaves the cohort in the
|
|
5114
|
+
* state machine; whether that cohort's `session` state is also removed is the
|
|
5115
|
+
* caller's choice (see {@link #completeCohort} vs {@link #failCohort}).
|
|
5116
|
+
*/
|
|
5117
|
+
#disposeCohort(ctx) {
|
|
5118
|
+
this.#stopAdvertRepeating(ctx);
|
|
5119
|
+
this.#clearTimers(ctx);
|
|
5120
|
+
this.#contexts.delete(ctx.cohortId);
|
|
5121
|
+
}
|
|
5122
|
+
/**
|
|
5123
|
+
* Settle one cohort successfully. Reclaims the runner context but leaves the
|
|
5124
|
+
* completed cohort in `session` so callers can read its beaconAddress / cohort
|
|
5125
|
+
* via `session.getCohort(result.cohortId)`; reclaim it with
|
|
5126
|
+
* `session.removeCohort(cohortId)` when done. Idempotent via `ctx.settled`.
|
|
5127
|
+
*/
|
|
5128
|
+
#completeCohort(ctx, result) {
|
|
5129
|
+
if (ctx.settled) return;
|
|
5130
|
+
ctx.settled = true;
|
|
5131
|
+
this.#disposeCohort(ctx);
|
|
5132
|
+
this.emit("signing-complete", result);
|
|
5133
|
+
ctx.resolve(result);
|
|
5134
|
+
}
|
|
5135
|
+
/**
|
|
5136
|
+
* Fail one cohort. Reclaims its runner context, drops its now-dead state from
|
|
5137
|
+
* the state machine, and rejects only its completion; siblings keep running
|
|
5138
|
+
* and the shared transport handlers stay registered. Idempotent via
|
|
5139
|
+
* `ctx.settled`.
|
|
5140
|
+
*/
|
|
5141
|
+
#failCohort(ctx, err) {
|
|
5142
|
+
if (ctx.settled) return;
|
|
5143
|
+
ctx.settled = true;
|
|
5144
|
+
this.#disposeCohort(ctx);
|
|
5145
|
+
this.session.removeCohort(ctx.cohortId);
|
|
5146
|
+
this.emit("error", err);
|
|
5147
|
+
ctx.reject(err);
|
|
4160
5148
|
}
|
|
4161
|
-
/**
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
5149
|
+
/**
|
|
5150
|
+
* Stop a single cohort early without affecting the rest of the runner. Drops
|
|
5151
|
+
* the cohort's state machine state; its `completion` promise rejects with a
|
|
5152
|
+
* stopped error.
|
|
5153
|
+
*/
|
|
5154
|
+
stopCohort(cohortId) {
|
|
5155
|
+
const ctx = this.#contexts.get(cohortId);
|
|
5156
|
+
if (!ctx || ctx.settled) return;
|
|
5157
|
+
ctx.settled = true;
|
|
5158
|
+
this.#disposeCohort(ctx);
|
|
5159
|
+
this.session.removeCohort(cohortId);
|
|
5160
|
+
ctx.reject(new AggregationServiceError(`Cohort ${cohortId} stopped.`, "COHORT_STOPPED", { cohortId }));
|
|
4167
5161
|
}
|
|
4168
5162
|
/**
|
|
4169
|
-
* Stop the runner
|
|
4170
|
-
* handlers so a restart or a new runner doesn't inherit
|
|
5163
|
+
* Stop the whole runner. Fails every outstanding cohort, then detaches the
|
|
5164
|
+
* shared transport handlers so a restart or a new runner doesn't inherit
|
|
5165
|
+
* stale dispatch. Safe to call repeatedly.
|
|
4171
5166
|
*/
|
|
4172
5167
|
stop() {
|
|
4173
5168
|
this.#stopped = true;
|
|
4174
|
-
this.#
|
|
4175
|
-
|
|
5169
|
+
for (const ctx of [...this.#contexts.values()]) {
|
|
5170
|
+
if (ctx.settled) continue;
|
|
5171
|
+
ctx.settled = true;
|
|
5172
|
+
this.#disposeCohort(ctx);
|
|
5173
|
+
this.session.removeCohort(ctx.cohortId);
|
|
5174
|
+
ctx.reject(new AggregationServiceError("Service runner stopped.", "RUNNER_STOPPED", { cohortId: ctx.cohortId }));
|
|
5175
|
+
}
|
|
5176
|
+
this.#contexts.clear();
|
|
4176
5177
|
this.#unregisterHandlers();
|
|
4177
|
-
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
4178
5178
|
}
|
|
4179
5179
|
/** Message types this runner listens for on the transport. */
|
|
4180
5180
|
static #HANDLED_MESSAGE_TYPES = [
|
|
4181
5181
|
COHORT_OPT_IN,
|
|
4182
5182
|
SUBMIT_UPDATE,
|
|
5183
|
+
SUBMIT_NONINCLUDED,
|
|
4183
5184
|
VALIDATION_ACK,
|
|
4184
5185
|
NONCE_CONTRIBUTION,
|
|
4185
|
-
SIGNATURE_AUTHORIZATION
|
|
5186
|
+
SIGNATURE_AUTHORIZATION,
|
|
5187
|
+
FALLBACK_SIGNATURE
|
|
4186
5188
|
];
|
|
4187
5189
|
/**
|
|
4188
|
-
* Internal: handler registration with the transport. Idempotent.
|
|
5190
|
+
* Internal: handler registration with the transport. Idempotent. Handlers
|
|
5191
|
+
* are DID-scoped and cohort-agnostic - one registration serves every cohort
|
|
5192
|
+
* this runner drives; demux to the right {@link RunContext} happens in each
|
|
5193
|
+
* handler via the inbound message's cohortId.
|
|
4189
5194
|
*/
|
|
4190
5195
|
#registerHandlers() {
|
|
4191
5196
|
if (this.#handlersRegistered) return;
|
|
4192
5197
|
this.#handlersRegistered = true;
|
|
4193
5198
|
this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
|
|
4194
5199
|
this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
|
|
5200
|
+
this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
|
|
4195
5201
|
this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
|
|
4196
5202
|
this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
|
|
4197
5203
|
this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
|
|
5204
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
|
|
4198
5205
|
}
|
|
4199
5206
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
4200
5207
|
#unregisterHandlers() {
|
|
@@ -4206,23 +5213,25 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4206
5213
|
}
|
|
4207
5214
|
/**
|
|
4208
5215
|
* Internal: message handlers for each protocol step. Each handler:
|
|
4209
|
-
* 1)
|
|
4210
|
-
* 2)
|
|
4211
|
-
* 3)
|
|
5216
|
+
* 1) resolves the cohort the message belongs to (by cohortId); ignores it if unknown
|
|
5217
|
+
* 2) feeds the message into the state machine via session.receive()
|
|
5218
|
+
* 3) emits a high-level event (carrying cohortId) for external observers
|
|
5219
|
+
* 4) checks if the new state triggers any automatic next steps, and if so:
|
|
4212
5220
|
* a) calls the appropriate decision callback(s)
|
|
4213
5221
|
* b) sends any resulting messages from the state machine
|
|
5222
|
+
* Errors fail only the owning cohort. A stopped runner ignores messages.
|
|
4214
5223
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
4215
5224
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4216
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
4217
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
4218
5225
|
*/
|
|
4219
5226
|
async #handleOptIn(msg) {
|
|
4220
5227
|
if (this.#stopped) return;
|
|
5228
|
+
const ctx = this.#contextFor(msg);
|
|
5229
|
+
if (!ctx) return;
|
|
4221
5230
|
try {
|
|
4222
5231
|
this.session.receive(msg);
|
|
4223
|
-
this.#drainRejections();
|
|
4224
|
-
this.#onPhaseMaybeChanged();
|
|
4225
|
-
const optIn = this.session.pendingOptIns(
|
|
5232
|
+
this.#drainRejections(ctx);
|
|
5233
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5234
|
+
const optIn = this.session.pendingOptIns(ctx.cohortId).get(msg.from);
|
|
4226
5235
|
if (!optIn) return;
|
|
4227
5236
|
this.emit("opt-in-received", optIn);
|
|
4228
5237
|
if (optIn.communicationPk) {
|
|
@@ -4230,34 +5239,34 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4230
5239
|
}
|
|
4231
5240
|
const decision = await this.#onOptInReceived(optIn);
|
|
4232
5241
|
if (!decision.accepted) return;
|
|
4233
|
-
const maxParticipants =
|
|
4234
|
-
const cohortNow = this.session.getCohort(
|
|
5242
|
+
const maxParticipants = ctx.config.maxParticipants;
|
|
5243
|
+
const cohortNow = this.session.getCohort(ctx.cohortId);
|
|
4235
5244
|
if (maxParticipants !== void 0 && cohortNow && cohortNow.participants.length >= maxParticipants) {
|
|
4236
5245
|
return;
|
|
4237
5246
|
}
|
|
4238
|
-
await this.#sendAll(this.session.acceptParticipant(
|
|
4239
|
-
this.emit("participant-accepted", { participantDid: msg.from });
|
|
4240
|
-
const cohort = this.session.getCohort(
|
|
4241
|
-
if (cohort.participants.length >=
|
|
4242
|
-
|
|
5247
|
+
await this.#sendAll(this.session.acceptParticipant(ctx.cohortId, msg.from));
|
|
5248
|
+
this.emit("participant-accepted", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
5249
|
+
const cohort = this.session.getCohort(ctx.cohortId);
|
|
5250
|
+
if (cohort.participants.length >= ctx.config.minParticipants && !ctx.finalizing) {
|
|
5251
|
+
ctx.finalizing = true;
|
|
4243
5252
|
const finalizeDecision = await this.#onReadyToFinalize({
|
|
4244
5253
|
acceptedCount: cohort.participants.length,
|
|
4245
|
-
minRequired:
|
|
5254
|
+
minRequired: ctx.config.minParticipants
|
|
4246
5255
|
});
|
|
4247
5256
|
if (!finalizeDecision.finalize) {
|
|
4248
|
-
|
|
5257
|
+
ctx.finalizing = false;
|
|
4249
5258
|
return;
|
|
4250
5259
|
}
|
|
4251
|
-
const readyMsgs = this.session.finalizeKeygen(
|
|
4252
|
-
this.#stopAdvertRepeating();
|
|
5260
|
+
const readyMsgs = this.session.finalizeKeygen(ctx.cohortId);
|
|
5261
|
+
this.#stopAdvertRepeating(ctx);
|
|
4253
5262
|
this.emit("keygen-complete", {
|
|
4254
|
-
cohortId:
|
|
5263
|
+
cohortId: ctx.cohortId,
|
|
4255
5264
|
beaconAddress: cohort.beaconAddress
|
|
4256
5265
|
});
|
|
4257
5266
|
await this.#sendAll(readyMsgs);
|
|
4258
5267
|
}
|
|
4259
5268
|
} catch (err) {
|
|
4260
|
-
this.#
|
|
5269
|
+
this.#failCohort(ctx, err);
|
|
4261
5270
|
}
|
|
4262
5271
|
}
|
|
4263
5272
|
/**
|
|
@@ -4265,23 +5274,23 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4265
5274
|
* and distributes the data for validation.
|
|
4266
5275
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
4267
5276
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4268
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
4269
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
4270
5277
|
*/
|
|
4271
5278
|
async #handleSubmitUpdate(msg) {
|
|
4272
5279
|
if (this.#stopped) return;
|
|
5280
|
+
const ctx = this.#contextFor(msg);
|
|
5281
|
+
if (!ctx) return;
|
|
4273
5282
|
try {
|
|
4274
5283
|
this.session.receive(msg);
|
|
4275
|
-
this.#drainRejections();
|
|
4276
|
-
this.#onPhaseMaybeChanged();
|
|
4277
|
-
this.emit("update-received", { participantDid: msg.from });
|
|
4278
|
-
if (this.session.getCohortPhase(
|
|
4279
|
-
const distributeMsgs = this.session.buildAndDistribute(
|
|
4280
|
-
this.emit("data-distributed", { cohortId:
|
|
5284
|
+
this.#drainRejections(ctx);
|
|
5285
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5286
|
+
this.emit("update-received", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
5287
|
+
if (this.session.getCohortPhase(ctx.cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
|
|
5288
|
+
const distributeMsgs = this.session.buildAndDistribute(ctx.cohortId);
|
|
5289
|
+
this.emit("data-distributed", { cohortId: ctx.cohortId });
|
|
4281
5290
|
await this.#sendAll(distributeMsgs);
|
|
4282
5291
|
}
|
|
4283
5292
|
} catch (err) {
|
|
4284
|
-
this.#
|
|
5293
|
+
this.#failCohort(ctx, err);
|
|
4285
5294
|
}
|
|
4286
5295
|
}
|
|
4287
5296
|
/**
|
|
@@ -4289,111 +5298,121 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
4289
5298
|
* automatically requests tx data and starts signing.
|
|
4290
5299
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
4291
5300
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4292
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
4293
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
4294
5301
|
*/
|
|
4295
5302
|
async #handleValidationAck(msg) {
|
|
4296
5303
|
if (this.#stopped) return;
|
|
5304
|
+
const ctx = this.#contextFor(msg);
|
|
5305
|
+
if (!ctx) return;
|
|
4297
5306
|
try {
|
|
4298
5307
|
this.session.receive(msg);
|
|
4299
|
-
this.#drainRejections();
|
|
4300
|
-
this.#onPhaseMaybeChanged();
|
|
5308
|
+
this.#drainRejections(ctx);
|
|
5309
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
4301
5310
|
const approved = !!msg.body?.approved;
|
|
4302
|
-
this.emit("validation-received", { participantDid: msg.from, approved });
|
|
4303
|
-
const phase = this.session.getCohortPhase(
|
|
5311
|
+
this.emit("validation-received", { cohortId: ctx.cohortId, participantDid: msg.from, approved });
|
|
5312
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
4304
5313
|
if (phase === "Failed" /* Failed */) {
|
|
4305
5314
|
const reason = `Validation rejected by participant ${msg.from}`;
|
|
4306
|
-
this.emit("cohort-failed", { cohortId:
|
|
4307
|
-
this.#
|
|
5315
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
5316
|
+
this.#failCohort(ctx, new Error(reason));
|
|
4308
5317
|
return;
|
|
4309
5318
|
}
|
|
4310
5319
|
if (phase === "Validated" /* Validated */) {
|
|
4311
|
-
const cohort = this.session.getCohort(
|
|
5320
|
+
const cohort = this.session.getCohort(ctx.cohortId);
|
|
4312
5321
|
const txData = await this.#onProvideTxData({
|
|
4313
|
-
cohortId:
|
|
5322
|
+
cohortId: ctx.cohortId,
|
|
4314
5323
|
beaconAddress: cohort.beaconAddress,
|
|
4315
5324
|
signalBytes: cohort.signalBytes
|
|
4316
5325
|
});
|
|
4317
|
-
const authMsgs = this.session.startSigning(
|
|
4318
|
-
const sessionId = this.session.getSigningSessionId(
|
|
4319
|
-
this.emit("signing-started", { sessionId });
|
|
5326
|
+
const authMsgs = this.session.startSigning(ctx.cohortId, txData);
|
|
5327
|
+
const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
|
|
5328
|
+
this.emit("signing-started", { cohortId: ctx.cohortId, sessionId });
|
|
4320
5329
|
await this.#sendAll(authMsgs);
|
|
4321
5330
|
}
|
|
4322
5331
|
} catch (err) {
|
|
4323
|
-
this.#
|
|
5332
|
+
this.#failCohort(ctx, err);
|
|
4324
5333
|
}
|
|
4325
5334
|
}
|
|
4326
5335
|
/**
|
|
4327
|
-
* Handler for receiving nonce contributions
|
|
4328
|
-
*
|
|
5336
|
+
* Handler for receiving nonce contributions. When all nonces are received, sends the aggregated
|
|
5337
|
+
* nonce back to the cohort.
|
|
4329
5338
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
4330
5339
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4331
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
4332
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
4333
5340
|
*/
|
|
4334
5341
|
async #handleNonceContribution(msg) {
|
|
4335
5342
|
if (this.#stopped) return;
|
|
5343
|
+
const ctx = this.#contextFor(msg);
|
|
5344
|
+
if (!ctx) return;
|
|
4336
5345
|
try {
|
|
4337
5346
|
this.session.receive(msg);
|
|
4338
|
-
this.#drainRejections();
|
|
4339
|
-
this.#onPhaseMaybeChanged();
|
|
4340
|
-
this.emit("nonce-received", { participantDid: msg.from });
|
|
4341
|
-
if (this.session.getCohortPhase(
|
|
4342
|
-
await this.#sendAll(this.session.sendAggregatedNonce(
|
|
5347
|
+
this.#drainRejections(ctx);
|
|
5348
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5349
|
+
this.emit("nonce-received", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
5350
|
+
if (this.session.getCohortPhase(ctx.cohortId) === "NoncesCollected" /* NoncesCollected */) {
|
|
5351
|
+
await this.#sendAll(this.session.sendAggregatedNonce(ctx.cohortId));
|
|
4343
5352
|
}
|
|
4344
5353
|
} catch (err) {
|
|
4345
|
-
this.#
|
|
5354
|
+
this.#failCohort(ctx, err);
|
|
4346
5355
|
}
|
|
4347
5356
|
}
|
|
4348
5357
|
/**
|
|
4349
5358
|
* Handler for receiving signature authorizations. When all partial signatures are received, the
|
|
4350
|
-
* session automatically completes
|
|
5359
|
+
* session automatically completes; the final result is emitted and the cohort's completion
|
|
5360
|
+
* promise resolves.
|
|
4351
5361
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
4352
5362
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
4353
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
4354
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
4355
5363
|
*/
|
|
4356
5364
|
async #handleSignatureAuthorization(msg) {
|
|
4357
5365
|
if (this.#stopped) return;
|
|
5366
|
+
const ctx = this.#contextFor(msg);
|
|
5367
|
+
if (!ctx) return;
|
|
4358
5368
|
try {
|
|
4359
5369
|
this.session.receive(msg);
|
|
4360
|
-
this.#drainRejections();
|
|
4361
|
-
this.#onPhaseMaybeChanged();
|
|
4362
|
-
|
|
5370
|
+
this.#drainRejections(ctx);
|
|
5371
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5372
|
+
if (ctx.committedPath === "fallback") return;
|
|
5373
|
+
const result = this.session.getResult(ctx.cohortId);
|
|
4363
5374
|
if (result) {
|
|
4364
|
-
|
|
4365
|
-
this.#
|
|
4366
|
-
this.emit("signing-complete", result);
|
|
4367
|
-
this.#resolveRun?.(result);
|
|
5375
|
+
ctx.committedPath = "optimistic";
|
|
5376
|
+
this.#completeCohort(ctx, result);
|
|
4368
5377
|
}
|
|
4369
5378
|
} catch (err) {
|
|
4370
|
-
this.#
|
|
5379
|
+
this.#failCohort(ctx, err);
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
/**
|
|
5383
|
+
* Handler for receiving fallback (k-of-n script-path) signatures. The state
|
|
5384
|
+
* machine assembles and finalizes the fallback spend once k valid signatures
|
|
5385
|
+
* are in; the result is then emitted and the cohort's completion resolves. The
|
|
5386
|
+
* cohort is already committed to the fallback path (via {@link triggerFallback}).
|
|
5387
|
+
* @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
|
|
5388
|
+
* @returns {Promise<void>} Resolves when handling is complete.
|
|
5389
|
+
*/
|
|
5390
|
+
async #handleFallbackSignature(msg) {
|
|
5391
|
+
if (this.#stopped) return;
|
|
5392
|
+
const ctx = this.#contextFor(msg);
|
|
5393
|
+
if (!ctx) return;
|
|
5394
|
+
try {
|
|
5395
|
+
this.session.receive(msg);
|
|
5396
|
+
this.#drainRejections(ctx);
|
|
5397
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
5398
|
+
const result = this.session.getResult(ctx.cohortId);
|
|
5399
|
+
if (result) {
|
|
5400
|
+
this.#completeCohort(ctx, result);
|
|
5401
|
+
}
|
|
5402
|
+
} catch (err) {
|
|
5403
|
+
this.#failCohort(ctx, err);
|
|
4371
5404
|
}
|
|
4372
5405
|
}
|
|
4373
5406
|
/**
|
|
4374
5407
|
* Internal: helper to send all messages sequentially. Catches and propagates errors.
|
|
4375
5408
|
* @param {BaseMessage[]} msgs - The messages to send.
|
|
4376
5409
|
* @returns {Promise<void>} Resolves when all messages have been sent.
|
|
4377
|
-
* @throws {Error} If sending any message fails, the error is emitted and the run promise is
|
|
4378
|
-
* rejected.
|
|
4379
5410
|
*/
|
|
4380
5411
|
async #sendAll(msgs) {
|
|
4381
5412
|
for (const m of msgs) {
|
|
4382
5413
|
await this.#transport.sendMessage(m, this.#did, m.to);
|
|
4383
5414
|
}
|
|
4384
5415
|
}
|
|
4385
|
-
/**
|
|
4386
|
-
* Internal: helper to handle errors. Emits an 'error' event and rejects the run promise.
|
|
4387
|
-
* @param {Error} err - The error to handle.
|
|
4388
|
-
*/
|
|
4389
|
-
#fail(err) {
|
|
4390
|
-
this.#stopAdvertRepeating();
|
|
4391
|
-
this.#clearTimers();
|
|
4392
|
-
this.#unregisterHandlers();
|
|
4393
|
-
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
4394
|
-
this.emit("error", err);
|
|
4395
|
-
this.#rejectRun?.(err);
|
|
4396
|
-
}
|
|
4397
5416
|
};
|
|
4398
5417
|
|
|
4399
5418
|
// src/core/aggregation/runner/participant-runner.ts
|
|
@@ -4441,7 +5460,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4441
5460
|
COHORT_READY,
|
|
4442
5461
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
4443
5462
|
AUTHORIZATION_REQUEST,
|
|
4444
|
-
AGGREGATED_NONCE
|
|
5463
|
+
AGGREGATED_NONCE,
|
|
5464
|
+
FALLBACK_AUTHORIZATION_REQUEST
|
|
4445
5465
|
];
|
|
4446
5466
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
4447
5467
|
#unregisterHandlers() {
|
|
@@ -4453,7 +5473,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4453
5473
|
}
|
|
4454
5474
|
/**
|
|
4455
5475
|
* Single-shot helper: start, join the first cohort that passes `shouldJoin`,
|
|
4456
|
-
* drive it to completion, and resolve. Convenient for tests and demos.
|
|
5476
|
+
* drive it to completion, and resolve. Convenient for tests and demos. The
|
|
5477
|
+
* single-cohort special case of {@link joinMatching} (count = 1).
|
|
4457
5478
|
*/
|
|
4458
5479
|
static async joinFirst(options) {
|
|
4459
5480
|
return new Promise((resolve, reject) => {
|
|
@@ -4466,6 +5487,37 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4466
5487
|
runner.start().catch(reject);
|
|
4467
5488
|
});
|
|
4468
5489
|
}
|
|
5490
|
+
/**
|
|
5491
|
+
* Multi-cohort helper: start, join EVERY cohort whose advert passes
|
|
5492
|
+
* `shouldJoin`, drive each to completion in parallel, and resolve once
|
|
5493
|
+
* `count` cohorts have completed (the runner stops at that point). The
|
|
5494
|
+
* N-cohort generalization of {@link joinFirst}, for a participant that joins
|
|
5495
|
+
* several cohorts advertised by one service.
|
|
5496
|
+
*
|
|
5497
|
+
* For an open-ended, long-lived subscriber (no fixed count), construct an
|
|
5498
|
+
* {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
|
|
5499
|
+
* `start()`, and listen for `cohort-complete` - the runner already drives
|
|
5500
|
+
* any number of cohorts concurrently.
|
|
5501
|
+
*
|
|
5502
|
+
* @param options Participant runner options (set `shouldJoin` to select cohorts).
|
|
5503
|
+
* @param count Number of completed cohorts to collect before resolving.
|
|
5504
|
+
* @returns The {@link CohortCompleteInfo} for each completed cohort, in completion order.
|
|
5505
|
+
*/
|
|
5506
|
+
static async joinMatching(options, count) {
|
|
5507
|
+
return new Promise((resolve, reject) => {
|
|
5508
|
+
const runner = new _AggregationParticipantRunner(options);
|
|
5509
|
+
const completed = [];
|
|
5510
|
+
runner.on("cohort-complete", (info) => {
|
|
5511
|
+
completed.push(info);
|
|
5512
|
+
if (completed.length >= count) {
|
|
5513
|
+
runner.stop();
|
|
5514
|
+
resolve(completed);
|
|
5515
|
+
}
|
|
5516
|
+
});
|
|
5517
|
+
runner.on("error", reject);
|
|
5518
|
+
runner.start().catch(reject);
|
|
5519
|
+
});
|
|
5520
|
+
}
|
|
4469
5521
|
/**
|
|
4470
5522
|
* Internal: handler registration with the transport. Idempotent and safe to call multiple times,
|
|
4471
5523
|
* but only registers handlers once.
|
|
@@ -4479,6 +5531,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4479
5531
|
this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
|
|
4480
5532
|
this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
|
|
4481
5533
|
this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
|
|
5534
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
|
|
4482
5535
|
}
|
|
4483
5536
|
/**
|
|
4484
5537
|
* Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
|
|
@@ -4534,8 +5587,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4534
5587
|
cohortId,
|
|
4535
5588
|
beaconAddress: info.beaconAddress
|
|
4536
5589
|
});
|
|
4537
|
-
|
|
4538
|
-
|
|
5590
|
+
if (signedUpdate === null) {
|
|
5591
|
+
await this.#sendAll(this.session.declineUpdate(cohortId));
|
|
5592
|
+
this.emit("update-declined", { cohortId });
|
|
5593
|
+
} else {
|
|
5594
|
+
await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
|
|
5595
|
+
this.emit("update-submitted", { cohortId });
|
|
5596
|
+
}
|
|
4539
5597
|
} catch (err) {
|
|
4540
5598
|
this.emit("error", err);
|
|
4541
5599
|
}
|
|
@@ -4611,23 +5669,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4611
5669
|
const cohortId = msg.body?.cohortId;
|
|
4612
5670
|
if (!cohortId) return;
|
|
4613
5671
|
await this.#sendAll(this.session.generatePartialSignature(cohortId));
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
5672
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
5673
|
+
} catch (err) {
|
|
5674
|
+
this.emit("error", err);
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Internal: handler for fallback authorization requests. The service abandoned
|
|
5679
|
+
* the optimistic key path; the member authorizes the k-of-n script-path spend
|
|
5680
|
+
* of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
|
|
5681
|
+
* the decision, signs the fallback, and completes once it has contributed (the
|
|
5682
|
+
* service needs only k members).
|
|
5683
|
+
* @param {BaseMessage} msg - The received fallback authorization request message.
|
|
5684
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
5685
|
+
*/
|
|
5686
|
+
async #handleFallbackAuthorizationRequest(msg) {
|
|
5687
|
+
if (this.#stopped) return;
|
|
5688
|
+
try {
|
|
5689
|
+
this.session.receive(msg);
|
|
5690
|
+
const cohortId = msg.body?.cohortId;
|
|
5691
|
+
if (!cohortId) return;
|
|
5692
|
+
const req = this.session.pendingFallbackRequests.get(cohortId);
|
|
5693
|
+
if (!req) return;
|
|
5694
|
+
this.emit("fallback-requested", req);
|
|
5695
|
+
const decision = await this.#onApproveSigning(req);
|
|
5696
|
+
if (!decision.approved) {
|
|
5697
|
+
this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
|
|
5698
|
+
return;
|
|
4626
5699
|
}
|
|
5700
|
+
await this.#sendAll(this.session.approveFallback(cohortId));
|
|
5701
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
4627
5702
|
} catch (err) {
|
|
4628
5703
|
this.emit("error", err);
|
|
4629
5704
|
}
|
|
4630
5705
|
}
|
|
5706
|
+
/**
|
|
5707
|
+
* Internal: emit `cohort-complete` with the participant's retained sidecar once
|
|
5708
|
+
* the cohort has reached the Complete phase. Surfaces the CAS Announcement map
|
|
5709
|
+
* (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
|
|
5710
|
+
* DID resolution. Read via getValidation (not pendingValidations, which lists
|
|
5711
|
+
* only the AwaitingValidation phase) so the sidecar is still available at
|
|
5712
|
+
* Complete. Shared by the optimistic and fallback completion paths.
|
|
5713
|
+
*/
|
|
5714
|
+
#emitCohortCompleteIfDone(cohortId) {
|
|
5715
|
+
if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
|
|
5716
|
+
const info = this.session.joinedCohorts.get(cohortId);
|
|
5717
|
+
if (!info) return;
|
|
5718
|
+
const validation = this.session.getValidation(cohortId);
|
|
5719
|
+
this.emit("cohort-complete", {
|
|
5720
|
+
cohortId,
|
|
5721
|
+
beaconAddress: info.beaconAddress,
|
|
5722
|
+
beaconType: validation?.beaconType ?? "",
|
|
5723
|
+
included: validation?.included ?? true,
|
|
5724
|
+
casAnnouncement: validation?.casAnnouncement,
|
|
5725
|
+
smtProof: validation?.smtProof
|
|
5726
|
+
});
|
|
5727
|
+
}
|
|
4631
5728
|
/**
|
|
4632
5729
|
* Internal: send helper to ensure messages are sent sequentially. This is important for protocol
|
|
4633
5730
|
* correctness, as some transports may not guarantee message order if sent in parallel.
|
|
@@ -4643,6 +5740,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
4643
5740
|
};
|
|
4644
5741
|
|
|
4645
5742
|
// src/core/aggregation/runner/aggregation-runner.ts
|
|
5743
|
+
var import_utils9 = require("@noble/hashes/utils");
|
|
4646
5744
|
var AggregationRunner = class {
|
|
4647
5745
|
/**
|
|
4648
5746
|
* Run a cohort of ONE participant entirely in-process and return the
|
|
@@ -4650,8 +5748,8 @@ var AggregationRunner = class {
|
|
|
4650
5748
|
*
|
|
4651
5749
|
* One party plays both the coordinating service and the lone participant,
|
|
4652
5750
|
* connected over an {@link InMemoryTransport} (no relay or HTTP server). This
|
|
4653
|
-
* makes the single-participant aggregate-beacon path
|
|
4654
|
-
* two-axis beacon matrix
|
|
5751
|
+
* makes the single-participant aggregate-beacon path (the N=1 corner of the
|
|
5752
|
+
* two-axis beacon matrix, see ADR 037) first-class, useful for generating
|
|
4655
5753
|
* and reproducing single-participant aggregate test vectors.
|
|
4656
5754
|
*
|
|
4657
5755
|
* The service advertises a cohort with `minParticipants: 1`; the participant
|
|
@@ -4669,11 +5767,13 @@ var AggregationRunner = class {
|
|
|
4669
5767
|
transport.registerPeer(options.participant.did, options.participant.keys.publicKey.compressed);
|
|
4670
5768
|
transport.registerPeer(options.service.did, options.service.keys.publicKey.compressed);
|
|
4671
5769
|
transport.start();
|
|
5770
|
+
const recoveryKey = options.config.recoveryKey ?? (0, import_utils9.bytesToHex)(options.service.keys.publicKey.compressed.slice(1));
|
|
5771
|
+
const recoverySequence = options.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
|
|
4672
5772
|
const service = new AggregationServiceRunner({
|
|
4673
5773
|
transport,
|
|
4674
5774
|
did: options.service.did,
|
|
4675
5775
|
keys: options.service.keys,
|
|
4676
|
-
config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType },
|
|
5776
|
+
config: { minParticipants: 1, network: options.config.network, beaconType: options.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
|
|
4677
5777
|
onProvideTxData: options.onProvideTxData,
|
|
4678
5778
|
cohortTtlMs: options.cohortTtlMs,
|
|
4679
5779
|
phaseTimeoutMs: options.phaseTimeoutMs,
|
|
@@ -4699,8 +5799,8 @@ var AggregationRunner = class {
|
|
|
4699
5799
|
};
|
|
4700
5800
|
|
|
4701
5801
|
// src/core/beacon/beacon.ts
|
|
4702
|
-
var
|
|
4703
|
-
var
|
|
5802
|
+
var import_utils10 = require("@noble/hashes/utils.js");
|
|
5803
|
+
var import_btc_signer7 = require("@scure/btc-signer");
|
|
4704
5804
|
|
|
4705
5805
|
// src/core/beacon/error.ts
|
|
4706
5806
|
var import_common12 = require("@did-btcr2/common");
|
|
@@ -4761,7 +5861,7 @@ var SINGLETON_BEACON_TX_VSIZE = {
|
|
|
4761
5861
|
p2tr: P2TR_BEACON_TX_VSIZE
|
|
4762
5862
|
};
|
|
4763
5863
|
function detectSingletonScriptKind(bitcoinAddress, network) {
|
|
4764
|
-
const decoded = (0,
|
|
5864
|
+
const decoded = (0, import_btc_signer7.Address)(network).decode(bitcoinAddress);
|
|
4765
5865
|
if (decoded.type === "pkh") return "p2pkh";
|
|
4766
5866
|
if (decoded.type === "wpkh") return "p2wpkh";
|
|
4767
5867
|
if (decoded.type === "tr") return "p2tr";
|
|
@@ -4772,12 +5872,12 @@ function detectSingletonScriptKind(bitcoinAddress, network) {
|
|
|
4772
5872
|
);
|
|
4773
5873
|
}
|
|
4774
5874
|
function deriveSingletonAddress(kind, pubkey, network) {
|
|
4775
|
-
if (kind === "p2pkh") return (0,
|
|
4776
|
-
if (kind === "p2wpkh") return (0,
|
|
4777
|
-
return (0,
|
|
5875
|
+
if (kind === "p2pkh") return (0, import_btc_signer7.p2pkh)(pubkey, network).address;
|
|
5876
|
+
if (kind === "p2wpkh") return (0, import_btc_signer7.p2wpkh)(pubkey, network).address;
|
|
5877
|
+
return (0, import_btc_signer7.p2tr)(pubkey.slice(1, 33), void 0, network).address;
|
|
4778
5878
|
}
|
|
4779
5879
|
function opReturnScript(signalBytes) {
|
|
4780
|
-
return
|
|
5880
|
+
return import_btc_signer7.Script.encode(["RETURN", signalBytes]);
|
|
4781
5881
|
}
|
|
4782
5882
|
async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
4783
5883
|
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
@@ -4797,13 +5897,12 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
|
4797
5897
|
);
|
|
4798
5898
|
}
|
|
4799
5899
|
const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
4800
|
-
return { utxo, prevTxBytes: (0,
|
|
5900
|
+
return { utxo, prevTxBytes: (0, import_utils10.hexToBytes)(prevTxHex) };
|
|
4801
5901
|
}
|
|
4802
5902
|
async function buildAggregationBeaconTx(opts) {
|
|
4803
5903
|
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
4804
5904
|
const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
|
|
4805
|
-
const
|
|
4806
|
-
const witnessScript = tapOut.script;
|
|
5905
|
+
const witnessScript = import_btc_signer7.OutScript.encode((0, import_btc_signer7.Address)(opts.network).decode(opts.beaconAddress));
|
|
4807
5906
|
const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
|
|
4808
5907
|
if (BigInt(utxo.value) <= feeSats) {
|
|
4809
5908
|
throw new BeaconError(
|
|
@@ -4812,7 +5911,7 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
4812
5911
|
{ address: opts.beaconAddress, valueSats: utxo.value, feeSats }
|
|
4813
5912
|
);
|
|
4814
5913
|
}
|
|
4815
|
-
const tx = new
|
|
5914
|
+
const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
|
|
4816
5915
|
tx.addInput({
|
|
4817
5916
|
txid: utxo.txid,
|
|
4818
5917
|
index: utxo.vout,
|
|
@@ -4835,16 +5934,16 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
4835
5934
|
async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
|
|
4836
5935
|
const pubkey = signer.publicKey;
|
|
4837
5936
|
if (kind === "p2pkh") {
|
|
4838
|
-
const sighashType =
|
|
5937
|
+
const sighashType = import_btc_signer7.SigHash.ALL;
|
|
4839
5938
|
const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
|
|
4840
5939
|
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
4841
|
-
const sigWithType = (0,
|
|
5940
|
+
const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
4842
5941
|
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
4843
5942
|
tx.finalize();
|
|
4844
5943
|
return tx.hex;
|
|
4845
5944
|
}
|
|
4846
5945
|
if (kind === "p2wpkh") {
|
|
4847
|
-
const decoded =
|
|
5946
|
+
const decoded = import_btc_signer7.OutScript.decode(prevOutScript);
|
|
4848
5947
|
if (decoded.type !== "wpkh") {
|
|
4849
5948
|
throw new BeaconError(
|
|
4850
5949
|
`Expected P2WPKH prev-output script, got "${decoded.type}".`,
|
|
@@ -4852,16 +5951,16 @@ async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amo
|
|
|
4852
5951
|
{ kind, observedScriptType: decoded.type }
|
|
4853
5952
|
);
|
|
4854
5953
|
}
|
|
4855
|
-
const sighashScript =
|
|
4856
|
-
const sighashType =
|
|
5954
|
+
const sighashScript = import_btc_signer7.OutScript.encode({ type: "pkh", hash: decoded.hash });
|
|
5955
|
+
const sighashType = import_btc_signer7.SigHash.ALL;
|
|
4857
5956
|
const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
|
|
4858
5957
|
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
4859
|
-
const sigWithType = (0,
|
|
5958
|
+
const sigWithType = (0, import_utils10.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
4860
5959
|
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
4861
5960
|
tx.finalize();
|
|
4862
5961
|
return tx.hex;
|
|
4863
5962
|
}
|
|
4864
|
-
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript],
|
|
5963
|
+
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer7.SigHash.DEFAULT, [amount]);
|
|
4865
5964
|
const sig = signer.sign(sighash, "bip341");
|
|
4866
5965
|
tx.updateInput(inputIdx, { tapKeySig: sig });
|
|
4867
5966
|
tx.finalize();
|
|
@@ -4882,7 +5981,7 @@ var SinglePartyBeacon = class {
|
|
|
4882
5981
|
*
|
|
4883
5982
|
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
4884
5983
|
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
4885
|
-
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
|
|
5984
|
+
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
|
|
4886
5985
|
* the multi-party path can't share the signing phase, but the tx-construction
|
|
4887
5986
|
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
4888
5987
|
*
|
|
@@ -4914,7 +6013,7 @@ var SinglePartyBeacon = class {
|
|
|
4914
6013
|
*
|
|
4915
6014
|
* Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
|
|
4916
6015
|
* the input accordingly. Validates that the signer's pubkey produces the beacon
|
|
4917
|
-
* address under that script kind
|
|
6016
|
+
* address under that script kind: without this check, a misconfigured caller
|
|
4918
6017
|
* would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
|
|
4919
6018
|
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
|
|
4920
6019
|
* round-trip.
|
|
@@ -4940,17 +6039,17 @@ var SinglePartyBeacon = class {
|
|
|
4940
6039
|
{ address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
|
|
4941
6040
|
);
|
|
4942
6041
|
}
|
|
4943
|
-
const tx = new
|
|
6042
|
+
const tx = new import_btc_signer7.Transaction({ allowUnknownOutputs: true });
|
|
4944
6043
|
let prevOutScript;
|
|
4945
6044
|
if (kind === "p2pkh") {
|
|
4946
|
-
prevOutScript = (0,
|
|
6045
|
+
prevOutScript = (0, import_btc_signer7.p2pkh)(pubkey, network).script;
|
|
4947
6046
|
tx.addInput({
|
|
4948
6047
|
txid: opts.utxo.txid,
|
|
4949
6048
|
index: opts.utxo.vout,
|
|
4950
6049
|
nonWitnessUtxo: opts.prevTxBytes
|
|
4951
6050
|
});
|
|
4952
6051
|
} else if (kind === "p2wpkh") {
|
|
4953
|
-
prevOutScript = (0,
|
|
6052
|
+
prevOutScript = (0, import_btc_signer7.p2wpkh)(pubkey, network).script;
|
|
4954
6053
|
tx.addInput({
|
|
4955
6054
|
txid: opts.utxo.txid,
|
|
4956
6055
|
index: opts.utxo.vout,
|
|
@@ -4959,7 +6058,7 @@ var SinglePartyBeacon = class {
|
|
|
4959
6058
|
});
|
|
4960
6059
|
} else {
|
|
4961
6060
|
const internalKey = pubkey.slice(1, 33);
|
|
4962
|
-
prevOutScript = (0,
|
|
6061
|
+
prevOutScript = (0, import_btc_signer7.p2tr)(internalKey, void 0, network).script;
|
|
4963
6062
|
tx.addInput({
|
|
4964
6063
|
txid: opts.utxo.txid,
|
|
4965
6064
|
index: opts.utxo.vout,
|
|
@@ -5148,7 +6247,7 @@ var SingletonBeacon = class extends SinglePartyBeacon {
|
|
|
5148
6247
|
// src/core/beacon/smt-beacon.ts
|
|
5149
6248
|
var import_common15 = require("@did-btcr2/common");
|
|
5150
6249
|
var import_smt3 = require("@did-btcr2/smt");
|
|
5151
|
-
var
|
|
6250
|
+
var import_utils11 = require("@noble/hashes/utils");
|
|
5152
6251
|
var SMTBeacon = class extends SinglePartyBeacon {
|
|
5153
6252
|
/**
|
|
5154
6253
|
* Creates an instance of SMTBeacon.
|
|
@@ -5237,7 +6336,7 @@ var SMTBeacon = class extends SinglePartyBeacon {
|
|
|
5237
6336
|
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
5238
6337
|
const did = this.service.id.split("#")[0];
|
|
5239
6338
|
const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
|
|
5240
|
-
const nonce = (0,
|
|
6339
|
+
const nonce = (0, import_utils11.randomBytes)(32);
|
|
5241
6340
|
const tree = new import_smt3.BTCR2MerkleTree();
|
|
5242
6341
|
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
5243
6342
|
tree.finalize();
|
|
@@ -5268,13 +6367,13 @@ var BeaconFactory = class {
|
|
|
5268
6367
|
};
|
|
5269
6368
|
|
|
5270
6369
|
// src/core/beacon/signal-discovery.ts
|
|
5271
|
-
var
|
|
6370
|
+
var import_bitcoin5 = require("@did-btcr2/bitcoin");
|
|
5272
6371
|
var import_common18 = require("@did-btcr2/common");
|
|
5273
6372
|
|
|
5274
6373
|
// src/core/beacon/utils.ts
|
|
5275
|
-
var
|
|
6374
|
+
var import_bitcoin4 = require("@did-btcr2/bitcoin");
|
|
5276
6375
|
var import_common17 = require("@did-btcr2/common");
|
|
5277
|
-
var
|
|
6376
|
+
var import_btc_signer8 = require("@scure/btc-signer");
|
|
5278
6377
|
|
|
5279
6378
|
// src/utils/appendix.ts
|
|
5280
6379
|
var import_dids = require("@web5/dids");
|
|
@@ -6379,10 +7478,10 @@ var BeaconUtils = class {
|
|
|
6379
7478
|
static createBeaconService(did, addressType, beaconType) {
|
|
6380
7479
|
try {
|
|
6381
7480
|
const components = Identifier.decode(did);
|
|
6382
|
-
const network = (0,
|
|
7481
|
+
const network = (0, import_bitcoin4.getNetwork)(components.network);
|
|
6383
7482
|
const pubkey = components.genesisBytes;
|
|
6384
7483
|
const id = `${did}#initial${addressType.toUpperCase()}`;
|
|
6385
|
-
const address = addressType === "p2tr" ? (0,
|
|
7484
|
+
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;
|
|
6386
7485
|
const serviceEndpoint = `bitcoin:${address}`;
|
|
6387
7486
|
return { id, type: beaconType, serviceEndpoint };
|
|
6388
7487
|
} catch (error) {
|
|
@@ -6401,9 +7500,9 @@ var BeaconUtils = class {
|
|
|
6401
7500
|
*/
|
|
6402
7501
|
static generateBeaconServices({ id, publicKey, network, beaconType }) {
|
|
6403
7502
|
try {
|
|
6404
|
-
const p2pkhAddr = (0,
|
|
6405
|
-
const p2wpkhAddr = (0,
|
|
6406
|
-
const p2trAddr = (0,
|
|
7503
|
+
const p2pkhAddr = (0, import_btc_signer8.p2pkh)(publicKey, network).address;
|
|
7504
|
+
const p2wpkhAddr = (0, import_btc_signer8.p2wpkh)(publicKey, network).address;
|
|
7505
|
+
const p2trAddr = (0, import_btc_signer8.p2tr)(publicKey.slice(1, 33), void 0, network).address;
|
|
6407
7506
|
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
6408
7507
|
throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
|
|
6409
7508
|
}
|
|
@@ -6530,14 +7629,14 @@ var BeaconSignalDiscovery = class {
|
|
|
6530
7629
|
console.info(`Searching for beacon signals, please wait ...`);
|
|
6531
7630
|
while (block.height <= targetHeight) {
|
|
6532
7631
|
for (const tx of block.tx) {
|
|
6533
|
-
if (tx.txid ===
|
|
7632
|
+
if (tx.txid === import_bitcoin5.GENESIS_TX_ID) {
|
|
6534
7633
|
continue;
|
|
6535
7634
|
}
|
|
6536
7635
|
for (const vin of tx.vin) {
|
|
6537
7636
|
if (vin.coinbase) {
|
|
6538
7637
|
continue;
|
|
6539
7638
|
}
|
|
6540
|
-
if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] ===
|
|
7639
|
+
if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === import_bitcoin5.TXIN_WITNESS_COINBASE) {
|
|
6541
7640
|
continue;
|
|
6542
7641
|
}
|
|
6543
7642
|
if (!vin.txid) {
|
|
@@ -6591,7 +7690,7 @@ var BeaconSignalDiscovery = class {
|
|
|
6591
7690
|
};
|
|
6592
7691
|
|
|
6593
7692
|
// src/core/resolver.ts
|
|
6594
|
-
var
|
|
7693
|
+
var import_bitcoin7 = require("@did-btcr2/bitcoin");
|
|
6595
7694
|
var import_common22 = require("@did-btcr2/common");
|
|
6596
7695
|
var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
|
|
6597
7696
|
var import_keypair8 = require("@did-btcr2/keypair");
|
|
@@ -6605,11 +7704,11 @@ var import_common20 = require("@did-btcr2/common");
|
|
|
6605
7704
|
var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
|
|
6606
7705
|
|
|
6607
7706
|
// src/utils/did-document.ts
|
|
6608
|
-
var
|
|
7707
|
+
var import_bitcoin6 = require("@did-btcr2/bitcoin");
|
|
6609
7708
|
var import_common19 = require("@did-btcr2/common");
|
|
6610
7709
|
var import_keypair7 = require("@did-btcr2/keypair");
|
|
6611
|
-
var
|
|
6612
|
-
var
|
|
7710
|
+
var import_utils13 = require("@web5/dids/utils");
|
|
7711
|
+
var import_btc_signer9 = require("@scure/btc-signer");
|
|
6613
7712
|
var BTCR2_DID_DOCUMENT_CONTEXT = [
|
|
6614
7713
|
"https://www.w3.org/ns/did/v1.1",
|
|
6615
7714
|
"https://btcr2.dev/context/v1"
|
|
@@ -6818,7 +7917,7 @@ var DidDocument = class _DidDocument {
|
|
|
6818
7917
|
* @returns {boolean} True if the services are valid.
|
|
6819
7918
|
*/
|
|
6820
7919
|
static isValidServices(service) {
|
|
6821
|
-
return Array.isArray(service) && service.every(
|
|
7920
|
+
return Array.isArray(service) && service.every(import_utils13.isDidService);
|
|
6822
7921
|
}
|
|
6823
7922
|
/**
|
|
6824
7923
|
* Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
|
|
@@ -6931,7 +8030,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
6931
8030
|
static fromPublicKey(publicKey, network) {
|
|
6932
8031
|
const pk = new import_keypair7.CompressedSecp256k1PublicKey(publicKey);
|
|
6933
8032
|
const id = ID_PLACEHOLDER_VALUE;
|
|
6934
|
-
const address = (0,
|
|
8033
|
+
const address = (0, import_btc_signer9.p2pkh)(pk.compressed, (0, import_bitcoin6.getNetwork)(network)).address;
|
|
6935
8034
|
const services = [{
|
|
6936
8035
|
id: `${id}#service-0`,
|
|
6937
8036
|
serviceEndpoint: `bitcoin:${address}`,
|
|
@@ -6980,7 +8079,7 @@ var Updater = class _Updater {
|
|
|
6980
8079
|
#verificationMethod;
|
|
6981
8080
|
#beaconService;
|
|
6982
8081
|
/**
|
|
6983
|
-
* @internal
|
|
8082
|
+
* @internal Use {@link DidBtcr2.update} to create instances.
|
|
6984
8083
|
*/
|
|
6985
8084
|
constructor(params) {
|
|
6986
8085
|
this.#sourceDocument = params.sourceDocument;
|
|
@@ -7017,7 +8116,7 @@ var Updater = class _Updater {
|
|
|
7017
8116
|
const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
|
|
7018
8117
|
if (targetDocument.id !== sourceDocument.id) {
|
|
7019
8118
|
throw new import_common20.UpdateError(
|
|
7020
|
-
`Patches must not change the DID document id (source "${sourceDocument.id}"
|
|
8119
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
|
|
7021
8120
|
import_common20.INVALID_DID_UPDATE,
|
|
7022
8121
|
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
7023
8122
|
);
|
|
@@ -7101,8 +8200,8 @@ var Updater = class _Updater {
|
|
|
7101
8200
|
}
|
|
7102
8201
|
/**
|
|
7103
8202
|
* Advance the state machine. Returns either:
|
|
7104
|
-
* - `{ status: 'action-required', needs }`
|
|
7105
|
-
* - `{ status: 'complete', result }`
|
|
8203
|
+
* - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
|
|
8204
|
+
* - `{ status: 'complete', result }` update is signed and broadcast
|
|
7106
8205
|
*/
|
|
7107
8206
|
advance() {
|
|
7108
8207
|
while (true) {
|
|
@@ -7115,7 +8214,7 @@ var Updater = class _Updater {
|
|
|
7115
8214
|
continue;
|
|
7116
8215
|
}
|
|
7117
8216
|
// Phase: Sign
|
|
7118
|
-
// Emit NeedSigningKey
|
|
8217
|
+
// Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
|
|
7119
8218
|
case "Sign": {
|
|
7120
8219
|
return {
|
|
7121
8220
|
status: "action-required",
|
|
@@ -7298,8 +8397,8 @@ var DidBtcr2 = class {
|
|
|
7298
8397
|
*
|
|
7299
8398
|
* Factory method that validates the update parameters and returns a sans-I/O
|
|
7300
8399
|
* {@link Updater} state machine. The caller drives the updater through its
|
|
7301
|
-
* phases (Construct
|
|
7302
|
-
* `provide()`. The method package performs **zero I/O
|
|
8400
|
+
* phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
|
|
8401
|
+
* `provide()`. The method package performs **zero I/O**: signing key retrieval
|
|
7303
8402
|
* (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
|
|
7304
8403
|
*
|
|
7305
8404
|
* For a fully-wired version with Bitcoin broadcast and key handling, see
|
|
@@ -7398,7 +8497,7 @@ var DidBtcr2 = class {
|
|
|
7398
8497
|
};
|
|
7399
8498
|
|
|
7400
8499
|
// src/core/resolver.ts
|
|
7401
|
-
var
|
|
8500
|
+
var import_utils15 = require("@noble/curves/utils.js");
|
|
7402
8501
|
var Resolver = class _Resolver {
|
|
7403
8502
|
// --- Immutable inputs ---
|
|
7404
8503
|
#didComponents;
|
|
@@ -7417,7 +8516,7 @@ var Resolver = class _Resolver {
|
|
|
7417
8516
|
#unsortedUpdates = [];
|
|
7418
8517
|
#resolvedResponse = null;
|
|
7419
8518
|
/**
|
|
7420
|
-
* @internal
|
|
8519
|
+
* @internal Use {@link DidBtcr2.resolve} to create instances.
|
|
7421
8520
|
*/
|
|
7422
8521
|
constructor(didComponents, sidecarData, currentDocument, options) {
|
|
7423
8522
|
this.#didComponents = didComponents;
|
|
@@ -7442,7 +8541,7 @@ var Resolver = class _Resolver {
|
|
|
7442
8541
|
const service = BeaconUtils.generateBeaconServices({
|
|
7443
8542
|
id: did,
|
|
7444
8543
|
publicKey: genesisBytes,
|
|
7445
|
-
network: (0,
|
|
8544
|
+
network: (0, import_bitcoin7.getNetwork)(didComponents.network),
|
|
7446
8545
|
beaconType: "SingletonBeacon"
|
|
7447
8546
|
});
|
|
7448
8547
|
return new DidDocument({
|
|
@@ -7465,7 +8564,7 @@ var Resolver = class _Resolver {
|
|
|
7465
8564
|
*/
|
|
7466
8565
|
static external(didComponents, genesisDocument) {
|
|
7467
8566
|
const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
|
|
7468
|
-
if (!(0,
|
|
8567
|
+
if (!(0, import_utils15.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
7469
8568
|
throw new import_common22.ResolveError(
|
|
7470
8569
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
7471
8570
|
import_common22.INVALID_DID_DOCUMENT,
|
|
@@ -7542,7 +8641,7 @@ var Resolver = class _Resolver {
|
|
|
7542
8641
|
this.confirmDuplicate(update, updateHashHistory);
|
|
7543
8642
|
} else if (update.targetVersionId === currentVersionId + 1) {
|
|
7544
8643
|
const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
|
|
7545
|
-
if (!(0,
|
|
8644
|
+
if (!(0, import_utils15.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
7546
8645
|
throw new import_common22.ResolveError(
|
|
7547
8646
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
7548
8647
|
import_common22.INVALID_DID_UPDATE,
|
|
@@ -7589,7 +8688,7 @@ var Resolver = class _Resolver {
|
|
|
7589
8688
|
const { proof: _, ...unsignedUpdate } = update;
|
|
7590
8689
|
const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
|
|
7591
8690
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
7592
|
-
if (!(0,
|
|
8691
|
+
if (!(0, import_utils15.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
|
|
7593
8692
|
throw new import_common22.ResolveError(
|
|
7594
8693
|
`Invalid duplicate: unsigned update hash does not match historical hash`,
|
|
7595
8694
|
import_common22.LATE_PUBLISHING_ERROR,
|
|
@@ -7642,7 +8741,7 @@ var Resolver = class _Resolver {
|
|
|
7642
8741
|
DidDocument.validate(updatedDocument);
|
|
7643
8742
|
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
|
|
7644
8743
|
const updateTargetHash = (0, import_common22.decode)(update.targetHash);
|
|
7645
|
-
if (!(0,
|
|
8744
|
+
if (!(0, import_utils15.equalBytes)(updateTargetHash, currentDocumentHash)) {
|
|
7646
8745
|
throw new import_common22.ResolveError(
|
|
7647
8746
|
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
7648
8747
|
import_common22.INVALID_DID_UPDATE,
|
|
@@ -7653,8 +8752,8 @@ var Resolver = class _Resolver {
|
|
|
7653
8752
|
}
|
|
7654
8753
|
/**
|
|
7655
8754
|
* Advance the state machine. Returns either:
|
|
7656
|
-
* - `{ status: 'action-required', needs }`
|
|
7657
|
-
* - `{ status: 'resolved', result }`
|
|
8755
|
+
* - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
|
|
8756
|
+
* - `{ status: 'resolved', result }` - resolution complete
|
|
7658
8757
|
*
|
|
7659
8758
|
* Analogous to Rust's `Resolver::resolve()`.
|
|
7660
8759
|
*/
|
|
@@ -7892,9 +8991,11 @@ var DidDocumentBuilder = class {
|
|
|
7892
8991
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
7893
8992
|
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
7894
8993
|
DEFAULT_CLOCK_SKEW_SEC,
|
|
8994
|
+
DEFAULT_FUNDING_MODEL,
|
|
7895
8995
|
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
7896
8996
|
DEFAULT_NONCE_LEN_BYTES,
|
|
7897
8997
|
DEFAULT_NOSTR_RELAYS,
|
|
8998
|
+
DEFAULT_RECOVERY_SEQUENCE,
|
|
7898
8999
|
DID_REGEX,
|
|
7899
9000
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
7900
9001
|
DidBtcr2,
|
|
@@ -7903,6 +9004,8 @@ var DidDocumentBuilder = class {
|
|
|
7903
9004
|
DidDocumentBuilder,
|
|
7904
9005
|
DidVerificationMethod,
|
|
7905
9006
|
Document,
|
|
9007
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
9008
|
+
FALLBACK_SIGNATURE,
|
|
7906
9009
|
GenesisDocument,
|
|
7907
9010
|
HTTP_ENVELOPE_VERSION,
|
|
7908
9011
|
HTTP_ROUTE,
|
|
@@ -7916,7 +9019,9 @@ var DidDocumentBuilder = class {
|
|
|
7916
9019
|
InMemoryTransport,
|
|
7917
9020
|
InboxBuffer,
|
|
7918
9021
|
KNOWN_BEACON_TYPES,
|
|
9022
|
+
KNOWN_FUNDING_MODELS,
|
|
7919
9023
|
KeyPairAggregationSigner,
|
|
9024
|
+
MAX_RECOVERY_SEQUENCE,
|
|
7920
9025
|
NONCE_CONTRIBUTION,
|
|
7921
9026
|
NonceCache,
|
|
7922
9027
|
NostrTransport,
|
|
@@ -7933,6 +9038,7 @@ var DidDocumentBuilder = class {
|
|
|
7933
9038
|
SMTBeacon,
|
|
7934
9039
|
SMTBeaconError,
|
|
7935
9040
|
SSE_EVENT,
|
|
9041
|
+
SUBMIT_NONINCLUDED,
|
|
7936
9042
|
SUBMIT_UPDATE,
|
|
7937
9043
|
ServiceCohortPhase,
|
|
7938
9044
|
SigningSessionError,
|
|
@@ -7941,6 +9047,7 @@ var DidDocumentBuilder = class {
|
|
|
7941
9047
|
SingletonBeacon,
|
|
7942
9048
|
SingletonBeaconError,
|
|
7943
9049
|
StaticFeeEstimator,
|
|
9050
|
+
TAPROOT_LEAF_VERSION,
|
|
7944
9051
|
TransportAdapterError,
|
|
7945
9052
|
TransportError,
|
|
7946
9053
|
TransportFactory,
|
|
@@ -7948,6 +9055,11 @@ var DidDocumentBuilder = class {
|
|
|
7948
9055
|
Updater,
|
|
7949
9056
|
VALIDATION_ACK,
|
|
7950
9057
|
buildAggregationBeaconTx,
|
|
9058
|
+
buildFallbackLeaf,
|
|
9059
|
+
buildFallbackSpend,
|
|
9060
|
+
buildRecoveryLeaves,
|
|
9061
|
+
buildRecoveryScript,
|
|
9062
|
+
buildRecoverySpend,
|
|
7951
9063
|
buildRequestAuth,
|
|
7952
9064
|
createAggregatedNonceMessage,
|
|
7953
9065
|
createAuthorizationRequestMessage,
|
|
@@ -7956,13 +9068,17 @@ var DidDocumentBuilder = class {
|
|
|
7956
9068
|
createCohortOptInMessage,
|
|
7957
9069
|
createCohortReadyMessage,
|
|
7958
9070
|
createDistributeAggregatedDataMessage,
|
|
9071
|
+
createFallbackAuthorizationRequestMessage,
|
|
9072
|
+
createFallbackSignatureMessage,
|
|
7959
9073
|
createNonceContributionMessage,
|
|
7960
9074
|
createSignatureAuthorizationMessage,
|
|
9075
|
+
createSubmitNonIncludedMessage,
|
|
7961
9076
|
createSubmitUpdateMessage,
|
|
7962
9077
|
createValidationAckMessage,
|
|
7963
9078
|
defaultReconnectBackoff,
|
|
7964
9079
|
deriveSingletonAddress,
|
|
7965
9080
|
detectSingletonScriptKind,
|
|
9081
|
+
fallbackSighash,
|
|
7966
9082
|
formatSseComment,
|
|
7967
9083
|
formatSseEvent,
|
|
7968
9084
|
getBeaconStrategy,
|
|
@@ -7974,10 +9090,13 @@ var DidDocumentBuilder = class {
|
|
|
7974
9090
|
isCohortOptInMessage,
|
|
7975
9091
|
isCohortReadyMessage,
|
|
7976
9092
|
isDistributeAggregatedDataMessage,
|
|
9093
|
+
isFallbackAuthorizationRequestMessage,
|
|
9094
|
+
isFallbackSignatureMessage,
|
|
7977
9095
|
isKeygenMessageType,
|
|
7978
9096
|
isNonceContributionMessage,
|
|
7979
9097
|
isSignMessageType,
|
|
7980
9098
|
isSignatureAuthorizationMessage,
|
|
9099
|
+
isSubmitNonIncludedMessage,
|
|
7981
9100
|
isSubmitUpdateMessage,
|
|
7982
9101
|
isUpdateMessageType,
|
|
7983
9102
|
isValidationAckMessage,
|
|
@@ -7986,8 +9105,10 @@ var DidDocumentBuilder = class {
|
|
|
7986
9105
|
parseRequestAuth,
|
|
7987
9106
|
parseSseStream,
|
|
7988
9107
|
registerBeaconStrategy,
|
|
9108
|
+
resolveFallbackThreshold,
|
|
7989
9109
|
reviveFromWire,
|
|
7990
9110
|
signEnvelope,
|
|
9111
|
+
tapLeafHash,
|
|
7991
9112
|
validateCohortConditions,
|
|
7992
9113
|
verifyEnvelope,
|
|
7993
9114
|
verifyRequestAuth
|