@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/browser.mjs
CHANGED
|
@@ -100123,7 +100123,7 @@ var SchnorrMultikey = class _SchnorrMultikey {
|
|
|
100123
100123
|
* @readonly
|
|
100124
100124
|
* Get signing ability of the Multikey: true if a local Secp256k1SecretKey
|
|
100125
100125
|
* is present (note: the SchnorrKeyPair.secretKey getter throws when absent;
|
|
100126
|
-
* that throw is the historical error contract
|
|
100126
|
+
* that throw is the historical error contract - see multikey tests) or if
|
|
100127
100127
|
* an external signer is available.
|
|
100128
100128
|
*/
|
|
100129
100129
|
get signer() {
|
|
@@ -100228,6 +100228,7 @@ var SchnorrMultikey = class _SchnorrMultikey {
|
|
|
100228
100228
|
init_shim();
|
|
100229
100229
|
|
|
100230
100230
|
// src/core/aggregation/service.ts
|
|
100231
|
+
init_secp256k1();
|
|
100231
100232
|
init_utils();
|
|
100232
100233
|
|
|
100233
100234
|
// src/core/aggregation/beacon-strategy.ts
|
|
@@ -100528,9 +100529,12 @@ var CAS_STRATEGY = {
|
|
|
100528
100529
|
getDistributePayload(cohort) {
|
|
100529
100530
|
return { casAnnouncement: cohort.casAnnouncement };
|
|
100530
100531
|
},
|
|
100531
|
-
validateParticipantView({ participantDid, expectedHash, body }) {
|
|
100532
|
+
validateParticipantView({ participantDid, included, expectedHash, body }) {
|
|
100532
100533
|
const casAnnouncement = body.casAnnouncement;
|
|
100533
100534
|
if (!casAnnouncement) return { matches: false };
|
|
100535
|
+
if (!included) {
|
|
100536
|
+
return { matches: !(participantDid in casAnnouncement), casAnnouncement };
|
|
100537
|
+
}
|
|
100534
100538
|
return {
|
|
100535
100539
|
matches: casAnnouncement[participantDid] === expectedHash,
|
|
100536
100540
|
casAnnouncement
|
|
@@ -100546,15 +100550,20 @@ var SMT_STRATEGY = {
|
|
|
100546
100550
|
const proof = cohort.smtProofs?.get(participantDid);
|
|
100547
100551
|
return { smtProof: proof };
|
|
100548
100552
|
},
|
|
100549
|
-
validateParticipantView({ participantDid, submittedUpdate, body }) {
|
|
100553
|
+
validateParticipantView({ participantDid, included, submittedUpdate, body }) {
|
|
100550
100554
|
const smtProof = body.smtProof;
|
|
100551
|
-
|
|
100555
|
+
const index = didToIndex(participantDid);
|
|
100556
|
+
if (!included) {
|
|
100557
|
+
if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
|
|
100558
|
+
const candidateHash2 = blockHash(blockHash(base64UrlToHash(smtProof.nonce)));
|
|
100559
|
+
return { matches: verifySerializedProof(smtProof, index, candidateHash2), smtProof };
|
|
100560
|
+
}
|
|
100561
|
+
if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
|
|
100552
100562
|
const canonicalBytes = new TextEncoder().encode(canonicalize2(submittedUpdate));
|
|
100553
100563
|
const expectedUpdateId = hashToBase64Url(blockHash(canonicalBytes));
|
|
100554
100564
|
if (smtProof.updateId !== expectedUpdateId) {
|
|
100555
100565
|
return { matches: false, smtProof };
|
|
100556
100566
|
}
|
|
100557
|
-
const index = didToIndex(participantDid);
|
|
100558
100567
|
const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)), base64UrlToHash(smtProof.updateId));
|
|
100559
100568
|
return {
|
|
100560
100569
|
matches: verifySerializedProof(smtProof, index, candidateHash),
|
|
@@ -101553,6 +101562,15 @@ function checkScript(script, redeemScript, witnessScript) {
|
|
|
101553
101562
|
checkWSH(r2, witnessScript);
|
|
101554
101563
|
}
|
|
101555
101564
|
}
|
|
101565
|
+
function uniqPubkey(pubkeys) {
|
|
101566
|
+
const map3 = {};
|
|
101567
|
+
for (const pub of pubkeys) {
|
|
101568
|
+
const key = hex.encode(pub);
|
|
101569
|
+
if (map3[key])
|
|
101570
|
+
throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
101571
|
+
map3[key] = true;
|
|
101572
|
+
}
|
|
101573
|
+
}
|
|
101556
101574
|
var p2pkh = (publicKey2, network = NETWORK) => {
|
|
101557
101575
|
if (!isValidPubkey(publicKey2, PubT.ecdsa))
|
|
101558
101576
|
throw new Error("P2PKH: invalid publicKey");
|
|
@@ -101725,6 +101743,14 @@ function p2tr(internalPubKey, tree2, network = NETWORK, allowUnknownOutputs = fa
|
|
|
101725
101743
|
};
|
|
101726
101744
|
}
|
|
101727
101745
|
}
|
|
101746
|
+
function p2tr_ms(m2, pubkeys, allowSamePubkeys = false) {
|
|
101747
|
+
if (!allowSamePubkeys)
|
|
101748
|
+
uniqPubkey(pubkeys);
|
|
101749
|
+
return {
|
|
101750
|
+
type: "tr_ms",
|
|
101751
|
+
script: OutScript.encode({ type: "tr_ms", pubkeys, m: m2 })
|
|
101752
|
+
};
|
|
101753
|
+
}
|
|
101728
101754
|
var base58check = createBase58check(sha256);
|
|
101729
101755
|
function validateWitness(version3, data) {
|
|
101730
101756
|
if (data.length < 2 || data.length > 40)
|
|
@@ -103200,6 +103226,104 @@ var SigningSessionError = class extends MethodError {
|
|
|
103200
103226
|
}
|
|
103201
103227
|
};
|
|
103202
103228
|
|
|
103229
|
+
// src/core/aggregation/recovery-policy.ts
|
|
103230
|
+
init_shim();
|
|
103231
|
+
init_secp256k1();
|
|
103232
|
+
init_utils();
|
|
103233
|
+
var DEFAULT_FUNDING_MODEL = "operator-funded";
|
|
103234
|
+
var DEFAULT_RECOVERY_SEQUENCE = 144;
|
|
103235
|
+
var MAX_RECOVERY_SEQUENCE = 65535;
|
|
103236
|
+
var TAPROOT_LEAF_VERSION = 192;
|
|
103237
|
+
function compactSize(n2) {
|
|
103238
|
+
if (n2 < 253) return new Uint8Array([n2]);
|
|
103239
|
+
if (n2 <= 65535) return new Uint8Array([253, n2 & 255, n2 >> 8 & 255]);
|
|
103240
|
+
return new Uint8Array([254, n2 & 255, n2 >> 8 & 255, n2 >> 16 & 255, n2 >> 24 & 255]);
|
|
103241
|
+
}
|
|
103242
|
+
function tapLeafHash2(script, leafVersion = TAPROOT_LEAF_VERSION) {
|
|
103243
|
+
return schnorr2.utils.taggedHash(
|
|
103244
|
+
"TapLeaf",
|
|
103245
|
+
concatBytes(new Uint8Array([leafVersion]), compactSize(script.length), script)
|
|
103246
|
+
);
|
|
103247
|
+
}
|
|
103248
|
+
function resolveFallbackThreshold(advertised, n2) {
|
|
103249
|
+
return advertised ?? Math.max(1, n2 - 1);
|
|
103250
|
+
}
|
|
103251
|
+
function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
|
|
103252
|
+
const n2 = cohortKeys.length;
|
|
103253
|
+
if (n2 === 0) {
|
|
103254
|
+
throw new AggregationCohortError(
|
|
103255
|
+
"Cannot build fallback leaf: no cohort keys.",
|
|
103256
|
+
"NO_COHORT_KEYS"
|
|
103257
|
+
);
|
|
103258
|
+
}
|
|
103259
|
+
for (const key of cohortKeys) {
|
|
103260
|
+
if (key.length !== 33) {
|
|
103261
|
+
throw new AggregationCohortError(
|
|
103262
|
+
`Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
|
|
103263
|
+
"INVALID_COHORT_KEY",
|
|
103264
|
+
{ length: key.length }
|
|
103265
|
+
);
|
|
103266
|
+
}
|
|
103267
|
+
}
|
|
103268
|
+
if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n2) {
|
|
103269
|
+
throw new AggregationCohortError(
|
|
103270
|
+
`Fallback threshold must be an integer in [1, ${n2}] (k-of-n), got ${fallbackThreshold}.`,
|
|
103271
|
+
"INVALID_FALLBACK_THRESHOLD",
|
|
103272
|
+
{ fallbackThreshold, n: n2 }
|
|
103273
|
+
);
|
|
103274
|
+
}
|
|
103275
|
+
}
|
|
103276
|
+
function buildFallbackLeaf(params) {
|
|
103277
|
+
assertFallbackParams(params);
|
|
103278
|
+
const xOnlyKeys = sortKeys(params.cohortKeys).map((k) => k.slice(1));
|
|
103279
|
+
return p2tr_ms(params.fallbackThreshold, xOnlyKeys).script;
|
|
103280
|
+
}
|
|
103281
|
+
function assertRecoveryParams({ recoveryKey, recoverySequence }) {
|
|
103282
|
+
if (recoveryKey.length !== 32) {
|
|
103283
|
+
throw new AggregationCohortError(
|
|
103284
|
+
`Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
|
|
103285
|
+
"INVALID_RECOVERY_KEY"
|
|
103286
|
+
);
|
|
103287
|
+
}
|
|
103288
|
+
if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
103289
|
+
throw new AggregationCohortError(
|
|
103290
|
+
`Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
|
|
103291
|
+
"INVALID_RECOVERY_SEQUENCE"
|
|
103292
|
+
);
|
|
103293
|
+
}
|
|
103294
|
+
}
|
|
103295
|
+
function buildRecoveryScript(params) {
|
|
103296
|
+
assertRecoveryParams(params);
|
|
103297
|
+
return Script.encode([
|
|
103298
|
+
params.recoverySequence,
|
|
103299
|
+
"CHECKSEQUENCEVERIFY",
|
|
103300
|
+
"DROP",
|
|
103301
|
+
params.recoveryKey,
|
|
103302
|
+
"CHECKSIG"
|
|
103303
|
+
]);
|
|
103304
|
+
}
|
|
103305
|
+
function buildRecoveryLeaves(fundingModel, params) {
|
|
103306
|
+
switch (fundingModel) {
|
|
103307
|
+
case "operator-funded":
|
|
103308
|
+
return [
|
|
103309
|
+
{ script: buildFallbackLeaf(params) },
|
|
103310
|
+
{ script: buildRecoveryScript(params) }
|
|
103311
|
+
];
|
|
103312
|
+
case "participant-funded":
|
|
103313
|
+
throw new AggregationCohortError(
|
|
103314
|
+
"Funding model 'participant-funded' is reserved and not yet implemented.",
|
|
103315
|
+
"UNSUPPORTED_FUNDING_MODEL",
|
|
103316
|
+
{ fundingModel }
|
|
103317
|
+
);
|
|
103318
|
+
default:
|
|
103319
|
+
throw new AggregationCohortError(
|
|
103320
|
+
`Unknown funding model: ${fundingModel}.`,
|
|
103321
|
+
"UNKNOWN_FUNDING_MODEL",
|
|
103322
|
+
{ fundingModel }
|
|
103323
|
+
);
|
|
103324
|
+
}
|
|
103325
|
+
}
|
|
103326
|
+
|
|
103203
103327
|
// src/core/aggregation/cohort.ts
|
|
103204
103328
|
var AggregationCohort = class {
|
|
103205
103329
|
/** Unique identifier for the cohort. */
|
|
@@ -103215,8 +103339,8 @@ var AggregationCohort = class {
|
|
|
103215
103339
|
/** List of participant DIDs that have been accepted into the cohort. */
|
|
103216
103340
|
participants = [];
|
|
103217
103341
|
/**
|
|
103218
|
-
* Mapping from participant DID
|
|
103219
|
-
* Distinct from {@link cohortKeys} (which is sorted per BIP-327)
|
|
103342
|
+
* Mapping from participant DID to their compressed secp256k1 public key.
|
|
103343
|
+
* Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
|
|
103220
103344
|
* callers look up a participant's key without knowing their position in the
|
|
103221
103345
|
* sorted array. Populated by the service at `acceptParticipant` time.
|
|
103222
103346
|
*/
|
|
@@ -103224,16 +103348,41 @@ var AggregationCohort = class {
|
|
|
103224
103348
|
/** Sorted list of cohort participants' compressed public keys. */
|
|
103225
103349
|
#cohortKeys = [];
|
|
103226
103350
|
/**
|
|
103227
|
-
* BIP-341 TapTweak
|
|
103228
|
-
*
|
|
103229
|
-
*
|
|
103351
|
+
* BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
|
|
103352
|
+
* The beacon output is an internal key (the MuSig2 aggregate) plus a script
|
|
103353
|
+
* tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
|
|
103354
|
+
* The MuSig2 signing session applies this as an x-only tweak; a value that does
|
|
103355
|
+
* not match the root the funded address was derived from silently yields an
|
|
103356
|
+
* invalid key-path signature.
|
|
103230
103357
|
*/
|
|
103231
103358
|
tapTweak = new Uint8Array();
|
|
103232
|
-
/** The n-of-n MuSig2
|
|
103359
|
+
/** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
|
|
103360
|
+
internalKey = new Uint8Array();
|
|
103361
|
+
/** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
|
|
103362
|
+
tapMerkleRoot = new Uint8Array();
|
|
103363
|
+
/** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
|
|
103364
|
+
recoveryKey;
|
|
103365
|
+
/** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
|
|
103366
|
+
recoverySequence;
|
|
103367
|
+
/** Funding model governing the recovery leaves (default 'operator-funded'). */
|
|
103368
|
+
fundingModel;
|
|
103369
|
+
/**
|
|
103370
|
+
* Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
|
|
103371
|
+
* address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
|
|
103372
|
+
*/
|
|
103373
|
+
fallbackThreshold;
|
|
103374
|
+
/** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
|
|
103233
103375
|
beaconAddress = "";
|
|
103234
103376
|
/** Pending DID updates submitted by participants, keyed by DID. */
|
|
103235
103377
|
pendingUpdates = /* @__PURE__ */ new Map();
|
|
103236
|
-
/**
|
|
103378
|
+
/**
|
|
103379
|
+
* Participant DIDs that explicitly declined to submit an update this round
|
|
103380
|
+
* (cooperative non-inclusion). A decliner is absent from the CAS Announcement
|
|
103381
|
+
* Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
|
|
103382
|
+
* disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
|
|
103383
|
+
*/
|
|
103384
|
+
nonIncluded = /* @__PURE__ */ new Set();
|
|
103385
|
+
/** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
|
|
103237
103386
|
casAnnouncement;
|
|
103238
103387
|
/** Per-participant SMT proofs, set by buildSMTTree(). */
|
|
103239
103388
|
smtProofs;
|
|
@@ -103243,12 +103392,16 @@ var AggregationCohort = class {
|
|
|
103243
103392
|
validationAcks = /* @__PURE__ */ new Set();
|
|
103244
103393
|
/** Set of participant DIDs that have rejected the aggregated data. */
|
|
103245
103394
|
validationRejections = /* @__PURE__ */ new Set();
|
|
103246
|
-
constructor({ id, minParticipants, serviceDid, network, beaconType }) {
|
|
103395
|
+
constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
|
|
103247
103396
|
this.id = id || crypto.randomUUID();
|
|
103248
103397
|
this.minParticipants = minParticipants ?? 2;
|
|
103249
103398
|
this.serviceDid = serviceDid || "";
|
|
103250
103399
|
this.network = network;
|
|
103251
103400
|
this.beaconType = beaconType || "CASBeacon";
|
|
103401
|
+
this.recoveryKey = recoveryKey;
|
|
103402
|
+
this.recoverySequence = recoverySequence;
|
|
103403
|
+
this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
|
|
103404
|
+
this.fallbackThreshold = fallbackThreshold;
|
|
103252
103405
|
}
|
|
103253
103406
|
/** Sorted cohort keys (sorted on assignment per BIP-327). */
|
|
103254
103407
|
get cohortKeys() {
|
|
@@ -103258,8 +103411,26 @@ var AggregationCohort = class {
|
|
|
103258
103411
|
this.#cohortKeys = sortKeys(keys);
|
|
103259
103412
|
}
|
|
103260
103413
|
/**
|
|
103261
|
-
*
|
|
103262
|
-
*
|
|
103414
|
+
* The resolved k of the k-of-n fallback leaf: the advertised
|
|
103415
|
+
* {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
|
|
103416
|
+
* the current cohort size. This is the value the beacon address commits to and
|
|
103417
|
+
* the spend builders must reproduce. Returns 0 before any cohort keys are set.
|
|
103418
|
+
*/
|
|
103419
|
+
get effectiveFallbackThreshold() {
|
|
103420
|
+
if (this.#cohortKeys.length === 0) return 0;
|
|
103421
|
+
return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
|
|
103422
|
+
}
|
|
103423
|
+
/**
|
|
103424
|
+
* Computes the Taproot beacon address from the cohort keys and recovery params.
|
|
103425
|
+
*
|
|
103426
|
+
* The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
|
|
103427
|
+
* script path is the recovery tree (a CSV recovery leaf so a missing signer
|
|
103428
|
+
* cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
|
|
103429
|
+
* the `tapTweak` the MuSig2 session must apply for the key-path spend.
|
|
103430
|
+
*
|
|
103431
|
+
* The tweak is derived from the Merkle root the address was built with (read
|
|
103432
|
+
* back from the payment), never recomputed by hand, so the MuSig2 key-path
|
|
103433
|
+
* signature is guaranteed to validate against the funded address.
|
|
103263
103434
|
*/
|
|
103264
103435
|
computeBeaconAddress() {
|
|
103265
103436
|
if (this.#cohortKeys.length === 0) {
|
|
@@ -103269,10 +103440,25 @@ var AggregationCohort = class {
|
|
|
103269
103440
|
{ cohortId: this.id }
|
|
103270
103441
|
);
|
|
103271
103442
|
}
|
|
103443
|
+
if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
|
|
103444
|
+
throw new AggregationCohortError(
|
|
103445
|
+
"Cannot compute beacon address: missing recovery key or sequence.",
|
|
103446
|
+
"NO_RECOVERY_PARAMS",
|
|
103447
|
+
{ cohortId: this.id }
|
|
103448
|
+
);
|
|
103449
|
+
}
|
|
103272
103450
|
const keyAggContext = keyAggregate(this.#cohortKeys);
|
|
103273
103451
|
const aggPubkey = keyAggExport(keyAggContext);
|
|
103274
|
-
const
|
|
103275
|
-
|
|
103452
|
+
const leaves = buildRecoveryLeaves(this.fundingModel, {
|
|
103453
|
+
recoveryKey: this.recoveryKey,
|
|
103454
|
+
recoverySequence: this.recoverySequence,
|
|
103455
|
+
cohortKeys: this.#cohortKeys,
|
|
103456
|
+
fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
|
|
103457
|
+
});
|
|
103458
|
+
const payment = p2tr(aggPubkey, leaves, getNetwork(this.network), true);
|
|
103459
|
+
this.internalKey = aggPubkey;
|
|
103460
|
+
this.tapMerkleRoot = payment.tapMerkleRoot;
|
|
103461
|
+
this.tapTweak = schnorr2.utils.taggedHash("TapTweak", concatBytes(aggPubkey, payment.tapMerkleRoot));
|
|
103276
103462
|
if (!payment.address) {
|
|
103277
103463
|
throw new AggregationCohortError(
|
|
103278
103464
|
"Failed to compute Taproot address",
|
|
@@ -103328,20 +103514,54 @@ var AggregationCohort = class {
|
|
|
103328
103514
|
}
|
|
103329
103515
|
this.pendingUpdates.set(participantDid, signedUpdate);
|
|
103330
103516
|
}
|
|
103517
|
+
/**
|
|
103518
|
+
* Record that a participant declined to submit an update this round
|
|
103519
|
+
* (cooperative non-inclusion). The member stays in the cohort and still signs.
|
|
103520
|
+
*/
|
|
103521
|
+
addNonInclusion(participantDid) {
|
|
103522
|
+
if (!this.participants.includes(participantDid)) {
|
|
103523
|
+
throw new AggregationCohortError(
|
|
103524
|
+
`Participant ${participantDid} is not in cohort ${this.id}.`,
|
|
103525
|
+
"UNKNOWN_PARTICIPANT",
|
|
103526
|
+
{ cohortId: this.id, participantDid }
|
|
103527
|
+
);
|
|
103528
|
+
}
|
|
103529
|
+
if (this.pendingUpdates.has(participantDid)) {
|
|
103530
|
+
throw new AggregationCohortError(
|
|
103531
|
+
`Participant ${participantDid} already submitted an update; cannot also decline.`,
|
|
103532
|
+
"CONFLICTING_RESPONSE",
|
|
103533
|
+
{ cohortId: this.id, participantDid }
|
|
103534
|
+
);
|
|
103535
|
+
}
|
|
103536
|
+
this.nonIncluded.add(participantDid);
|
|
103537
|
+
}
|
|
103331
103538
|
hasAllUpdates() {
|
|
103332
103539
|
return this.pendingUpdates.size === this.participants.length;
|
|
103333
103540
|
}
|
|
103541
|
+
/**
|
|
103542
|
+
* True when every participant has responded for this round, either with an
|
|
103543
|
+
* update or with an explicit non-inclusion. This is the aggregation gate when
|
|
103544
|
+
* non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
|
|
103545
|
+
* {@link hasAllValidationResponses} generalizes a unanimous ack.
|
|
103546
|
+
*/
|
|
103547
|
+
hasAllResponses() {
|
|
103548
|
+
return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
|
|
103549
|
+
}
|
|
103334
103550
|
/**
|
|
103335
103551
|
* Builds a CAS Announcement Map from collected updates.
|
|
103336
|
-
* Maps each participant DID
|
|
103552
|
+
* Maps each participant DID to base64url canonical hash of their signed update.
|
|
103337
103553
|
* Computes signal bytes as SHA-256 of canonicalized announcement.
|
|
103554
|
+
*
|
|
103555
|
+
* Members who declined (cooperative non-inclusion) are naturally absent from
|
|
103556
|
+
* the map: the body iterates {@link pendingUpdates}, which never holds a
|
|
103557
|
+
* decliner. Absence from the map is exactly the CAS non-inclusion signal.
|
|
103338
103558
|
*/
|
|
103339
103559
|
buildCASAnnouncement() {
|
|
103340
|
-
if (!this.
|
|
103560
|
+
if (!this.hasAllResponses()) {
|
|
103341
103561
|
throw new AggregationCohortError(
|
|
103342
|
-
"Cannot build CAS Announcement: not all
|
|
103343
|
-
"
|
|
103344
|
-
{ cohortId: this.id,
|
|
103562
|
+
"Cannot build CAS Announcement: not all participants have responded.",
|
|
103563
|
+
"INCOMPLETE_RESPONSES",
|
|
103564
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
103345
103565
|
);
|
|
103346
103566
|
}
|
|
103347
103567
|
const announcement = {};
|
|
@@ -103353,31 +103573,41 @@ var AggregationCohort = class {
|
|
|
103353
103573
|
return announcement;
|
|
103354
103574
|
}
|
|
103355
103575
|
/**
|
|
103356
|
-
* Builds an SMT tree
|
|
103357
|
-
*
|
|
103358
|
-
*
|
|
103576
|
+
* Builds an SMT tree with one leaf per participant.
|
|
103577
|
+
*
|
|
103578
|
+
* A member who submitted an update gets an inclusion leaf
|
|
103579
|
+
* (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
|
|
103580
|
+
* non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
|
|
103581
|
+
* omitted). The cohort mints each member's nonce and returns it inside that
|
|
103582
|
+
* member's serialized proof, so a decliner can self-validate its own
|
|
103583
|
+
* non-inclusion slot and the resolver can recompute the leaf. Stores
|
|
103584
|
+
* per-participant proofs and the SMT root as signalBytes.
|
|
103359
103585
|
*/
|
|
103360
103586
|
buildSMTTree() {
|
|
103361
|
-
if (!this.
|
|
103587
|
+
if (!this.hasAllResponses()) {
|
|
103362
103588
|
throw new AggregationCohortError(
|
|
103363
|
-
"Cannot build SMT tree: not all
|
|
103364
|
-
"
|
|
103365
|
-
{ cohortId: this.id }
|
|
103589
|
+
"Cannot build SMT tree: not all participants have responded.",
|
|
103590
|
+
"INCOMPLETE_RESPONSES",
|
|
103591
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
103366
103592
|
);
|
|
103367
103593
|
}
|
|
103368
103594
|
const tree2 = new BTCR2MerkleTree();
|
|
103369
103595
|
const entries = [];
|
|
103370
103596
|
const encoder4 = new TextEncoder();
|
|
103371
|
-
for (const
|
|
103372
|
-
const canonicalBytes = encoder4.encode(canonicalize2(signedUpdate));
|
|
103597
|
+
for (const did of this.participants) {
|
|
103373
103598
|
const nonce = randomBytes(32);
|
|
103374
|
-
|
|
103599
|
+
const signedUpdate = this.pendingUpdates.get(did);
|
|
103600
|
+
if (signedUpdate) {
|
|
103601
|
+
entries.push({ did, nonce, signedUpdate: encoder4.encode(canonicalize2(signedUpdate)) });
|
|
103602
|
+
} else {
|
|
103603
|
+
entries.push({ did, nonce });
|
|
103604
|
+
}
|
|
103375
103605
|
}
|
|
103376
103606
|
tree2.addEntries(entries);
|
|
103377
103607
|
tree2.finalize();
|
|
103378
103608
|
this.signalBytes = tree2.rootHash;
|
|
103379
103609
|
this.smtProofs = /* @__PURE__ */ new Map();
|
|
103380
|
-
for (const
|
|
103610
|
+
for (const did of this.participants) {
|
|
103381
103611
|
this.smtProofs.set(did, tree2.proof(did));
|
|
103382
103612
|
}
|
|
103383
103613
|
return this.smtProofs;
|
|
@@ -103403,7 +103633,7 @@ var AggregationCohort = class {
|
|
|
103403
103633
|
return this.validationAcks.size + this.validationRejections.size === this.participants.length;
|
|
103404
103634
|
}
|
|
103405
103635
|
/**
|
|
103406
|
-
* True when all participants approved. Note: differs from {@link hasAllValidationResponses}
|
|
103636
|
+
* True when all participants approved. Note: differs from {@link hasAllValidationResponses},
|
|
103407
103637
|
* this returns false if any participant rejected, even if all responses are in.
|
|
103408
103638
|
*/
|
|
103409
103639
|
isFullyValidated() {
|
|
@@ -103414,6 +103644,7 @@ var AggregationCohort = class {
|
|
|
103414
103644
|
// src/core/aggregation/conditions.ts
|
|
103415
103645
|
init_shim();
|
|
103416
103646
|
var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
|
|
103647
|
+
var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
|
|
103417
103648
|
function checkPair(problems, label, min, max) {
|
|
103418
103649
|
if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
|
|
103419
103650
|
problems.push(`min${label} must be an integer >= 0`);
|
|
@@ -103457,11 +103688,128 @@ function validateCohortConditions(c2) {
|
|
|
103457
103688
|
if (c2.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c2.pendingUpdateTrigger) || c2.pendingUpdateTrigger < 1)) {
|
|
103458
103689
|
problems.push("pendingUpdateTrigger must be an integer >= 1");
|
|
103459
103690
|
}
|
|
103691
|
+
if (c2.fallbackThreshold !== void 0) {
|
|
103692
|
+
if (!Number.isInteger(c2.fallbackThreshold) || c2.fallbackThreshold < 1) {
|
|
103693
|
+
problems.push("fallbackThreshold must be an integer >= 1");
|
|
103694
|
+
} else if (c2.maxParticipants !== void 0 && Number.isInteger(c2.maxParticipants) && c2.fallbackThreshold > c2.maxParticipants) {
|
|
103695
|
+
problems.push("fallbackThreshold must be <= maxParticipants");
|
|
103696
|
+
}
|
|
103697
|
+
}
|
|
103460
103698
|
checkCost(problems, "costOfEnrollment", c2.costOfEnrollment);
|
|
103461
103699
|
checkCost(problems, "costPerAnnouncement", c2.costPerAnnouncement);
|
|
103700
|
+
if (typeof c2.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c2.recoveryKey)) {
|
|
103701
|
+
problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
|
|
103702
|
+
}
|
|
103703
|
+
if (!Number.isInteger(c2.recoverySequence) || c2.recoverySequence < 1 || c2.recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
103704
|
+
problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
|
|
103705
|
+
}
|
|
103706
|
+
if (c2.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c2.fundingModel)) {
|
|
103707
|
+
problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
|
|
103708
|
+
}
|
|
103462
103709
|
return problems;
|
|
103463
103710
|
}
|
|
103464
103711
|
|
|
103712
|
+
// src/core/aggregation/fallback-spend.ts
|
|
103713
|
+
init_shim();
|
|
103714
|
+
init_secp256k1();
|
|
103715
|
+
function bytesEqual(a2, b) {
|
|
103716
|
+
return a2.length === b.length && a2.every((x, i4) => x === b[i4]);
|
|
103717
|
+
}
|
|
103718
|
+
function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
|
|
103719
|
+
return tx.preimageWitnessV1(
|
|
103720
|
+
inputIndex,
|
|
103721
|
+
[prevOutScript],
|
|
103722
|
+
SigHash.DEFAULT,
|
|
103723
|
+
[prevOutValue],
|
|
103724
|
+
void 0,
|
|
103725
|
+
fallbackLeafScript,
|
|
103726
|
+
TAPROOT_LEAF_VERSION
|
|
103727
|
+
);
|
|
103728
|
+
}
|
|
103729
|
+
function buildFallbackSpend(params) {
|
|
103730
|
+
const {
|
|
103731
|
+
pendingTx,
|
|
103732
|
+
cohortKeys,
|
|
103733
|
+
recoveryKey,
|
|
103734
|
+
recoverySequence,
|
|
103735
|
+
fundingModel,
|
|
103736
|
+
network,
|
|
103737
|
+
prevOutScript,
|
|
103738
|
+
prevOutValue,
|
|
103739
|
+
signatures
|
|
103740
|
+
} = params;
|
|
103741
|
+
const inputIndex = params.inputIndex ?? 0;
|
|
103742
|
+
if (cohortKeys.length === 0) {
|
|
103743
|
+
throw new AggregationCohortError(
|
|
103744
|
+
"Cannot build fallback spend: no cohort keys.",
|
|
103745
|
+
"NO_COHORT_KEYS"
|
|
103746
|
+
);
|
|
103747
|
+
}
|
|
103748
|
+
const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
|
|
103749
|
+
const internalKey = keyAggExport(keyAggregate(sortKeys(cohortKeys)));
|
|
103750
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
103751
|
+
recoveryKey,
|
|
103752
|
+
recoverySequence,
|
|
103753
|
+
cohortKeys,
|
|
103754
|
+
fallbackThreshold
|
|
103755
|
+
});
|
|
103756
|
+
const net = getNetwork(network);
|
|
103757
|
+
const payment = p2tr(internalKey, leaves, net, true);
|
|
103758
|
+
if (!bytesEqual(payment.script, prevOutScript)) {
|
|
103759
|
+
throw new AggregationCohortError(
|
|
103760
|
+
"Reconstructed beacon output script does not match the spent prevout script.",
|
|
103761
|
+
"PREVOUT_SCRIPT_MISMATCH"
|
|
103762
|
+
);
|
|
103763
|
+
}
|
|
103764
|
+
const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
|
|
103765
|
+
const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
|
|
103766
|
+
const script = scriptVer.slice(0, scriptVer.length - 1);
|
|
103767
|
+
return bytesEqual(script, fallbackLeaf);
|
|
103768
|
+
});
|
|
103769
|
+
if (!leafEntry) {
|
|
103770
|
+
throw new AggregationCohortError(
|
|
103771
|
+
"Could not locate the fallback leaf in the reconstructed beacon output.",
|
|
103772
|
+
"FALLBACK_LEAF_NOT_FOUND"
|
|
103773
|
+
);
|
|
103774
|
+
}
|
|
103775
|
+
const cohortXOnly = sortKeys(cohortKeys).map((k) => k.slice(1));
|
|
103776
|
+
const tx = pendingTx;
|
|
103777
|
+
const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
|
|
103778
|
+
const leafHash = tapLeafHash2(fallbackLeaf);
|
|
103779
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
103780
|
+
for (const { pubKey, signature } of signatures) {
|
|
103781
|
+
if (pubKey.length !== 32 || signature.length !== 64) continue;
|
|
103782
|
+
const isCohortKey = cohortXOnly.some((k) => bytesEqual(k, pubKey));
|
|
103783
|
+
if (!isCohortKey) continue;
|
|
103784
|
+
const hex2 = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
103785
|
+
if (accepted.has(hex2)) continue;
|
|
103786
|
+
let ok = false;
|
|
103787
|
+
try {
|
|
103788
|
+
ok = schnorr2.verify(signature, sighash, pubKey);
|
|
103789
|
+
} catch {
|
|
103790
|
+
ok = false;
|
|
103791
|
+
}
|
|
103792
|
+
if (ok) accepted.set(hex2, signature);
|
|
103793
|
+
}
|
|
103794
|
+
if (accepted.size < fallbackThreshold) {
|
|
103795
|
+
throw new AggregationCohortError(
|
|
103796
|
+
`Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
|
|
103797
|
+
"NOT_ENOUGH_FALLBACK_SIGNATURES",
|
|
103798
|
+
{ have: accepted.size, need: fallbackThreshold }
|
|
103799
|
+
);
|
|
103800
|
+
}
|
|
103801
|
+
const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
|
|
103802
|
+
tx.updateInput(inputIndex, {
|
|
103803
|
+
tapLeafScript: [leafEntry],
|
|
103804
|
+
tapScriptSig: chosen.map((pubKey) => {
|
|
103805
|
+
const hex2 = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
103806
|
+
return [{ pubKey, leafHash }, accepted.get(hex2)];
|
|
103807
|
+
})
|
|
103808
|
+
});
|
|
103809
|
+
tx.finalize();
|
|
103810
|
+
return tx;
|
|
103811
|
+
}
|
|
103812
|
+
|
|
103465
103813
|
// src/core/aggregation/messages/base.ts
|
|
103466
103814
|
init_shim();
|
|
103467
103815
|
var AGGREGATION_WIRE_VERSION = 1;
|
|
@@ -103501,12 +103849,15 @@ var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
|
103501
103849
|
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
103502
103850
|
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
103503
103851
|
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
103852
|
+
var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
|
|
103504
103853
|
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
103505
103854
|
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
103506
103855
|
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
103507
103856
|
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
103508
103857
|
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
103509
103858
|
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
103859
|
+
var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
|
|
103860
|
+
var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
|
|
103510
103861
|
|
|
103511
103862
|
// src/core/aggregation/messages/factories.ts
|
|
103512
103863
|
init_shim();
|
|
@@ -103530,6 +103881,10 @@ function createSubmitUpdateMessage(fields) {
|
|
|
103530
103881
|
const { from: from8, to, ...body } = fields;
|
|
103531
103882
|
return new BaseMessage({ type: SUBMIT_UPDATE, from: from8, to, body });
|
|
103532
103883
|
}
|
|
103884
|
+
function createSubmitNonIncludedMessage(fields) {
|
|
103885
|
+
const { from: from8, to, ...body } = fields;
|
|
103886
|
+
return new BaseMessage({ type: SUBMIT_NONINCLUDED, from: from8, to, body });
|
|
103887
|
+
}
|
|
103533
103888
|
function createDistributeAggregatedDataMessage(fields) {
|
|
103534
103889
|
const { from: from8, to, ...body } = fields;
|
|
103535
103890
|
return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from: from8, to, body });
|
|
@@ -103554,6 +103909,14 @@ function createSignatureAuthorizationMessage(fields) {
|
|
|
103554
103909
|
const { from: from8, to, ...body } = fields;
|
|
103555
103910
|
return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from: from8, to, body });
|
|
103556
103911
|
}
|
|
103912
|
+
function createFallbackAuthorizationRequestMessage(fields) {
|
|
103913
|
+
const { from: from8, to, ...body } = fields;
|
|
103914
|
+
return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from: from8, to, body });
|
|
103915
|
+
}
|
|
103916
|
+
function createFallbackSignatureMessage(fields) {
|
|
103917
|
+
const { from: from8, to, ...body } = fields;
|
|
103918
|
+
return new BaseMessage({ type: FALLBACK_SIGNATURE, from: from8, to, body });
|
|
103919
|
+
}
|
|
103557
103920
|
|
|
103558
103921
|
// src/core/aggregation/phases.ts
|
|
103559
103922
|
init_shim();
|
|
@@ -103568,6 +103931,7 @@ var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
|
103568
103931
|
ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
|
|
103569
103932
|
ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
|
|
103570
103933
|
ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
|
|
103934
|
+
ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
|
|
103571
103935
|
ServiceCohortPhase2["Complete"] = "Complete";
|
|
103572
103936
|
ServiceCohortPhase2["Failed"] = "Failed";
|
|
103573
103937
|
return ServiceCohortPhase2;
|
|
@@ -103577,11 +103941,13 @@ var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
|
|
|
103577
103941
|
ParticipantCohortPhase2["OptedIn"] = "OptedIn";
|
|
103578
103942
|
ParticipantCohortPhase2["CohortReady"] = "CohortReady";
|
|
103579
103943
|
ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
|
|
103944
|
+
ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
|
|
103580
103945
|
ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
|
|
103581
103946
|
ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
|
|
103582
103947
|
ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
|
|
103583
103948
|
ParticipantCohortPhase2["NonceSent"] = "NonceSent";
|
|
103584
103949
|
ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
|
|
103950
|
+
ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
|
|
103585
103951
|
ParticipantCohortPhase2["Complete"] = "Complete";
|
|
103586
103952
|
ParticipantCohortPhase2["Failed"] = "Failed";
|
|
103587
103953
|
return ParticipantCohortPhase2;
|
|
@@ -103609,11 +103975,11 @@ var BeaconSigningSession = class {
|
|
|
103609
103975
|
prevOutScripts;
|
|
103610
103976
|
/** Previous output values for Taproot sighash computation. */
|
|
103611
103977
|
prevOutValues;
|
|
103612
|
-
/** Map of participant publicKey-hex
|
|
103978
|
+
/** Map of participant publicKey-hex to public nonce contribution. */
|
|
103613
103979
|
nonceContributions = /* @__PURE__ */ new Map();
|
|
103614
103980
|
/** Aggregated MuSig2 nonce (66 bytes). */
|
|
103615
103981
|
aggregatedNonce;
|
|
103616
|
-
/** Map of participant DID
|
|
103982
|
+
/** Map of participant DID to partial signature. */
|
|
103617
103983
|
partialSignatures = /* @__PURE__ */ new Map();
|
|
103618
103984
|
/** Final 64-byte Schnorr signature. */
|
|
103619
103985
|
signature;
|
|
@@ -103796,7 +104162,7 @@ var BeaconSigningSession = class {
|
|
|
103796
104162
|
throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
|
|
103797
104163
|
}
|
|
103798
104164
|
if (!this.#secretNonce) {
|
|
103799
|
-
throw new SigningSessionError("Secret nonce not available
|
|
104165
|
+
throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
|
|
103800
104166
|
}
|
|
103801
104167
|
const session = new Session(
|
|
103802
104168
|
this.aggregatedNonce,
|
|
@@ -103866,6 +104232,9 @@ var AggregationService = class {
|
|
|
103866
104232
|
case SUBMIT_UPDATE:
|
|
103867
104233
|
this.#handleSubmitUpdate(message2);
|
|
103868
104234
|
break;
|
|
104235
|
+
case SUBMIT_NONINCLUDED:
|
|
104236
|
+
this.#handleSubmitNonInclusion(message2);
|
|
104237
|
+
break;
|
|
103869
104238
|
case VALIDATION_ACK:
|
|
103870
104239
|
this.#handleValidationAck(message2);
|
|
103871
104240
|
break;
|
|
@@ -103875,6 +104244,9 @@ var AggregationService = class {
|
|
|
103875
104244
|
case SIGNATURE_AUTHORIZATION:
|
|
103876
104245
|
this.#handleSignatureAuthorization(message2);
|
|
103877
104246
|
break;
|
|
104247
|
+
case FALLBACK_SIGNATURE:
|
|
104248
|
+
this.#handleFallbackSignature(message2);
|
|
104249
|
+
break;
|
|
103878
104250
|
default:
|
|
103879
104251
|
break;
|
|
103880
104252
|
}
|
|
@@ -103893,7 +104265,7 @@ var AggregationService = class {
|
|
|
103893
104265
|
}
|
|
103894
104266
|
/**
|
|
103895
104267
|
* Create a new cohort with the given config. Returns the cohort ID.
|
|
103896
|
-
* Cohort starts in `Created` phase
|
|
104268
|
+
* Cohort starts in `Created` phase - call `advertise()` to broadcast.
|
|
103897
104269
|
*/
|
|
103898
104270
|
createCohort(config) {
|
|
103899
104271
|
const problems = validateCohortConditions(config);
|
|
@@ -103908,7 +104280,11 @@ var AggregationService = class {
|
|
|
103908
104280
|
serviceDid: this.did,
|
|
103909
104281
|
minParticipants: config.minParticipants,
|
|
103910
104282
|
network: config.network,
|
|
103911
|
-
beaconType: config.beaconType
|
|
104283
|
+
beaconType: config.beaconType,
|
|
104284
|
+
recoveryKey: hexToBytes(config.recoveryKey),
|
|
104285
|
+
recoverySequence: config.recoverySequence,
|
|
104286
|
+
fundingModel: config.fundingModel,
|
|
104287
|
+
fallbackThreshold: config.fallbackThreshold
|
|
103912
104288
|
});
|
|
103913
104289
|
this.#cohortStates.set(cohort.id, {
|
|
103914
104290
|
phase: "Created" /* Created */,
|
|
@@ -104109,11 +104485,64 @@ var AggregationService = class {
|
|
|
104109
104485
|
});
|
|
104110
104486
|
return;
|
|
104111
104487
|
}
|
|
104488
|
+
if (state.cohort.nonIncluded.has(message2.from)) {
|
|
104489
|
+
state.rejections.push({
|
|
104490
|
+
from: message2.from,
|
|
104491
|
+
code: "UPDATE_MALFORMED",
|
|
104492
|
+
reason: "Participant already declined this round; cannot also submit an update"
|
|
104493
|
+
});
|
|
104494
|
+
return;
|
|
104495
|
+
}
|
|
104496
|
+
if (state.cohort.pendingUpdates.has(message2.from)) {
|
|
104497
|
+
state.rejections.push({
|
|
104498
|
+
from: message2.from,
|
|
104499
|
+
code: "UPDATE_MALFORMED",
|
|
104500
|
+
reason: "Participant already submitted an update this round; cannot resubmit"
|
|
104501
|
+
});
|
|
104502
|
+
return;
|
|
104503
|
+
}
|
|
104112
104504
|
state.cohort.addUpdate(message2.from, signedUpdate);
|
|
104113
104505
|
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
104114
104506
|
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
104115
104507
|
}
|
|
104116
|
-
if (state.cohort.
|
|
104508
|
+
if (state.cohort.hasAllResponses()) {
|
|
104509
|
+
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
104510
|
+
}
|
|
104511
|
+
}
|
|
104512
|
+
/**
|
|
104513
|
+
* Handle an incoming SUBMIT_NONINCLUDED message: a member declares it has no
|
|
104514
|
+
* update this round (cooperative non-inclusion). Membership is proven by the
|
|
104515
|
+
* signed transport envelope, so the body carries only the cohortId. The member
|
|
104516
|
+
* stays in the cohort and still signs; it is absent from the CAS map and
|
|
104517
|
+
* carries a non-inclusion leaf in the SMT.
|
|
104518
|
+
*/
|
|
104519
|
+
#handleSubmitNonInclusion(message2) {
|
|
104520
|
+
const cohortId = message2.body?.cohortId;
|
|
104521
|
+
if (!cohortId) return;
|
|
104522
|
+
const state = this.#cohortStates.get(cohortId);
|
|
104523
|
+
if (!state) return;
|
|
104524
|
+
if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
|
|
104525
|
+
if (!state.cohort.participants.includes(message2.from)) {
|
|
104526
|
+
state.rejections.push({
|
|
104527
|
+
from: message2.from,
|
|
104528
|
+
code: "UPDATE_MALFORMED",
|
|
104529
|
+
reason: "Sender is not a member of this cohort"
|
|
104530
|
+
});
|
|
104531
|
+
return;
|
|
104532
|
+
}
|
|
104533
|
+
if (state.cohort.pendingUpdates.has(message2.from) || state.cohort.nonIncluded.has(message2.from)) {
|
|
104534
|
+
state.rejections.push({
|
|
104535
|
+
from: message2.from,
|
|
104536
|
+
code: "UPDATE_MALFORMED",
|
|
104537
|
+
reason: "Participant already responded this round"
|
|
104538
|
+
});
|
|
104539
|
+
return;
|
|
104540
|
+
}
|
|
104541
|
+
state.cohort.addNonInclusion(message2.from);
|
|
104542
|
+
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
104543
|
+
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
104544
|
+
}
|
|
104545
|
+
if (state.cohort.hasAllResponses()) {
|
|
104117
104546
|
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
104118
104547
|
}
|
|
104119
104548
|
}
|
|
@@ -104223,7 +104652,7 @@ var AggregationService = class {
|
|
|
104223
104652
|
}
|
|
104224
104653
|
/**
|
|
104225
104654
|
* Start a signing session by creating auth requests for all participants.
|
|
104226
|
-
* The caller provides the transaction data
|
|
104655
|
+
* The caller provides the transaction data - typically built via
|
|
104227
104656
|
* `buildBeaconTransaction()` against a Bitcoin connection.
|
|
104228
104657
|
*/
|
|
104229
104658
|
startSigning(cohortId, txData) {
|
|
@@ -104330,11 +104759,144 @@ var AggregationService = class {
|
|
|
104330
104759
|
state.result = {
|
|
104331
104760
|
cohortId,
|
|
104332
104761
|
signature,
|
|
104333
|
-
signedTx: state.signingSession.pendingTx
|
|
104762
|
+
signedTx: state.signingSession.pendingTx,
|
|
104763
|
+
path: "key-path"
|
|
104334
104764
|
};
|
|
104335
104765
|
state.phase = "Complete" /* Complete */;
|
|
104336
104766
|
}
|
|
104337
104767
|
}
|
|
104768
|
+
/**
|
|
104769
|
+
* Abandon the optimistic n-of-n key path and ask members to authorize the
|
|
104770
|
+
* k-of-n fallback (script-path) spend of the SAME beacon transaction (graceful
|
|
104771
|
+
* liveness, ADR 042). Reuses the in-flight signing session's transaction and
|
|
104772
|
+
* spent output, so the announcement and its outputs are unchanged: only the
|
|
104773
|
+
* witness path differs. Returns one FALLBACK_AUTHORIZATION_REQUEST per
|
|
104774
|
+
* participant.
|
|
104775
|
+
*
|
|
104776
|
+
* Callable once optimistic signing has started (the session and its tx exist)
|
|
104777
|
+
* and before it completes. A cohort can take exactly one of the two paths: the
|
|
104778
|
+
* caller (runner) must commit to fallback and stop driving the optimistic path.
|
|
104779
|
+
*/
|
|
104780
|
+
startFallbackSigning(cohortId) {
|
|
104781
|
+
const state = this.#cohortStates.get(cohortId);
|
|
104782
|
+
if (!state) {
|
|
104783
|
+
throw new AggregationServiceError(`Cohort ${cohortId} not found.`, "COHORT_NOT_FOUND", { cohortId });
|
|
104784
|
+
}
|
|
104785
|
+
if (!state.signingSession) {
|
|
104786
|
+
throw new AggregationServiceError(
|
|
104787
|
+
`Cannot start fallback for cohort ${cohortId}: no signing session.`,
|
|
104788
|
+
"NO_SIGNING_SESSION",
|
|
104789
|
+
{ cohortId }
|
|
104790
|
+
);
|
|
104791
|
+
}
|
|
104792
|
+
const signingPhases = [
|
|
104793
|
+
"SigningStarted" /* SigningStarted */,
|
|
104794
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
104795
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
104796
|
+
];
|
|
104797
|
+
if (!signingPhases.includes(state.phase)) {
|
|
104798
|
+
throw new AggregationServiceError(
|
|
104799
|
+
`Cannot start fallback for cohort ${cohortId}: phase is ${state.phase}.`,
|
|
104800
|
+
"INVALID_PHASE",
|
|
104801
|
+
{ cohortId, phase: state.phase }
|
|
104802
|
+
);
|
|
104803
|
+
}
|
|
104804
|
+
const session = state.signingSession;
|
|
104805
|
+
const prevOutScript = session.prevOutScripts[0];
|
|
104806
|
+
const prevOutValue = session.prevOutValues[0];
|
|
104807
|
+
if (!prevOutScript || prevOutValue === void 0) {
|
|
104808
|
+
throw new AggregationServiceError(
|
|
104809
|
+
`Cannot start fallback for cohort ${cohortId}: signing session missing prevout data.`,
|
|
104810
|
+
"MISSING_PREV_OUT",
|
|
104811
|
+
{ cohortId }
|
|
104812
|
+
);
|
|
104813
|
+
}
|
|
104814
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
104815
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
104816
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
104817
|
+
});
|
|
104818
|
+
state.fallbackSignatures = /* @__PURE__ */ new Map();
|
|
104819
|
+
state.phase = "FallbackRequested" /* FallbackRequested */;
|
|
104820
|
+
const messages2 = [];
|
|
104821
|
+
for (const participantDid of state.cohort.participants) {
|
|
104822
|
+
messages2.push(createFallbackAuthorizationRequestMessage({
|
|
104823
|
+
from: this.did,
|
|
104824
|
+
to: participantDid,
|
|
104825
|
+
cohortId,
|
|
104826
|
+
sessionId: session.id,
|
|
104827
|
+
pendingTx: session.pendingTx.hex,
|
|
104828
|
+
prevOutScriptHex: bytesToHex(prevOutScript),
|
|
104829
|
+
prevOutValue: prevOutValue.toString(),
|
|
104830
|
+
fallbackLeafScriptHex: bytesToHex(fallbackLeaf)
|
|
104831
|
+
}));
|
|
104832
|
+
}
|
|
104833
|
+
return messages2;
|
|
104834
|
+
}
|
|
104835
|
+
/**
|
|
104836
|
+
* Handle an incoming FALLBACK_SIGNATURE: a member's standalone BIP-340
|
|
104837
|
+
* signature over the fallback script-path sighash. The signature is
|
|
104838
|
+
* authenticated to the sender (its `signerPk` must be the sender's own cohort
|
|
104839
|
+
* key and the signature must verify against the sighash) and collected. Once k
|
|
104840
|
+
* valid signatures are in, the k-of-n fallback spend is assembled and the
|
|
104841
|
+
* cohort completes via the script path.
|
|
104842
|
+
*/
|
|
104843
|
+
#handleFallbackSignature(message2) {
|
|
104844
|
+
const cohortId = message2.body?.cohortId;
|
|
104845
|
+
if (!cohortId) return;
|
|
104846
|
+
const state = this.#cohortStates.get(cohortId);
|
|
104847
|
+
if (!state || !state.signingSession || !state.fallbackSignatures) return;
|
|
104848
|
+
if (state.phase !== "FallbackRequested" /* FallbackRequested */) return;
|
|
104849
|
+
const sessionId = message2.body?.sessionId;
|
|
104850
|
+
if (sessionId !== state.signingSession.id) return;
|
|
104851
|
+
const signerPk = message2.body?.signerPk;
|
|
104852
|
+
const fallbackSignature = message2.body?.fallbackSignature;
|
|
104853
|
+
if (!signerPk || !fallbackSignature) return;
|
|
104854
|
+
const prevOutScript = state.signingSession.prevOutScripts[0];
|
|
104855
|
+
const prevOutValue = state.signingSession.prevOutValues[0];
|
|
104856
|
+
if (!prevOutScript || prevOutValue === void 0) return;
|
|
104857
|
+
const memberKey = state.cohort.participantKeys.get(message2.from);
|
|
104858
|
+
if (!memberKey) {
|
|
104859
|
+
state.rejections.push({ from: message2.from, code: "UPDATE_MALFORMED", reason: "Fallback signature from a non-member" });
|
|
104860
|
+
return;
|
|
104861
|
+
}
|
|
104862
|
+
const memberXOnly = memberKey.slice(1);
|
|
104863
|
+
if (signerPk.length !== 32 || !memberXOnly.every((b, i4) => b === signerPk[i4])) {
|
|
104864
|
+
state.rejections.push({ from: message2.from, code: "UPDATE_MALFORMED", reason: "Fallback signerPk does not match the sender cohort key" });
|
|
104865
|
+
return;
|
|
104866
|
+
}
|
|
104867
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
104868
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
104869
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
104870
|
+
});
|
|
104871
|
+
const sighash = fallbackSighash(state.signingSession.pendingTx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
104872
|
+
let valid = false;
|
|
104873
|
+
try {
|
|
104874
|
+
valid = fallbackSignature.length === 64 && schnorr2.verify(fallbackSignature, sighash, signerPk);
|
|
104875
|
+
} catch {
|
|
104876
|
+
valid = false;
|
|
104877
|
+
}
|
|
104878
|
+
if (!valid) {
|
|
104879
|
+
state.rejections.push({ from: message2.from, code: "UPDATE_VERIFICATION_FAILED", reason: "Fallback signature failed verification" });
|
|
104880
|
+
return;
|
|
104881
|
+
}
|
|
104882
|
+
state.fallbackSignatures.set(message2.from, { pubKey: signerPk, signature: fallbackSignature });
|
|
104883
|
+
if (state.fallbackSignatures.size >= state.cohort.effectiveFallbackThreshold) {
|
|
104884
|
+
const signedTx = buildFallbackSpend({
|
|
104885
|
+
pendingTx: state.signingSession.pendingTx,
|
|
104886
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
104887
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold,
|
|
104888
|
+
recoveryKey: state.cohort.recoveryKey,
|
|
104889
|
+
recoverySequence: state.cohort.recoverySequence,
|
|
104890
|
+
fundingModel: state.cohort.fundingModel,
|
|
104891
|
+
network: state.cohort.network,
|
|
104892
|
+
prevOutScript,
|
|
104893
|
+
prevOutValue,
|
|
104894
|
+
signatures: [...state.fallbackSignatures.values()]
|
|
104895
|
+
});
|
|
104896
|
+
state.result = { cohortId, signature: new Uint8Array(), signedTx, path: "script-path" };
|
|
104897
|
+
state.phase = "Complete" /* Complete */;
|
|
104898
|
+
}
|
|
104899
|
+
}
|
|
104338
104900
|
getResult(cohortId) {
|
|
104339
104901
|
return this.#cohortStates.get(cohortId)?.result;
|
|
104340
104902
|
}
|
|
@@ -104366,6 +104928,7 @@ var AggregationService = class {
|
|
|
104366
104928
|
|
|
104367
104929
|
// src/core/aggregation/participant.ts
|
|
104368
104930
|
init_shim();
|
|
104931
|
+
init_secp256k1();
|
|
104369
104932
|
init_utils();
|
|
104370
104933
|
|
|
104371
104934
|
// src/core/aggregation/messages/bodies.ts
|
|
@@ -104379,6 +104942,14 @@ var optIntMin = (b, k, min) => {
|
|
|
104379
104942
|
const v = b ? b[k] : void 0;
|
|
104380
104943
|
return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
|
|
104381
104944
|
};
|
|
104945
|
+
var intInRange = (b, k, min, max) => {
|
|
104946
|
+
const v = b ? b[k] : void 0;
|
|
104947
|
+
return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
104948
|
+
};
|
|
104949
|
+
var optStrOneOf = (b, k, allowed) => {
|
|
104950
|
+
const v = b ? b[k] : void 0;
|
|
104951
|
+
return v === void 0 || typeof v === "string" && allowed.includes(v);
|
|
104952
|
+
};
|
|
104382
104953
|
var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
|
|
104383
104954
|
var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
|
|
104384
104955
|
var hasBytesArray = (b, k) => {
|
|
@@ -104386,7 +104957,7 @@ var hasBytesArray = (b, k) => {
|
|
|
104386
104957
|
return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
|
|
104387
104958
|
};
|
|
104388
104959
|
function isCohortAdvertMessage(m2) {
|
|
104389
|
-
return m2.type === COHORT_ADVERT && hasStr(m2.body, "cohortId") && hasIntMin(m2.body, "minParticipants", 1) && optIntMin(m2.body, "maxParticipants", 1) && hasStr(m2.body, "beaconType") && hasStr(m2.body, "network") && hasBytes(m2.body, "communicationPk");
|
|
104960
|
+
return m2.type === COHORT_ADVERT && hasStr(m2.body, "cohortId") && hasIntMin(m2.body, "minParticipants", 1) && optIntMin(m2.body, "maxParticipants", 1) && hasStr(m2.body, "beaconType") && hasStr(m2.body, "network") && hasStr(m2.body, "recoveryKey") && intInRange(m2.body, "recoverySequence", 1, MAX_RECOVERY_SEQUENCE) && optStrOneOf(m2.body, "fundingModel", KNOWN_FUNDING_MODELS) && optIntMin(m2.body, "fallbackThreshold", 1) && hasBytes(m2.body, "communicationPk");
|
|
104390
104961
|
}
|
|
104391
104962
|
function isCohortOptInMessage(m2) {
|
|
104392
104963
|
return m2.type === COHORT_OPT_IN && hasStr(m2.body, "cohortId") && hasBytes(m2.body, "participantPk") && hasBytes(m2.body, "communicationPk");
|
|
@@ -104400,6 +104971,9 @@ function isCohortReadyMessage(m2) {
|
|
|
104400
104971
|
function isSubmitUpdateMessage(m2) {
|
|
104401
104972
|
return m2.type === SUBMIT_UPDATE && hasStr(m2.body, "cohortId") && !!m2.body && typeof m2.body.signedUpdate === "object";
|
|
104402
104973
|
}
|
|
104974
|
+
function isSubmitNonIncludedMessage(m2) {
|
|
104975
|
+
return m2.type === SUBMIT_NONINCLUDED && hasStr(m2.body, "cohortId");
|
|
104976
|
+
}
|
|
104403
104977
|
function isDistributeAggregatedDataMessage(m2) {
|
|
104404
104978
|
return m2.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m2.body, "cohortId") && hasStr(m2.body, "beaconType") && hasStr(m2.body, "signalBytesHex");
|
|
104405
104979
|
}
|
|
@@ -104418,8 +104992,38 @@ function isAggregatedNonceMessage(m2) {
|
|
|
104418
104992
|
function isSignatureAuthorizationMessage(m2) {
|
|
104419
104993
|
return m2.type === SIGNATURE_AUTHORIZATION && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "partialSignature");
|
|
104420
104994
|
}
|
|
104995
|
+
function isFallbackAuthorizationRequestMessage(m2) {
|
|
104996
|
+
return m2.type === FALLBACK_AUTHORIZATION_REQUEST && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasStr(m2.body, "pendingTx") && hasStr(m2.body, "prevOutScriptHex") && hasStr(m2.body, "prevOutValue") && hasStr(m2.body, "fallbackLeafScriptHex");
|
|
104997
|
+
}
|
|
104998
|
+
function isFallbackSignatureMessage(m2) {
|
|
104999
|
+
return m2.type === FALLBACK_SIGNATURE && hasStr(m2.body, "cohortId") && hasStr(m2.body, "sessionId") && hasBytes(m2.body, "signerPk") && hasBytes(m2.body, "fallbackSignature");
|
|
105000
|
+
}
|
|
104421
105001
|
|
|
104422
105002
|
// src/core/aggregation/participant.ts
|
|
105003
|
+
function txEmbedsSignal(tx, signalHex) {
|
|
105004
|
+
let expected;
|
|
105005
|
+
try {
|
|
105006
|
+
expected = hexToBytes(signalHex);
|
|
105007
|
+
} catch {
|
|
105008
|
+
return false;
|
|
105009
|
+
}
|
|
105010
|
+
if (expected.length === 0) return false;
|
|
105011
|
+
for (let i4 = 0; i4 < tx.outputsLength; i4++) {
|
|
105012
|
+
const script = tx.getOutput(i4)?.script;
|
|
105013
|
+
if (!script) continue;
|
|
105014
|
+
let decoded;
|
|
105015
|
+
try {
|
|
105016
|
+
decoded = Script.decode(script);
|
|
105017
|
+
} catch {
|
|
105018
|
+
continue;
|
|
105019
|
+
}
|
|
105020
|
+
if (decoded.length === 2 && decoded[0] === "RETURN" && decoded[1] instanceof Uint8Array) {
|
|
105021
|
+
const payload = decoded[1];
|
|
105022
|
+
if (payload.length === expected.length && payload.every((b, j) => b === expected[j])) return true;
|
|
105023
|
+
}
|
|
105024
|
+
}
|
|
105025
|
+
return false;
|
|
105026
|
+
}
|
|
104423
105027
|
var AggregationParticipant = class {
|
|
104424
105028
|
did;
|
|
104425
105029
|
/** MuSig2 signing capability. The raw secret never lives as a field here. */
|
|
@@ -104436,7 +105040,7 @@ var AggregationParticipant = class {
|
|
|
104436
105040
|
}
|
|
104437
105041
|
/**
|
|
104438
105042
|
* Process an incoming message. Updates internal state but never produces
|
|
104439
|
-
* outgoing messages
|
|
105043
|
+
* outgoing messages: those come exclusively from action methods.
|
|
104440
105044
|
*/
|
|
104441
105045
|
receive(message2) {
|
|
104442
105046
|
if (message2.version === void 0 || message2.version !== AGGREGATION_WIRE_VERSION) {
|
|
@@ -104462,6 +105066,9 @@ var AggregationParticipant = class {
|
|
|
104462
105066
|
case AGGREGATED_NONCE:
|
|
104463
105067
|
this.#handleAggregatedNonce(message2);
|
|
104464
105068
|
break;
|
|
105069
|
+
case FALLBACK_AUTHORIZATION_REQUEST:
|
|
105070
|
+
this.#handleFallbackAuthorizationRequest(message2);
|
|
105071
|
+
break;
|
|
104465
105072
|
default:
|
|
104466
105073
|
break;
|
|
104467
105074
|
}
|
|
@@ -104507,12 +105114,17 @@ var AggregationParticipant = class {
|
|
|
104507
105114
|
{ cohortId, phase: state?.phase }
|
|
104508
105115
|
);
|
|
104509
105116
|
}
|
|
105117
|
+
const advert = state.advert;
|
|
104510
105118
|
const cohort = new AggregationCohort({
|
|
104511
105119
|
id: cohortId,
|
|
104512
105120
|
serviceDid: state.serviceDid,
|
|
104513
|
-
minParticipants:
|
|
104514
|
-
network:
|
|
104515
|
-
beaconType:
|
|
105121
|
+
minParticipants: advert.minParticipants,
|
|
105122
|
+
network: advert.network,
|
|
105123
|
+
beaconType: advert.beaconType,
|
|
105124
|
+
recoveryKey: advert.recoveryKey ? hexToBytes(advert.recoveryKey) : void 0,
|
|
105125
|
+
recoverySequence: advert.recoverySequence,
|
|
105126
|
+
fundingModel: advert.fundingModel,
|
|
105127
|
+
fallbackThreshold: advert.fallbackThreshold
|
|
104516
105128
|
});
|
|
104517
105129
|
state.cohort = cohort;
|
|
104518
105130
|
state.phase = "OptedIn" /* OptedIn */;
|
|
@@ -104527,7 +105139,7 @@ var AggregationParticipant = class {
|
|
|
104527
105139
|
}
|
|
104528
105140
|
#handleOptInAccept(message2) {
|
|
104529
105141
|
}
|
|
104530
|
-
/** Cohorts that have been finalized
|
|
105142
|
+
/** Cohorts that have been finalized: beacon address available. */
|
|
104531
105143
|
get joinedCohorts() {
|
|
104532
105144
|
const map3 = /* @__PURE__ */ new Map();
|
|
104533
105145
|
for (const [id, state] of this.#cohortStates) {
|
|
@@ -104570,6 +105182,7 @@ var AggregationParticipant = class {
|
|
|
104570
105182
|
);
|
|
104571
105183
|
}
|
|
104572
105184
|
state.submittedUpdate = signedUpdate;
|
|
105185
|
+
state.included = true;
|
|
104573
105186
|
state.phase = "UpdateSubmitted" /* UpdateSubmitted */;
|
|
104574
105187
|
const message2 = createSubmitUpdateMessage({
|
|
104575
105188
|
from: this.did,
|
|
@@ -104579,6 +105192,30 @@ var AggregationParticipant = class {
|
|
|
104579
105192
|
});
|
|
104580
105193
|
return [message2];
|
|
104581
105194
|
}
|
|
105195
|
+
/**
|
|
105196
|
+
* User action: decline to submit an update this round (cooperative
|
|
105197
|
+
* non-inclusion). The member stays in the cohort and still signs; it will be
|
|
105198
|
+
* absent from the CAS Announcement Map, or carry a non-inclusion leaf in the
|
|
105199
|
+
* SMT. Returns the SUBMIT_NONINCLUDED message to send.
|
|
105200
|
+
*/
|
|
105201
|
+
declineUpdate(cohortId) {
|
|
105202
|
+
const state = this.#cohortStates.get(cohortId);
|
|
105203
|
+
if (!state || state.phase !== "CohortReady" /* CohortReady */) {
|
|
105204
|
+
throw new AggregationParticipantError(
|
|
105205
|
+
`Cannot decline in cohort ${cohortId}: not in CohortReady phase.`,
|
|
105206
|
+
"INVALID_PHASE",
|
|
105207
|
+
{ cohortId, phase: state?.phase }
|
|
105208
|
+
);
|
|
105209
|
+
}
|
|
105210
|
+
state.included = false;
|
|
105211
|
+
state.phase = "NonIncluded" /* NonIncluded */;
|
|
105212
|
+
const message2 = createSubmitNonIncludedMessage({
|
|
105213
|
+
from: this.did,
|
|
105214
|
+
to: state.serviceDid,
|
|
105215
|
+
cohortId
|
|
105216
|
+
});
|
|
105217
|
+
return [message2];
|
|
105218
|
+
}
|
|
104582
105219
|
/** Aggregated data awaiting user validation. */
|
|
104583
105220
|
get pendingValidations() {
|
|
104584
105221
|
const map3 = /* @__PURE__ */ new Map();
|
|
@@ -104589,22 +105226,35 @@ var AggregationParticipant = class {
|
|
|
104589
105226
|
}
|
|
104590
105227
|
return map3;
|
|
104591
105228
|
}
|
|
105229
|
+
/**
|
|
105230
|
+
* The validated aggregated data retained for a cohort, regardless of phase.
|
|
105231
|
+
* Unlike {@link pendingValidations} (which lists only cohorts still awaiting
|
|
105232
|
+
* the validate decision), this returns the stored validation, including the
|
|
105233
|
+
* participant's sidecar (the CAS Announcement map or its SMT inclusion proof),
|
|
105234
|
+
* so it is still readable once the cohort reaches Complete. Returns
|
|
105235
|
+
* undefined before aggregated data has been received.
|
|
105236
|
+
*/
|
|
105237
|
+
getValidation(cohortId) {
|
|
105238
|
+
return this.#cohortStates.get(cohortId)?.validation;
|
|
105239
|
+
}
|
|
104592
105240
|
#handleDistributeAggregatedData(message2) {
|
|
104593
105241
|
const cohortId = message2.body?.cohortId;
|
|
104594
105242
|
if (!cohortId) return;
|
|
104595
105243
|
const state = this.#cohortStates.get(cohortId);
|
|
104596
|
-
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
|
|
104597
|
-
|
|
105244
|
+
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */ && state.phase !== "NonIncluded" /* NonIncluded */) return;
|
|
105245
|
+
const declined = state.included === false;
|
|
105246
|
+
if (!declined && !state.submittedUpdate) return;
|
|
104598
105247
|
const beaconType = message2.body?.beaconType;
|
|
104599
105248
|
if (!beaconType) return;
|
|
104600
105249
|
const strategy = getBeaconStrategy(beaconType);
|
|
104601
105250
|
if (!strategy) return;
|
|
104602
105251
|
const signalBytesHex = message2.body?.signalBytesHex ?? "";
|
|
104603
|
-
const expectedHash = canonicalHash(state.submittedUpdate);
|
|
105252
|
+
const expectedHash = declined ? "" : canonicalHash(state.submittedUpdate);
|
|
104604
105253
|
const result = strategy.validateParticipantView({
|
|
104605
105254
|
participantDid: this.did,
|
|
104606
|
-
|
|
104607
|
-
|
|
105255
|
+
included: !declined,
|
|
105256
|
+
submittedUpdate: declined ? void 0 : state.submittedUpdate,
|
|
105257
|
+
expectedHash: declined ? void 0 : expectedHash,
|
|
104608
105258
|
body: message2.body
|
|
104609
105259
|
});
|
|
104610
105260
|
state.validation = {
|
|
@@ -104614,7 +105264,8 @@ var AggregationParticipant = class {
|
|
|
104614
105264
|
expectedHash,
|
|
104615
105265
|
matches: result.matches,
|
|
104616
105266
|
casAnnouncement: result.casAnnouncement,
|
|
104617
|
-
smtProof: result.smtProof
|
|
105267
|
+
smtProof: result.smtProof,
|
|
105268
|
+
included: !declined
|
|
104618
105269
|
};
|
|
104619
105270
|
state.phase = "AwaitingValidation" /* AwaitingValidation */;
|
|
104620
105271
|
}
|
|
@@ -104688,6 +105339,31 @@ var AggregationParticipant = class {
|
|
|
104688
105339
|
};
|
|
104689
105340
|
state.phase = "AwaitingSigning" /* AwaitingSigning */;
|
|
104690
105341
|
}
|
|
105342
|
+
/**
|
|
105343
|
+
* Bind a signing approval to the announcement the member validated: a beacon
|
|
105344
|
+
* transaction MUST carry an OP_RETURN with the exact 32-byte signal stored when
|
|
105345
|
+
* the aggregated data was distributed. Both the optimistic nonce approval and
|
|
105346
|
+
* the fallback approval sign with SIGHASH_DEFAULT (committing to every output)
|
|
105347
|
+
* while the coordinator drives output selection, so without this check a
|
|
105348
|
+
* coordinator could anchor a different signal under the member's signature.
|
|
105349
|
+
*/
|
|
105350
|
+
#assertTxAnchorsValidatedSignal(cohortId, state, tx) {
|
|
105351
|
+
const signalHex = state.validation?.signalBytesHex;
|
|
105352
|
+
if (!signalHex) {
|
|
105353
|
+
throw new AggregationParticipantError(
|
|
105354
|
+
`Cohort ${cohortId} has no validated signal to bind the signature to.`,
|
|
105355
|
+
"MISSING_STATE",
|
|
105356
|
+
{ cohortId }
|
|
105357
|
+
);
|
|
105358
|
+
}
|
|
105359
|
+
if (!txEmbedsSignal(tx, signalHex)) {
|
|
105360
|
+
throw new AggregationParticipantError(
|
|
105361
|
+
`Transaction for cohort ${cohortId} does not anchor the validated signal.`,
|
|
105362
|
+
"SIGNAL_MISMATCH",
|
|
105363
|
+
{ cohortId }
|
|
105364
|
+
);
|
|
105365
|
+
}
|
|
105366
|
+
}
|
|
104691
105367
|
/**
|
|
104692
105368
|
* User action: approve signing and generate nonce contribution.
|
|
104693
105369
|
*/
|
|
@@ -104707,7 +105383,8 @@ var AggregationParticipant = class {
|
|
|
104707
105383
|
{ cohortId }
|
|
104708
105384
|
);
|
|
104709
105385
|
}
|
|
104710
|
-
const tx = Transaction.fromRaw(hexToBytes(state.signingRequest.pendingTxHex));
|
|
105386
|
+
const tx = Transaction.fromRaw(hexToBytes(state.signingRequest.pendingTxHex), { allowUnknownOutputs: true });
|
|
105387
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
104711
105388
|
const prevOutScripts = [hexToBytes(state.signingRequest.prevOutScriptHex)];
|
|
104712
105389
|
const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
|
|
104713
105390
|
const session = new BeaconSigningSession({
|
|
@@ -104743,7 +105420,7 @@ var AggregationParticipant = class {
|
|
|
104743
105420
|
}
|
|
104744
105421
|
/**
|
|
104745
105422
|
* User action: generate and return the partial signature.
|
|
104746
|
-
* In most UIs this is automatic after AwaitingPartialSig
|
|
105423
|
+
* In most UIs this is automatic after AwaitingPartialSig, but exposing it
|
|
104747
105424
|
* as an explicit action lets the client UI confirm before signing if desired.
|
|
104748
105425
|
*/
|
|
104749
105426
|
generatePartialSignature(cohortId) {
|
|
@@ -104775,6 +105452,83 @@ var AggregationParticipant = class {
|
|
|
104775
105452
|
partialSignature: partialSig
|
|
104776
105453
|
})];
|
|
104777
105454
|
}
|
|
105455
|
+
/** Fallback signing requests awaiting user approval (UI: review the fallback spend). */
|
|
105456
|
+
get pendingFallbackRequests() {
|
|
105457
|
+
const map3 = /* @__PURE__ */ new Map();
|
|
105458
|
+
for (const [id, state] of this.#cohortStates) {
|
|
105459
|
+
if (state.phase === "AwaitingFallbackSig" /* AwaitingFallbackSig */ && state.fallbackRequest) {
|
|
105460
|
+
map3.set(id, state.fallbackRequest);
|
|
105461
|
+
}
|
|
105462
|
+
}
|
|
105463
|
+
return map3;
|
|
105464
|
+
}
|
|
105465
|
+
#handleFallbackAuthorizationRequest(message2) {
|
|
105466
|
+
const cohortId = message2.body?.cohortId;
|
|
105467
|
+
if (!cohortId) return;
|
|
105468
|
+
const state = this.#cohortStates.get(cohortId);
|
|
105469
|
+
if (!state || !state.cohort) return;
|
|
105470
|
+
const acceptFrom = [
|
|
105471
|
+
"ValidationSent" /* ValidationSent */,
|
|
105472
|
+
"AwaitingSigning" /* AwaitingSigning */,
|
|
105473
|
+
"NonceSent" /* NonceSent */,
|
|
105474
|
+
"AwaitingPartialSig" /* AwaitingPartialSig */,
|
|
105475
|
+
"Complete" /* Complete */
|
|
105476
|
+
];
|
|
105477
|
+
if (!acceptFrom.includes(state.phase)) return;
|
|
105478
|
+
const sessionId = message2.body?.sessionId;
|
|
105479
|
+
const pendingTxHex = message2.body?.pendingTx;
|
|
105480
|
+
const prevOutScriptHex = message2.body?.prevOutScriptHex;
|
|
105481
|
+
const prevOutValue = message2.body?.prevOutValue;
|
|
105482
|
+
const fallbackLeafScriptHex = message2.body?.fallbackLeafScriptHex;
|
|
105483
|
+
if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue || !fallbackLeafScriptHex) return;
|
|
105484
|
+
state.fallbackRequest = { cohortId, sessionId, pendingTxHex, prevOutScriptHex, prevOutValue, fallbackLeafScriptHex };
|
|
105485
|
+
state.signingSession?.clearSecrets();
|
|
105486
|
+
state.phase = "AwaitingFallbackSig" /* AwaitingFallbackSig */;
|
|
105487
|
+
}
|
|
105488
|
+
/**
|
|
105489
|
+
* User action: authorize the fallback spend. Recomputes the k-of-n fallback
|
|
105490
|
+
* leaf from the member's OWN cohort state (not the service-provided script),
|
|
105491
|
+
* computes the BIP-341 script-path sighash over the requested transaction, and
|
|
105492
|
+
* returns a standalone BIP-340 signature (no nonce round). The member completes
|
|
105493
|
+
* once it has contributed; the service needs only k of these.
|
|
105494
|
+
*/
|
|
105495
|
+
approveFallback(cohortId) {
|
|
105496
|
+
const state = this.#cohortStates.get(cohortId);
|
|
105497
|
+
if (!state || state.phase !== "AwaitingFallbackSig" /* AwaitingFallbackSig */) {
|
|
105498
|
+
throw new AggregationParticipantError(
|
|
105499
|
+
`Cannot approve fallback for cohort ${cohortId}: not in AwaitingFallbackSig phase.`,
|
|
105500
|
+
"INVALID_PHASE",
|
|
105501
|
+
{ cohortId, phase: state?.phase }
|
|
105502
|
+
);
|
|
105503
|
+
}
|
|
105504
|
+
if (!state.fallbackRequest || !state.cohort) {
|
|
105505
|
+
throw new AggregationParticipantError(
|
|
105506
|
+
`Cohort ${cohortId} missing fallback request or cohort state.`,
|
|
105507
|
+
"MISSING_STATE",
|
|
105508
|
+
{ cohortId }
|
|
105509
|
+
);
|
|
105510
|
+
}
|
|
105511
|
+
const req = state.fallbackRequest;
|
|
105512
|
+
const tx = Transaction.fromRaw(hexToBytes(req.pendingTxHex), { allowUnknownOutputs: true });
|
|
105513
|
+
const prevOutScript = hexToBytes(req.prevOutScriptHex);
|
|
105514
|
+
const prevOutValue = BigInt(req.prevOutValue);
|
|
105515
|
+
this.#assertTxAnchorsValidatedSignal(cohortId, state, tx);
|
|
105516
|
+
const fallbackLeaf = buildFallbackLeaf({
|
|
105517
|
+
cohortKeys: state.cohort.cohortKeys,
|
|
105518
|
+
fallbackThreshold: state.cohort.effectiveFallbackThreshold
|
|
105519
|
+
});
|
|
105520
|
+
const sighash = fallbackSighash(tx, 0, prevOutScript, prevOutValue, fallbackLeaf);
|
|
105521
|
+
const signature = this.#signer.withSecret((secretKey) => schnorr2.sign(sighash, secretKey));
|
|
105522
|
+
state.phase = "Complete" /* Complete */;
|
|
105523
|
+
return [createFallbackSignatureMessage({
|
|
105524
|
+
from: this.did,
|
|
105525
|
+
to: state.serviceDid,
|
|
105526
|
+
cohortId,
|
|
105527
|
+
sessionId: req.sessionId,
|
|
105528
|
+
signerPk: this.publicKey.slice(1),
|
|
105529
|
+
fallbackSignature: signature
|
|
105530
|
+
})];
|
|
105531
|
+
}
|
|
104778
105532
|
getCohortPhase(cohortId) {
|
|
104779
105533
|
return this.#cohortStates.get(cohortId)?.phase;
|
|
104780
105534
|
}
|
|
@@ -104811,6 +105565,85 @@ var KeyPairAggregationSigner = class {
|
|
|
104811
105565
|
}
|
|
104812
105566
|
};
|
|
104813
105567
|
|
|
105568
|
+
// src/core/aggregation/recovery-spend.ts
|
|
105569
|
+
init_shim();
|
|
105570
|
+
init_secp256k1();
|
|
105571
|
+
var DUST_LIMIT_SATS = 546n;
|
|
105572
|
+
function bytesEqual2(a2, b) {
|
|
105573
|
+
return a2.length === b.length && a2.every((x, i4) => x === b[i4]);
|
|
105574
|
+
}
|
|
105575
|
+
function buildRecoverySpend(params) {
|
|
105576
|
+
const {
|
|
105577
|
+
cohortKeys,
|
|
105578
|
+
recoverySecretKey,
|
|
105579
|
+
recoveryKey,
|
|
105580
|
+
recoverySequence,
|
|
105581
|
+
fallbackThreshold,
|
|
105582
|
+
fundingModel,
|
|
105583
|
+
network,
|
|
105584
|
+
utxo,
|
|
105585
|
+
destinationAddress,
|
|
105586
|
+
fee,
|
|
105587
|
+
beaconAddress
|
|
105588
|
+
} = params;
|
|
105589
|
+
if (cohortKeys.length === 0) {
|
|
105590
|
+
throw new AggregationCohortError(
|
|
105591
|
+
"Cannot build recovery spend: no cohort keys.",
|
|
105592
|
+
"NO_COHORT_KEYS"
|
|
105593
|
+
);
|
|
105594
|
+
}
|
|
105595
|
+
const derivedPub = schnorr2.getPublicKey(recoverySecretKey);
|
|
105596
|
+
if (!bytesEqual2(derivedPub, recoveryKey)) {
|
|
105597
|
+
throw new AggregationCohortError(
|
|
105598
|
+
"Recovery secret key does not correspond to the committed recovery key.",
|
|
105599
|
+
"RECOVERY_KEY_MISMATCH"
|
|
105600
|
+
);
|
|
105601
|
+
}
|
|
105602
|
+
const out = utxo.value - fee;
|
|
105603
|
+
if (out <= 0n) {
|
|
105604
|
+
throw new AggregationCohortError(
|
|
105605
|
+
`Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
|
|
105606
|
+
"FEE_EXCEEDS_VALUE",
|
|
105607
|
+
{ fee: fee.toString(), value: utxo.value.toString() }
|
|
105608
|
+
);
|
|
105609
|
+
}
|
|
105610
|
+
if (out < DUST_LIMIT_SATS) {
|
|
105611
|
+
throw new AggregationCohortError(
|
|
105612
|
+
`Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
|
|
105613
|
+
"DUST_OUTPUT",
|
|
105614
|
+
{ output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
|
|
105615
|
+
);
|
|
105616
|
+
}
|
|
105617
|
+
const internalKey = keyAggExport(keyAggregate(sortKeys(cohortKeys)));
|
|
105618
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
105619
|
+
recoveryKey,
|
|
105620
|
+
recoverySequence,
|
|
105621
|
+
cohortKeys,
|
|
105622
|
+
fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
|
|
105623
|
+
});
|
|
105624
|
+
const net = getNetwork(network);
|
|
105625
|
+
const payment = p2tr(internalKey, leaves, net, true);
|
|
105626
|
+
if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
|
|
105627
|
+
throw new AggregationCohortError(
|
|
105628
|
+
`Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
|
|
105629
|
+
"BEACON_ADDRESS_MISMATCH",
|
|
105630
|
+
{ reconstructed: payment.address, expected: beaconAddress }
|
|
105631
|
+
);
|
|
105632
|
+
}
|
|
105633
|
+
const tx = new Transaction({ version: 2, allowUnknownInputs: true });
|
|
105634
|
+
tx.addInput({
|
|
105635
|
+
txid: utxo.txid,
|
|
105636
|
+
index: utxo.vout,
|
|
105637
|
+
witnessUtxo: { script: payment.script, amount: utxo.value },
|
|
105638
|
+
tapLeafScript: payment.tapLeafScript,
|
|
105639
|
+
sequence: recoverySequence
|
|
105640
|
+
});
|
|
105641
|
+
tx.addOutputAddress(destinationAddress, out, net);
|
|
105642
|
+
tx.signIdx(recoverySecretKey, 0);
|
|
105643
|
+
tx.finalize();
|
|
105644
|
+
return tx;
|
|
105645
|
+
}
|
|
105646
|
+
|
|
104814
105647
|
// src/core/aggregation/logger.ts
|
|
104815
105648
|
init_shim();
|
|
104816
105649
|
var CONSOLE_LOGGER = {
|
|
@@ -104843,6 +105676,7 @@ var KEYGEN_VALUES = /* @__PURE__ */ new Set([
|
|
|
104843
105676
|
]);
|
|
104844
105677
|
var UPDATE_VALUES = /* @__PURE__ */ new Set([
|
|
104845
105678
|
SUBMIT_UPDATE,
|
|
105679
|
+
SUBMIT_NONINCLUDED,
|
|
104846
105680
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
104847
105681
|
VALIDATION_ACK
|
|
104848
105682
|
]);
|
|
@@ -104850,7 +105684,9 @@ var SIGN_VALUES = /* @__PURE__ */ new Set([
|
|
|
104850
105684
|
AUTHORIZATION_REQUEST,
|
|
104851
105685
|
NONCE_CONTRIBUTION,
|
|
104852
105686
|
AGGREGATED_NONCE,
|
|
104853
|
-
SIGNATURE_AUTHORIZATION
|
|
105687
|
+
SIGNATURE_AUTHORIZATION,
|
|
105688
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
105689
|
+
FALLBACK_SIGNATURE
|
|
104854
105690
|
]);
|
|
104855
105691
|
function isAggregationMessageType(type) {
|
|
104856
105692
|
return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
|
|
@@ -111011,7 +111847,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
111011
111847
|
* backfill historical events to late subscribers: republishing gives late
|
|
111012
111848
|
* joiners a window to discover the message without requiring protocol
|
|
111013
111849
|
* changes. Relay rate-limit / publish failures inside the interval are
|
|
111014
|
-
* caught and logged rather than propagated
|
|
111850
|
+
* caught and logged rather than propagated - the caller should stop the
|
|
111015
111851
|
* repeater once the protocol condition is satisfied.
|
|
111016
111852
|
*/
|
|
111017
111853
|
publishRepeating(message2, sender, intervalMs, recipient) {
|
|
@@ -111465,35 +112301,29 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111465
112301
|
session;
|
|
111466
112302
|
#transport;
|
|
111467
112303
|
#did;
|
|
111468
|
-
#
|
|
112304
|
+
#defaultConfig;
|
|
111469
112305
|
#onOptInReceived;
|
|
111470
112306
|
#onReadyToFinalize;
|
|
111471
112307
|
#onProvideTxData;
|
|
111472
112308
|
#cohortTtlMs;
|
|
111473
112309
|
#phaseTimeoutMs;
|
|
111474
112310
|
#advertRepeatIntervalMs;
|
|
111475
|
-
#
|
|
112311
|
+
#autoFallbackOnStall;
|
|
112312
|
+
/** Phases during which a stall can be salvaged by the k-of-n fallback (ADR 042). */
|
|
112313
|
+
static #SIGNING_PHASES = [
|
|
112314
|
+
"SigningStarted" /* SigningStarted */,
|
|
112315
|
+
"NoncesCollected" /* NoncesCollected */,
|
|
112316
|
+
"AwaitingPartialSigs" /* AwaitingPartialSigs */
|
|
112317
|
+
];
|
|
112318
|
+
/** Per-cohort run state, keyed by cohortId. */
|
|
112319
|
+
#contexts = /* @__PURE__ */ new Map();
|
|
111476
112320
|
#handlersRegistered = false;
|
|
111477
112321
|
#stopped = false;
|
|
111478
|
-
/**
|
|
111479
|
-
* Guard against the async race where two concurrent #handleOptIn invocations
|
|
111480
|
-
* both pass the `participants.length >= minParticipants` check before either
|
|
111481
|
-
* mutates the cohort phase. Set synchronously before any `await` so subsequent
|
|
111482
|
-
* handlers observe it on their next resumption.
|
|
111483
|
-
*/
|
|
111484
|
-
#finalizing = false;
|
|
111485
|
-
#resolveRun;
|
|
111486
|
-
#rejectRun;
|
|
111487
|
-
#cohortTtlTimer;
|
|
111488
|
-
#phaseTimer;
|
|
111489
|
-
#lastObservedPhase;
|
|
111490
|
-
/** Stop handle for the repeating COHORT_ADVERT publish loop. */
|
|
111491
|
-
#stopAdvertRepeat;
|
|
111492
112322
|
constructor(options2) {
|
|
111493
112323
|
super();
|
|
111494
112324
|
this.#transport = options2.transport;
|
|
111495
112325
|
this.#did = options2.did;
|
|
111496
|
-
this.#
|
|
112326
|
+
this.#defaultConfig = options2.config;
|
|
111497
112327
|
this.#onOptInReceived = options2.onOptInReceived ?? (async () => ({ accepted: true }));
|
|
111498
112328
|
this.#onReadyToFinalize = options2.onReadyToFinalize ?? (async ({ acceptedCount, minRequired }) => ({
|
|
111499
112329
|
finalize: acceptedCount >= minRequired
|
|
@@ -111502,6 +112332,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111502
112332
|
this.#cohortTtlMs = options2.cohortTtlMs;
|
|
111503
112333
|
this.#phaseTimeoutMs = options2.phaseTimeoutMs;
|
|
111504
112334
|
this.#advertRepeatIntervalMs = options2.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
|
|
112335
|
+
this.#autoFallbackOnStall = options2.autoFallbackOnStall ?? false;
|
|
111505
112336
|
this.session = new AggregationService({
|
|
111506
112337
|
// The coordinator never signs, so the state machine receives only the
|
|
111507
112338
|
// public half of the operator's keypair (see ADR 038). The full keypair
|
|
@@ -111511,55 +112342,126 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111511
112342
|
maxUpdateSizeBytes: options2.maxUpdateSizeBytes
|
|
111512
112343
|
});
|
|
111513
112344
|
}
|
|
112345
|
+
/** Resolve the {@link RunContext} an inbound message belongs to, by cohortId. */
|
|
112346
|
+
#contextFor(msg) {
|
|
112347
|
+
const cohortId = msg.body?.cohortId;
|
|
112348
|
+
if (!cohortId) return void 0;
|
|
112349
|
+
return this.#contexts.get(cohortId);
|
|
112350
|
+
}
|
|
111514
112351
|
/**
|
|
111515
|
-
* Drain any silent rejections the state machine recorded
|
|
111516
|
-
* recent receive() and surface them as `message-rejected` events.
|
|
111517
|
-
* call even before a cohortId is assigned.
|
|
112352
|
+
* Drain any silent rejections the state machine recorded for a cohort during
|
|
112353
|
+
* the most recent receive() and surface them as `message-rejected` events.
|
|
111518
112354
|
*/
|
|
111519
|
-
#drainRejections() {
|
|
111520
|
-
|
|
111521
|
-
|
|
111522
|
-
this.emit("message-rejected", { cohortId: this.#cohortId, ...r2 });
|
|
112355
|
+
#drainRejections(ctx) {
|
|
112356
|
+
for (const r2 of this.session.drainRejections(ctx.cohortId)) {
|
|
112357
|
+
this.emit("message-rejected", { cohortId: ctx.cohortId, ...r2 });
|
|
111523
112358
|
}
|
|
111524
112359
|
}
|
|
111525
112360
|
/**
|
|
111526
|
-
*
|
|
111527
|
-
*
|
|
112361
|
+
* Advertise a new cohort and begin driving it to completion. Callable many
|
|
112362
|
+
* times on one runner; each cohort runs concurrently and independently.
|
|
112363
|
+
*
|
|
112364
|
+
* @param config Per-cohort conditions + network (see {@link CohortConfig}).
|
|
112365
|
+
* @returns The new cohort's id and a `completion` promise that resolves with
|
|
112366
|
+
* that cohort's {@link AggregationResult} (or rejects if it fails/stalls).
|
|
112367
|
+
* @throws If the runner has been stopped, or the config is invalid
|
|
112368
|
+
* (fail-fast via `createCohort`).
|
|
112369
|
+
*/
|
|
112370
|
+
advertiseCohort(config) {
|
|
112371
|
+
if (this.#stopped) {
|
|
112372
|
+
throw new AggregationServiceError("Cannot advertise on a stopped runner.", "RUNNER_STOPPED", {});
|
|
112373
|
+
}
|
|
112374
|
+
this.#registerHandlers();
|
|
112375
|
+
const cohortId = this.session.createCohort(config);
|
|
112376
|
+
let resolve;
|
|
112377
|
+
let reject;
|
|
112378
|
+
const completion = new Promise((res, rej) => {
|
|
112379
|
+
resolve = res;
|
|
112380
|
+
reject = rej;
|
|
112381
|
+
});
|
|
112382
|
+
const ctx = {
|
|
112383
|
+
cohortId,
|
|
112384
|
+
config,
|
|
112385
|
+
resolve,
|
|
112386
|
+
reject,
|
|
112387
|
+
completion,
|
|
112388
|
+
finalizing: false,
|
|
112389
|
+
settled: false
|
|
112390
|
+
};
|
|
112391
|
+
this.#contexts.set(cohortId, ctx);
|
|
112392
|
+
try {
|
|
112393
|
+
this.#startTimers(ctx);
|
|
112394
|
+
const advertMsgs = this.session.advertise(cohortId);
|
|
112395
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112396
|
+
this.emit("cohort-advertised", { cohortId });
|
|
112397
|
+
if (this.#advertRepeatIntervalMs > 0) {
|
|
112398
|
+
this.#startAdvertRepeat(ctx, advertMsgs);
|
|
112399
|
+
} else {
|
|
112400
|
+
this.#sendAll(advertMsgs).catch((err) => this.#failCohort(ctx, err));
|
|
112401
|
+
}
|
|
112402
|
+
} catch (err) {
|
|
112403
|
+
this.#failCohort(ctx, err);
|
|
112404
|
+
}
|
|
112405
|
+
return { cohortId, completion };
|
|
112406
|
+
}
|
|
112407
|
+
/**
|
|
112408
|
+
* Run a single cohort to completion using the `config` supplied in the
|
|
112409
|
+
* runner options. Thin convenience over {@link advertiseCohort} for the
|
|
112410
|
+
* single-cohort case (and the path {@link AggregationRunner.solo} rides).
|
|
111528
112411
|
*
|
|
111529
112412
|
* @returns {Promise<AggregationResult>} The final result with signature and signed tx.
|
|
111530
112413
|
*/
|
|
111531
112414
|
run() {
|
|
111532
|
-
|
|
111533
|
-
|
|
111534
|
-
|
|
111535
|
-
|
|
111536
|
-
|
|
111537
|
-
|
|
111538
|
-
|
|
111539
|
-
|
|
111540
|
-
|
|
111541
|
-
|
|
111542
|
-
|
|
111543
|
-
|
|
111544
|
-
|
|
111545
|
-
|
|
111546
|
-
|
|
111547
|
-
|
|
111548
|
-
|
|
112415
|
+
if (!this.#defaultConfig) {
|
|
112416
|
+
return Promise.reject(new AggregationServiceError(
|
|
112417
|
+
"run() requires `config` in the runner options; use advertiseCohort(config) to drive cohorts explicitly.",
|
|
112418
|
+
"MISSING_COHORT_CONFIG",
|
|
112419
|
+
{}
|
|
112420
|
+
));
|
|
112421
|
+
}
|
|
112422
|
+
try {
|
|
112423
|
+
return this.advertiseCohort(this.#defaultConfig).completion;
|
|
112424
|
+
} catch (err) {
|
|
112425
|
+
return Promise.reject(err);
|
|
112426
|
+
}
|
|
112427
|
+
}
|
|
112428
|
+
/**
|
|
112429
|
+
* Wait for every currently-outstanding cohort to settle and return the
|
|
112430
|
+
* successful results. Dynamic drain: cohorts advertised while this is pending
|
|
112431
|
+
* are included, and it resolves only once no cohorts remain. Failed cohorts
|
|
112432
|
+
* are surfaced via `error` / `cohort-failed` events and their rejected
|
|
112433
|
+
* `completion` promises; they are omitted from the returned array (this
|
|
112434
|
+
* method does not throw). Bound long-running cohorts with `cohortTtlMs` /
|
|
112435
|
+
* `phaseTimeoutMs` or this may never resolve.
|
|
112436
|
+
*
|
|
112437
|
+
* @returns {Promise<AggregationResult[]>} Results of the cohorts that completed.
|
|
112438
|
+
*/
|
|
112439
|
+
async runAll() {
|
|
112440
|
+
const collected = /* @__PURE__ */ new Map();
|
|
112441
|
+
const onComplete = (result) => {
|
|
112442
|
+
collected.set(result.cohortId, result);
|
|
112443
|
+
};
|
|
112444
|
+
this.on("signing-complete", onComplete);
|
|
112445
|
+
try {
|
|
112446
|
+
while (this.#contexts.size > 0) {
|
|
112447
|
+
await Promise.allSettled([...this.#contexts.values()].map((c2) => c2.completion));
|
|
111549
112448
|
}
|
|
111550
|
-
}
|
|
112449
|
+
} finally {
|
|
112450
|
+
this.off("signing-complete", onComplete);
|
|
112451
|
+
}
|
|
112452
|
+
return [...collected.values()];
|
|
111551
112453
|
}
|
|
111552
112454
|
/**
|
|
111553
|
-
* Begin publishing
|
|
111554
|
-
* until
|
|
111555
|
-
*
|
|
112455
|
+
* Begin publishing a cohort's advert immediately and on a repeating interval
|
|
112456
|
+
* until the cohort's advert loop is stopped. Each advert is broadcast (no
|
|
112457
|
+
* recipient) via the transport's `publishRepeating` primitive.
|
|
111556
112458
|
*/
|
|
111557
|
-
#startAdvertRepeat(advertMsgs) {
|
|
112459
|
+
#startAdvertRepeat(ctx, advertMsgs) {
|
|
111558
112460
|
const stops = [];
|
|
111559
112461
|
for (const msg of advertMsgs) {
|
|
111560
112462
|
stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
|
|
111561
112463
|
}
|
|
111562
|
-
|
|
112464
|
+
ctx.stopAdvertRepeat = () => {
|
|
111563
112465
|
for (const stop2 of stops) {
|
|
111564
112466
|
try {
|
|
111565
112467
|
stop2();
|
|
@@ -111568,81 +112470,176 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111568
112470
|
}
|
|
111569
112471
|
};
|
|
111570
112472
|
}
|
|
111571
|
-
/** Stop
|
|
111572
|
-
#stopAdvertRepeating() {
|
|
111573
|
-
if (!
|
|
111574
|
-
const stop2 =
|
|
111575
|
-
|
|
112473
|
+
/** Stop a cohort's advert republish loop. Idempotent. */
|
|
112474
|
+
#stopAdvertRepeating(ctx) {
|
|
112475
|
+
if (!ctx.stopAdvertRepeat) return;
|
|
112476
|
+
const stop2 = ctx.stopAdvertRepeat;
|
|
112477
|
+
ctx.stopAdvertRepeat = void 0;
|
|
111576
112478
|
stop2();
|
|
111577
112479
|
}
|
|
111578
|
-
/** Schedule cohort TTL + phase timeout
|
|
111579
|
-
#startTimers() {
|
|
112480
|
+
/** Schedule a cohort's TTL + phase timeout when it is advertised. */
|
|
112481
|
+
#startTimers(ctx) {
|
|
111580
112482
|
if (this.#cohortTtlMs !== void 0) {
|
|
111581
|
-
|
|
111582
|
-
const reason = `Cohort ${
|
|
111583
|
-
this.emit("cohort-failed", { cohortId:
|
|
111584
|
-
this.#
|
|
112483
|
+
ctx.cohortTtlTimer = setTimeout(() => {
|
|
112484
|
+
const reason = `Cohort ${ctx.cohortId} exceeded TTL of ${this.#cohortTtlMs}ms`;
|
|
112485
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
112486
|
+
this.#failCohort(ctx, new Error(reason));
|
|
111585
112487
|
}, this.#cohortTtlMs);
|
|
111586
112488
|
}
|
|
111587
|
-
this.#resetPhaseTimer();
|
|
112489
|
+
this.#resetPhaseTimer(ctx);
|
|
111588
112490
|
}
|
|
111589
|
-
/** Reset
|
|
111590
|
-
#resetPhaseTimer() {
|
|
111591
|
-
if (
|
|
111592
|
-
|
|
112491
|
+
/** Reset a cohort's per-phase stall timer. Called when a phase transition is observed. */
|
|
112492
|
+
#resetPhaseTimer(ctx) {
|
|
112493
|
+
if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
|
|
112494
|
+
ctx.phaseTimer = void 0;
|
|
111593
112495
|
if (this.#phaseTimeoutMs === void 0) return;
|
|
111594
|
-
|
|
111595
|
-
const
|
|
111596
|
-
|
|
111597
|
-
this.#
|
|
112496
|
+
ctx.phaseTimer = setTimeout(() => {
|
|
112497
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
112498
|
+
const inSigning = phase !== void 0 && _AggregationServiceRunner.#SIGNING_PHASES.includes(phase);
|
|
112499
|
+
if (this.#autoFallbackOnStall && inSigning && !ctx.committedPath && !ctx.settled) {
|
|
112500
|
+
this.triggerFallback(ctx.cohortId).catch((err) => this.#failCohort(ctx, err));
|
|
112501
|
+
return;
|
|
112502
|
+
}
|
|
112503
|
+
const reason = `Cohort ${ctx.cohortId} stalled in phase ${ctx.lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
|
|
112504
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
112505
|
+
this.#failCohort(ctx, new Error(reason));
|
|
111598
112506
|
}, this.#phaseTimeoutMs);
|
|
111599
112507
|
}
|
|
111600
|
-
/**
|
|
111601
|
-
|
|
111602
|
-
|
|
111603
|
-
|
|
111604
|
-
|
|
111605
|
-
|
|
111606
|
-
|
|
111607
|
-
|
|
112508
|
+
/**
|
|
112509
|
+
* Abandon the optimistic n-of-n key path for a cohort and collect k-of-n
|
|
112510
|
+
* fallback (script-path) signatures instead (ADR 042). Idempotent and safe
|
|
112511
|
+
* against the optimistic completion: it commits the cohort to the fallback
|
|
112512
|
+
* path synchronously (the `committedPath` latch) before sending anything, so a
|
|
112513
|
+
* late optimistic signature can no longer complete-and-broadcast a competing
|
|
112514
|
+
* spend of the same UTXO. No-op if the cohort is unknown, already settled, or
|
|
112515
|
+
* already committed to a path.
|
|
112516
|
+
*
|
|
112517
|
+
* Wired automatically to the phase-stall timer when `autoFallbackOnStall` is
|
|
112518
|
+
* set; otherwise call it from an operator decision (a UI "fall back now"
|
|
112519
|
+
* action). Throws only if the underlying state machine rejects the transition
|
|
112520
|
+
* (e.g. signing has not started).
|
|
112521
|
+
*/
|
|
112522
|
+
async triggerFallback(cohortId) {
|
|
112523
|
+
const ctx = this.#contexts.get(cohortId);
|
|
112524
|
+
if (!ctx || ctx.settled || ctx.committedPath) return;
|
|
112525
|
+
const messages2 = this.session.startFallbackSigning(cohortId);
|
|
112526
|
+
ctx.committedPath = "fallback";
|
|
112527
|
+
this.#stopAdvertRepeating(ctx);
|
|
112528
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112529
|
+
const sessionId = this.session.getSigningSessionId(cohortId) ?? "";
|
|
112530
|
+
this.emit("fallback-started", { cohortId, sessionId });
|
|
112531
|
+
await this.#sendAll(messages2);
|
|
112532
|
+
}
|
|
112533
|
+
/** Detect a phase change for a cohort since the last observation and reset its phase timer. */
|
|
112534
|
+
#onPhaseMaybeChanged(ctx) {
|
|
112535
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
112536
|
+
if (phase !== ctx.lastObservedPhase) {
|
|
112537
|
+
ctx.lastObservedPhase = phase;
|
|
112538
|
+
this.#resetPhaseTimer(ctx);
|
|
112539
|
+
}
|
|
112540
|
+
}
|
|
112541
|
+
/** Clear a cohort's timers. Called on completion, stop, and failure. */
|
|
112542
|
+
#clearTimers(ctx) {
|
|
112543
|
+
if (ctx.cohortTtlTimer) clearTimeout(ctx.cohortTtlTimer);
|
|
112544
|
+
if (ctx.phaseTimer) clearTimeout(ctx.phaseTimer);
|
|
112545
|
+
ctx.cohortTtlTimer = void 0;
|
|
112546
|
+
ctx.phaseTimer = void 0;
|
|
111608
112547
|
}
|
|
111609
|
-
/**
|
|
111610
|
-
|
|
111611
|
-
|
|
111612
|
-
|
|
111613
|
-
|
|
111614
|
-
|
|
112548
|
+
/**
|
|
112549
|
+
* Reclaim one cohort's runner-layer bookkeeping: stop its advert loop, clear
|
|
112550
|
+
* its timers, and drop its {@link RunContext}. Does NOT touch sibling cohorts
|
|
112551
|
+
* and does NOT detach the shared transport handlers. Leaves the cohort in the
|
|
112552
|
+
* state machine; whether that cohort's `session` state is also removed is the
|
|
112553
|
+
* caller's choice (see {@link #completeCohort} vs {@link #failCohort}).
|
|
112554
|
+
*/
|
|
112555
|
+
#disposeCohort(ctx) {
|
|
112556
|
+
this.#stopAdvertRepeating(ctx);
|
|
112557
|
+
this.#clearTimers(ctx);
|
|
112558
|
+
this.#contexts.delete(ctx.cohortId);
|
|
112559
|
+
}
|
|
112560
|
+
/**
|
|
112561
|
+
* Settle one cohort successfully. Reclaims the runner context but leaves the
|
|
112562
|
+
* completed cohort in `session` so callers can read its beaconAddress / cohort
|
|
112563
|
+
* via `session.getCohort(result.cohortId)`; reclaim it with
|
|
112564
|
+
* `session.removeCohort(cohortId)` when done. Idempotent via `ctx.settled`.
|
|
112565
|
+
*/
|
|
112566
|
+
#completeCohort(ctx, result) {
|
|
112567
|
+
if (ctx.settled) return;
|
|
112568
|
+
ctx.settled = true;
|
|
112569
|
+
this.#disposeCohort(ctx);
|
|
112570
|
+
this.emit("signing-complete", result);
|
|
112571
|
+
ctx.resolve(result);
|
|
112572
|
+
}
|
|
112573
|
+
/**
|
|
112574
|
+
* Fail one cohort. Reclaims its runner context, drops its now-dead state from
|
|
112575
|
+
* the state machine, and rejects only its completion; siblings keep running
|
|
112576
|
+
* and the shared transport handlers stay registered. Idempotent via
|
|
112577
|
+
* `ctx.settled`.
|
|
112578
|
+
*/
|
|
112579
|
+
#failCohort(ctx, err) {
|
|
112580
|
+
if (ctx.settled) return;
|
|
112581
|
+
ctx.settled = true;
|
|
112582
|
+
this.#disposeCohort(ctx);
|
|
112583
|
+
this.session.removeCohort(ctx.cohortId);
|
|
112584
|
+
this.emit("error", err);
|
|
112585
|
+
ctx.reject(err);
|
|
112586
|
+
}
|
|
112587
|
+
/**
|
|
112588
|
+
* Stop a single cohort early without affecting the rest of the runner. Drops
|
|
112589
|
+
* the cohort's state machine state; its `completion` promise rejects with a
|
|
112590
|
+
* stopped error.
|
|
112591
|
+
*/
|
|
112592
|
+
stopCohort(cohortId) {
|
|
112593
|
+
const ctx = this.#contexts.get(cohortId);
|
|
112594
|
+
if (!ctx || ctx.settled) return;
|
|
112595
|
+
ctx.settled = true;
|
|
112596
|
+
this.#disposeCohort(ctx);
|
|
112597
|
+
this.session.removeCohort(cohortId);
|
|
112598
|
+
ctx.reject(new AggregationServiceError(`Cohort ${cohortId} stopped.`, "COHORT_STOPPED", { cohortId }));
|
|
111615
112599
|
}
|
|
111616
112600
|
/**
|
|
111617
|
-
* Stop the runner
|
|
111618
|
-
* handlers so a restart or a new runner doesn't inherit
|
|
112601
|
+
* Stop the whole runner. Fails every outstanding cohort, then detaches the
|
|
112602
|
+
* shared transport handlers so a restart or a new runner doesn't inherit
|
|
112603
|
+
* stale dispatch. Safe to call repeatedly.
|
|
111619
112604
|
*/
|
|
111620
112605
|
stop() {
|
|
111621
112606
|
this.#stopped = true;
|
|
111622
|
-
this.#
|
|
111623
|
-
|
|
112607
|
+
for (const ctx of [...this.#contexts.values()]) {
|
|
112608
|
+
if (ctx.settled) continue;
|
|
112609
|
+
ctx.settled = true;
|
|
112610
|
+
this.#disposeCohort(ctx);
|
|
112611
|
+
this.session.removeCohort(ctx.cohortId);
|
|
112612
|
+
ctx.reject(new AggregationServiceError("Service runner stopped.", "RUNNER_STOPPED", { cohortId: ctx.cohortId }));
|
|
112613
|
+
}
|
|
112614
|
+
this.#contexts.clear();
|
|
111624
112615
|
this.#unregisterHandlers();
|
|
111625
|
-
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
111626
112616
|
}
|
|
111627
112617
|
/** Message types this runner listens for on the transport. */
|
|
111628
112618
|
static #HANDLED_MESSAGE_TYPES = [
|
|
111629
112619
|
COHORT_OPT_IN,
|
|
111630
112620
|
SUBMIT_UPDATE,
|
|
112621
|
+
SUBMIT_NONINCLUDED,
|
|
111631
112622
|
VALIDATION_ACK,
|
|
111632
112623
|
NONCE_CONTRIBUTION,
|
|
111633
|
-
SIGNATURE_AUTHORIZATION
|
|
112624
|
+
SIGNATURE_AUTHORIZATION,
|
|
112625
|
+
FALLBACK_SIGNATURE
|
|
111634
112626
|
];
|
|
111635
112627
|
/**
|
|
111636
|
-
* Internal: handler registration with the transport. Idempotent.
|
|
112628
|
+
* Internal: handler registration with the transport. Idempotent. Handlers
|
|
112629
|
+
* are DID-scoped and cohort-agnostic - one registration serves every cohort
|
|
112630
|
+
* this runner drives; demux to the right {@link RunContext} happens in each
|
|
112631
|
+
* handler via the inbound message's cohortId.
|
|
111637
112632
|
*/
|
|
111638
112633
|
#registerHandlers() {
|
|
111639
112634
|
if (this.#handlersRegistered) return;
|
|
111640
112635
|
this.#handlersRegistered = true;
|
|
111641
112636
|
this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN, this.#handleOptIn.bind(this));
|
|
111642
112637
|
this.#transport.registerMessageHandler(this.#did, SUBMIT_UPDATE, this.#handleSubmitUpdate.bind(this));
|
|
112638
|
+
this.#transport.registerMessageHandler(this.#did, SUBMIT_NONINCLUDED, this.#handleSubmitUpdate.bind(this));
|
|
111643
112639
|
this.#transport.registerMessageHandler(this.#did, VALIDATION_ACK, this.#handleValidationAck.bind(this));
|
|
111644
112640
|
this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
|
|
111645
112641
|
this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
|
|
112642
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_SIGNATURE, this.#handleFallbackSignature.bind(this));
|
|
111646
112643
|
}
|
|
111647
112644
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
111648
112645
|
#unregisterHandlers() {
|
|
@@ -111654,23 +112651,25 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111654
112651
|
}
|
|
111655
112652
|
/**
|
|
111656
112653
|
* Internal: message handlers for each protocol step. Each handler:
|
|
111657
|
-
* 1)
|
|
111658
|
-
* 2)
|
|
111659
|
-
* 3)
|
|
112654
|
+
* 1) resolves the cohort the message belongs to (by cohortId); ignores it if unknown
|
|
112655
|
+
* 2) feeds the message into the state machine via session.receive()
|
|
112656
|
+
* 3) emits a high-level event (carrying cohortId) for external observers
|
|
112657
|
+
* 4) checks if the new state triggers any automatic next steps, and if so:
|
|
111660
112658
|
* a) calls the appropriate decision callback(s)
|
|
111661
112659
|
* b) sends any resulting messages from the state machine
|
|
112660
|
+
* Errors fail only the owning cohort. A stopped runner ignores messages.
|
|
111662
112661
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
111663
112662
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
111664
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
111665
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
111666
112663
|
*/
|
|
111667
112664
|
async #handleOptIn(msg) {
|
|
111668
112665
|
if (this.#stopped) return;
|
|
112666
|
+
const ctx = this.#contextFor(msg);
|
|
112667
|
+
if (!ctx) return;
|
|
111669
112668
|
try {
|
|
111670
112669
|
this.session.receive(msg);
|
|
111671
|
-
this.#drainRejections();
|
|
111672
|
-
this.#onPhaseMaybeChanged();
|
|
111673
|
-
const optIn = this.session.pendingOptIns(
|
|
112670
|
+
this.#drainRejections(ctx);
|
|
112671
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112672
|
+
const optIn = this.session.pendingOptIns(ctx.cohortId).get(msg.from);
|
|
111674
112673
|
if (!optIn) return;
|
|
111675
112674
|
this.emit("opt-in-received", optIn);
|
|
111676
112675
|
if (optIn.communicationPk) {
|
|
@@ -111678,34 +112677,34 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111678
112677
|
}
|
|
111679
112678
|
const decision = await this.#onOptInReceived(optIn);
|
|
111680
112679
|
if (!decision.accepted) return;
|
|
111681
|
-
const maxParticipants =
|
|
111682
|
-
const cohortNow = this.session.getCohort(
|
|
112680
|
+
const maxParticipants = ctx.config.maxParticipants;
|
|
112681
|
+
const cohortNow = this.session.getCohort(ctx.cohortId);
|
|
111683
112682
|
if (maxParticipants !== void 0 && cohortNow && cohortNow.participants.length >= maxParticipants) {
|
|
111684
112683
|
return;
|
|
111685
112684
|
}
|
|
111686
|
-
await this.#sendAll(this.session.acceptParticipant(
|
|
111687
|
-
this.emit("participant-accepted", { participantDid: msg.from });
|
|
111688
|
-
const cohort = this.session.getCohort(
|
|
111689
|
-
if (cohort.participants.length >=
|
|
111690
|
-
|
|
112685
|
+
await this.#sendAll(this.session.acceptParticipant(ctx.cohortId, msg.from));
|
|
112686
|
+
this.emit("participant-accepted", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
112687
|
+
const cohort = this.session.getCohort(ctx.cohortId);
|
|
112688
|
+
if (cohort.participants.length >= ctx.config.minParticipants && !ctx.finalizing) {
|
|
112689
|
+
ctx.finalizing = true;
|
|
111691
112690
|
const finalizeDecision = await this.#onReadyToFinalize({
|
|
111692
112691
|
acceptedCount: cohort.participants.length,
|
|
111693
|
-
minRequired:
|
|
112692
|
+
minRequired: ctx.config.minParticipants
|
|
111694
112693
|
});
|
|
111695
112694
|
if (!finalizeDecision.finalize) {
|
|
111696
|
-
|
|
112695
|
+
ctx.finalizing = false;
|
|
111697
112696
|
return;
|
|
111698
112697
|
}
|
|
111699
|
-
const readyMsgs = this.session.finalizeKeygen(
|
|
111700
|
-
this.#stopAdvertRepeating();
|
|
112698
|
+
const readyMsgs = this.session.finalizeKeygen(ctx.cohortId);
|
|
112699
|
+
this.#stopAdvertRepeating(ctx);
|
|
111701
112700
|
this.emit("keygen-complete", {
|
|
111702
|
-
cohortId:
|
|
112701
|
+
cohortId: ctx.cohortId,
|
|
111703
112702
|
beaconAddress: cohort.beaconAddress
|
|
111704
112703
|
});
|
|
111705
112704
|
await this.#sendAll(readyMsgs);
|
|
111706
112705
|
}
|
|
111707
112706
|
} catch (err) {
|
|
111708
|
-
this.#
|
|
112707
|
+
this.#failCohort(ctx, err);
|
|
111709
112708
|
}
|
|
111710
112709
|
}
|
|
111711
112710
|
/**
|
|
@@ -111713,23 +112712,23 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111713
112712
|
* and distributes the data for validation.
|
|
111714
112713
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
111715
112714
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
111716
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
111717
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
111718
112715
|
*/
|
|
111719
112716
|
async #handleSubmitUpdate(msg) {
|
|
111720
112717
|
if (this.#stopped) return;
|
|
112718
|
+
const ctx = this.#contextFor(msg);
|
|
112719
|
+
if (!ctx) return;
|
|
111721
112720
|
try {
|
|
111722
112721
|
this.session.receive(msg);
|
|
111723
|
-
this.#drainRejections();
|
|
111724
|
-
this.#onPhaseMaybeChanged();
|
|
111725
|
-
this.emit("update-received", { participantDid: msg.from });
|
|
111726
|
-
if (this.session.getCohortPhase(
|
|
111727
|
-
const distributeMsgs = this.session.buildAndDistribute(
|
|
111728
|
-
this.emit("data-distributed", { cohortId:
|
|
112722
|
+
this.#drainRejections(ctx);
|
|
112723
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112724
|
+
this.emit("update-received", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
112725
|
+
if (this.session.getCohortPhase(ctx.cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
|
|
112726
|
+
const distributeMsgs = this.session.buildAndDistribute(ctx.cohortId);
|
|
112727
|
+
this.emit("data-distributed", { cohortId: ctx.cohortId });
|
|
111729
112728
|
await this.#sendAll(distributeMsgs);
|
|
111730
112729
|
}
|
|
111731
112730
|
} catch (err) {
|
|
111732
|
-
this.#
|
|
112731
|
+
this.#failCohort(ctx, err);
|
|
111733
112732
|
}
|
|
111734
112733
|
}
|
|
111735
112734
|
/**
|
|
@@ -111737,111 +112736,121 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
|
|
|
111737
112736
|
* automatically requests tx data and starts signing.
|
|
111738
112737
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
111739
112738
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
111740
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
111741
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
111742
112739
|
*/
|
|
111743
112740
|
async #handleValidationAck(msg) {
|
|
111744
112741
|
if (this.#stopped) return;
|
|
112742
|
+
const ctx = this.#contextFor(msg);
|
|
112743
|
+
if (!ctx) return;
|
|
111745
112744
|
try {
|
|
111746
112745
|
this.session.receive(msg);
|
|
111747
|
-
this.#drainRejections();
|
|
111748
|
-
this.#onPhaseMaybeChanged();
|
|
112746
|
+
this.#drainRejections(ctx);
|
|
112747
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
111749
112748
|
const approved = !!msg.body?.approved;
|
|
111750
|
-
this.emit("validation-received", { participantDid: msg.from, approved });
|
|
111751
|
-
const phase = this.session.getCohortPhase(
|
|
112749
|
+
this.emit("validation-received", { cohortId: ctx.cohortId, participantDid: msg.from, approved });
|
|
112750
|
+
const phase = this.session.getCohortPhase(ctx.cohortId);
|
|
111752
112751
|
if (phase === "Failed" /* Failed */) {
|
|
111753
112752
|
const reason = `Validation rejected by participant ${msg.from}`;
|
|
111754
|
-
this.emit("cohort-failed", { cohortId:
|
|
111755
|
-
this.#
|
|
112753
|
+
this.emit("cohort-failed", { cohortId: ctx.cohortId, reason });
|
|
112754
|
+
this.#failCohort(ctx, new Error(reason));
|
|
111756
112755
|
return;
|
|
111757
112756
|
}
|
|
111758
112757
|
if (phase === "Validated" /* Validated */) {
|
|
111759
|
-
const cohort = this.session.getCohort(
|
|
112758
|
+
const cohort = this.session.getCohort(ctx.cohortId);
|
|
111760
112759
|
const txData = await this.#onProvideTxData({
|
|
111761
|
-
cohortId:
|
|
112760
|
+
cohortId: ctx.cohortId,
|
|
111762
112761
|
beaconAddress: cohort.beaconAddress,
|
|
111763
112762
|
signalBytes: cohort.signalBytes
|
|
111764
112763
|
});
|
|
111765
|
-
const authMsgs = this.session.startSigning(
|
|
111766
|
-
const sessionId = this.session.getSigningSessionId(
|
|
111767
|
-
this.emit("signing-started", { sessionId });
|
|
112764
|
+
const authMsgs = this.session.startSigning(ctx.cohortId, txData);
|
|
112765
|
+
const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
|
|
112766
|
+
this.emit("signing-started", { cohortId: ctx.cohortId, sessionId });
|
|
111768
112767
|
await this.#sendAll(authMsgs);
|
|
111769
112768
|
}
|
|
111770
112769
|
} catch (err) {
|
|
111771
|
-
this.#
|
|
112770
|
+
this.#failCohort(ctx, err);
|
|
111772
112771
|
}
|
|
111773
112772
|
}
|
|
111774
112773
|
/**
|
|
111775
|
-
* Handler for receiving nonce contributions
|
|
111776
|
-
*
|
|
112774
|
+
* Handler for receiving nonce contributions. When all nonces are received, sends the aggregated
|
|
112775
|
+
* nonce back to the cohort.
|
|
111777
112776
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
111778
112777
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
111779
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
111780
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
111781
112778
|
*/
|
|
111782
112779
|
async #handleNonceContribution(msg) {
|
|
111783
112780
|
if (this.#stopped) return;
|
|
112781
|
+
const ctx = this.#contextFor(msg);
|
|
112782
|
+
if (!ctx) return;
|
|
111784
112783
|
try {
|
|
111785
112784
|
this.session.receive(msg);
|
|
111786
|
-
this.#drainRejections();
|
|
111787
|
-
this.#onPhaseMaybeChanged();
|
|
111788
|
-
this.emit("nonce-received", { participantDid: msg.from });
|
|
111789
|
-
if (this.session.getCohortPhase(
|
|
111790
|
-
await this.#sendAll(this.session.sendAggregatedNonce(
|
|
112785
|
+
this.#drainRejections(ctx);
|
|
112786
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112787
|
+
this.emit("nonce-received", { cohortId: ctx.cohortId, participantDid: msg.from });
|
|
112788
|
+
if (this.session.getCohortPhase(ctx.cohortId) === "NoncesCollected" /* NoncesCollected */) {
|
|
112789
|
+
await this.#sendAll(this.session.sendAggregatedNonce(ctx.cohortId));
|
|
111791
112790
|
}
|
|
111792
112791
|
} catch (err) {
|
|
111793
|
-
this.#
|
|
112792
|
+
this.#failCohort(ctx, err);
|
|
111794
112793
|
}
|
|
111795
112794
|
}
|
|
111796
112795
|
/**
|
|
111797
112796
|
* Handler for receiving signature authorizations. When all partial signatures are received, the
|
|
111798
|
-
* session automatically completes
|
|
112797
|
+
* session automatically completes; the final result is emitted and the cohort's completion
|
|
112798
|
+
* promise resolves.
|
|
111799
112799
|
* @param {BaseMessage} msg - The incoming message to handle.
|
|
111800
112800
|
* @returns {Promise<void>} Resolves when handling is complete.
|
|
111801
|
-
* @throws {Error} If any step of handling fails, the error is emitted and the run promise is rejected.
|
|
111802
|
-
* Note: if the runner has been stopped, handlers will ignore incoming messages.
|
|
111803
112801
|
*/
|
|
111804
112802
|
async #handleSignatureAuthorization(msg) {
|
|
111805
112803
|
if (this.#stopped) return;
|
|
112804
|
+
const ctx = this.#contextFor(msg);
|
|
112805
|
+
if (!ctx) return;
|
|
112806
|
+
try {
|
|
112807
|
+
this.session.receive(msg);
|
|
112808
|
+
this.#drainRejections(ctx);
|
|
112809
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112810
|
+
if (ctx.committedPath === "fallback") return;
|
|
112811
|
+
const result = this.session.getResult(ctx.cohortId);
|
|
112812
|
+
if (result) {
|
|
112813
|
+
ctx.committedPath = "optimistic";
|
|
112814
|
+
this.#completeCohort(ctx, result);
|
|
112815
|
+
}
|
|
112816
|
+
} catch (err) {
|
|
112817
|
+
this.#failCohort(ctx, err);
|
|
112818
|
+
}
|
|
112819
|
+
}
|
|
112820
|
+
/**
|
|
112821
|
+
* Handler for receiving fallback (k-of-n script-path) signatures. The state
|
|
112822
|
+
* machine assembles and finalizes the fallback spend once k valid signatures
|
|
112823
|
+
* are in; the result is then emitted and the cohort's completion resolves. The
|
|
112824
|
+
* cohort is already committed to the fallback path (via {@link triggerFallback}).
|
|
112825
|
+
* @param {BaseMessage} msg - The incoming FALLBACK_SIGNATURE message.
|
|
112826
|
+
* @returns {Promise<void>} Resolves when handling is complete.
|
|
112827
|
+
*/
|
|
112828
|
+
async #handleFallbackSignature(msg) {
|
|
112829
|
+
if (this.#stopped) return;
|
|
112830
|
+
const ctx = this.#contextFor(msg);
|
|
112831
|
+
if (!ctx) return;
|
|
111806
112832
|
try {
|
|
111807
112833
|
this.session.receive(msg);
|
|
111808
|
-
this.#drainRejections();
|
|
111809
|
-
this.#onPhaseMaybeChanged();
|
|
111810
|
-
const result = this.session.getResult(
|
|
112834
|
+
this.#drainRejections(ctx);
|
|
112835
|
+
this.#onPhaseMaybeChanged(ctx);
|
|
112836
|
+
const result = this.session.getResult(ctx.cohortId);
|
|
111811
112837
|
if (result) {
|
|
111812
|
-
this.#
|
|
111813
|
-
this.#unregisterHandlers();
|
|
111814
|
-
this.emit("signing-complete", result);
|
|
111815
|
-
this.#resolveRun?.(result);
|
|
112838
|
+
this.#completeCohort(ctx, result);
|
|
111816
112839
|
}
|
|
111817
112840
|
} catch (err) {
|
|
111818
|
-
this.#
|
|
112841
|
+
this.#failCohort(ctx, err);
|
|
111819
112842
|
}
|
|
111820
112843
|
}
|
|
111821
112844
|
/**
|
|
111822
112845
|
* Internal: helper to send all messages sequentially. Catches and propagates errors.
|
|
111823
112846
|
* @param {BaseMessage[]} msgs - The messages to send.
|
|
111824
112847
|
* @returns {Promise<void>} Resolves when all messages have been sent.
|
|
111825
|
-
* @throws {Error} If sending any message fails, the error is emitted and the run promise is
|
|
111826
|
-
* rejected.
|
|
111827
112848
|
*/
|
|
111828
112849
|
async #sendAll(msgs) {
|
|
111829
112850
|
for (const m2 of msgs) {
|
|
111830
112851
|
await this.#transport.sendMessage(m2, this.#did, m2.to);
|
|
111831
112852
|
}
|
|
111832
112853
|
}
|
|
111833
|
-
/**
|
|
111834
|
-
* Internal: helper to handle errors. Emits an 'error' event and rejects the run promise.
|
|
111835
|
-
* @param {Error} err - The error to handle.
|
|
111836
|
-
*/
|
|
111837
|
-
#fail(err) {
|
|
111838
|
-
this.#stopAdvertRepeating();
|
|
111839
|
-
this.#clearTimers();
|
|
111840
|
-
this.#unregisterHandlers();
|
|
111841
|
-
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
111842
|
-
this.emit("error", err);
|
|
111843
|
-
this.#rejectRun?.(err);
|
|
111844
|
-
}
|
|
111845
112854
|
};
|
|
111846
112855
|
|
|
111847
112856
|
// src/core/aggregation/runner/participant-runner.ts
|
|
@@ -111890,7 +112899,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
111890
112899
|
COHORT_READY,
|
|
111891
112900
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
111892
112901
|
AUTHORIZATION_REQUEST,
|
|
111893
|
-
AGGREGATED_NONCE
|
|
112902
|
+
AGGREGATED_NONCE,
|
|
112903
|
+
FALLBACK_AUTHORIZATION_REQUEST
|
|
111894
112904
|
];
|
|
111895
112905
|
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
111896
112906
|
#unregisterHandlers() {
|
|
@@ -111902,7 +112912,8 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
111902
112912
|
}
|
|
111903
112913
|
/**
|
|
111904
112914
|
* Single-shot helper: start, join the first cohort that passes `shouldJoin`,
|
|
111905
|
-
* drive it to completion, and resolve. Convenient for tests and demos.
|
|
112915
|
+
* drive it to completion, and resolve. Convenient for tests and demos. The
|
|
112916
|
+
* single-cohort special case of {@link joinMatching} (count = 1).
|
|
111906
112917
|
*/
|
|
111907
112918
|
static async joinFirst(options2) {
|
|
111908
112919
|
return new Promise((resolve, reject) => {
|
|
@@ -111915,6 +112926,37 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
111915
112926
|
runner.start().catch(reject);
|
|
111916
112927
|
});
|
|
111917
112928
|
}
|
|
112929
|
+
/**
|
|
112930
|
+
* Multi-cohort helper: start, join EVERY cohort whose advert passes
|
|
112931
|
+
* `shouldJoin`, drive each to completion in parallel, and resolve once
|
|
112932
|
+
* `count` cohorts have completed (the runner stops at that point). The
|
|
112933
|
+
* N-cohort generalization of {@link joinFirst}, for a participant that joins
|
|
112934
|
+
* several cohorts advertised by one service.
|
|
112935
|
+
*
|
|
112936
|
+
* For an open-ended, long-lived subscriber (no fixed count), construct an
|
|
112937
|
+
* {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
|
|
112938
|
+
* `start()`, and listen for `cohort-complete` - the runner already drives
|
|
112939
|
+
* any number of cohorts concurrently.
|
|
112940
|
+
*
|
|
112941
|
+
* @param options Participant runner options (set `shouldJoin` to select cohorts).
|
|
112942
|
+
* @param count Number of completed cohorts to collect before resolving.
|
|
112943
|
+
* @returns The {@link CohortCompleteInfo} for each completed cohort, in completion order.
|
|
112944
|
+
*/
|
|
112945
|
+
static async joinMatching(options2, count) {
|
|
112946
|
+
return new Promise((resolve, reject) => {
|
|
112947
|
+
const runner = new _AggregationParticipantRunner(options2);
|
|
112948
|
+
const completed = [];
|
|
112949
|
+
runner.on("cohort-complete", (info) => {
|
|
112950
|
+
completed.push(info);
|
|
112951
|
+
if (completed.length >= count) {
|
|
112952
|
+
runner.stop();
|
|
112953
|
+
resolve(completed);
|
|
112954
|
+
}
|
|
112955
|
+
});
|
|
112956
|
+
runner.on("error", reject);
|
|
112957
|
+
runner.start().catch(reject);
|
|
112958
|
+
});
|
|
112959
|
+
}
|
|
111918
112960
|
/**
|
|
111919
112961
|
* Internal: handler registration with the transport. Idempotent and safe to call multiple times,
|
|
111920
112962
|
* but only registers handlers once.
|
|
@@ -111928,6 +112970,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
111928
112970
|
this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
|
|
111929
112971
|
this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
|
|
111930
112972
|
this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
|
|
112973
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
|
|
111931
112974
|
}
|
|
111932
112975
|
/**
|
|
111933
112976
|
* Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
|
|
@@ -111983,8 +113026,13 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
111983
113026
|
cohortId,
|
|
111984
113027
|
beaconAddress: info.beaconAddress
|
|
111985
113028
|
});
|
|
111986
|
-
|
|
111987
|
-
|
|
113029
|
+
if (signedUpdate === null) {
|
|
113030
|
+
await this.#sendAll(this.session.declineUpdate(cohortId));
|
|
113031
|
+
this.emit("update-declined", { cohortId });
|
|
113032
|
+
} else {
|
|
113033
|
+
await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
|
|
113034
|
+
this.emit("update-submitted", { cohortId });
|
|
113035
|
+
}
|
|
111988
113036
|
} catch (err) {
|
|
111989
113037
|
this.emit("error", err);
|
|
111990
113038
|
}
|
|
@@ -112060,23 +113108,62 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
112060
113108
|
const cohortId = msg.body?.cohortId;
|
|
112061
113109
|
if (!cohortId) return;
|
|
112062
113110
|
await this.#sendAll(this.session.generatePartialSignature(cohortId));
|
|
112063
|
-
|
|
112064
|
-
|
|
112065
|
-
|
|
112066
|
-
|
|
112067
|
-
|
|
112068
|
-
|
|
112069
|
-
|
|
112070
|
-
|
|
112071
|
-
|
|
112072
|
-
|
|
112073
|
-
|
|
112074
|
-
|
|
113111
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
113112
|
+
} catch (err) {
|
|
113113
|
+
this.emit("error", err);
|
|
113114
|
+
}
|
|
113115
|
+
}
|
|
113116
|
+
/**
|
|
113117
|
+
* Internal: handler for fallback authorization requests. The service abandoned
|
|
113118
|
+
* the optimistic key path; the member authorizes the k-of-n script-path spend
|
|
113119
|
+
* of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
|
|
113120
|
+
* the decision, signs the fallback, and completes once it has contributed (the
|
|
113121
|
+
* service needs only k members).
|
|
113122
|
+
* @param {BaseMessage} msg - The received fallback authorization request message.
|
|
113123
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
113124
|
+
*/
|
|
113125
|
+
async #handleFallbackAuthorizationRequest(msg) {
|
|
113126
|
+
if (this.#stopped) return;
|
|
113127
|
+
try {
|
|
113128
|
+
this.session.receive(msg);
|
|
113129
|
+
const cohortId = msg.body?.cohortId;
|
|
113130
|
+
if (!cohortId) return;
|
|
113131
|
+
const req = this.session.pendingFallbackRequests.get(cohortId);
|
|
113132
|
+
if (!req) return;
|
|
113133
|
+
this.emit("fallback-requested", req);
|
|
113134
|
+
const decision = await this.#onApproveSigning(req);
|
|
113135
|
+
if (!decision.approved) {
|
|
113136
|
+
this.emit("cohort-failed", { cohortId, reason: "Fallback signing rejected by participant" });
|
|
113137
|
+
return;
|
|
112075
113138
|
}
|
|
113139
|
+
await this.#sendAll(this.session.approveFallback(cohortId));
|
|
113140
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
112076
113141
|
} catch (err) {
|
|
112077
113142
|
this.emit("error", err);
|
|
112078
113143
|
}
|
|
112079
113144
|
}
|
|
113145
|
+
/**
|
|
113146
|
+
* Internal: emit `cohort-complete` with the participant's retained sidecar once
|
|
113147
|
+
* the cohort has reached the Complete phase. Surfaces the CAS Announcement map
|
|
113148
|
+
* (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
|
|
113149
|
+
* DID resolution. Read via getValidation (not pendingValidations, which lists
|
|
113150
|
+
* only the AwaitingValidation phase) so the sidecar is still available at
|
|
113151
|
+
* Complete. Shared by the optimistic and fallback completion paths.
|
|
113152
|
+
*/
|
|
113153
|
+
#emitCohortCompleteIfDone(cohortId) {
|
|
113154
|
+
if (this.session.getCohortPhase(cohortId) !== "Complete" /* Complete */) return;
|
|
113155
|
+
const info = this.session.joinedCohorts.get(cohortId);
|
|
113156
|
+
if (!info) return;
|
|
113157
|
+
const validation = this.session.getValidation(cohortId);
|
|
113158
|
+
this.emit("cohort-complete", {
|
|
113159
|
+
cohortId,
|
|
113160
|
+
beaconAddress: info.beaconAddress,
|
|
113161
|
+
beaconType: validation?.beaconType ?? "",
|
|
113162
|
+
included: validation?.included ?? true,
|
|
113163
|
+
casAnnouncement: validation?.casAnnouncement,
|
|
113164
|
+
smtProof: validation?.smtProof
|
|
113165
|
+
});
|
|
113166
|
+
}
|
|
112080
113167
|
/**
|
|
112081
113168
|
* Internal: send helper to ensure messages are sent sequentially. This is important for protocol
|
|
112082
113169
|
* correctness, as some transports may not guarantee message order if sent in parallel.
|
|
@@ -112093,6 +113180,7 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
112093
113180
|
|
|
112094
113181
|
// src/core/aggregation/runner/aggregation-runner.ts
|
|
112095
113182
|
init_shim();
|
|
113183
|
+
init_utils();
|
|
112096
113184
|
var AggregationRunner = class {
|
|
112097
113185
|
/**
|
|
112098
113186
|
* Run a cohort of ONE participant entirely in-process and return the
|
|
@@ -112100,8 +113188,8 @@ var AggregationRunner = class {
|
|
|
112100
113188
|
*
|
|
112101
113189
|
* One party plays both the coordinating service and the lone participant,
|
|
112102
113190
|
* connected over an {@link InMemoryTransport} (no relay or HTTP server). This
|
|
112103
|
-
* makes the single-participant aggregate-beacon path
|
|
112104
|
-
* two-axis beacon matrix
|
|
113191
|
+
* makes the single-participant aggregate-beacon path (the N=1 corner of the
|
|
113192
|
+
* two-axis beacon matrix, see ADR 037) first-class, useful for generating
|
|
112105
113193
|
* and reproducing single-participant aggregate test vectors.
|
|
112106
113194
|
*
|
|
112107
113195
|
* The service advertises a cohort with `minParticipants: 1`; the participant
|
|
@@ -112119,11 +113207,13 @@ var AggregationRunner = class {
|
|
|
112119
113207
|
transport.registerPeer(options2.participant.did, options2.participant.keys.publicKey.compressed);
|
|
112120
113208
|
transport.registerPeer(options2.service.did, options2.service.keys.publicKey.compressed);
|
|
112121
113209
|
transport.start();
|
|
113210
|
+
const recoveryKey = options2.config.recoveryKey ?? bytesToHex(options2.service.keys.publicKey.compressed.slice(1));
|
|
113211
|
+
const recoverySequence = options2.config.recoverySequence ?? DEFAULT_RECOVERY_SEQUENCE;
|
|
112122
113212
|
const service = new AggregationServiceRunner({
|
|
112123
113213
|
transport,
|
|
112124
113214
|
did: options2.service.did,
|
|
112125
113215
|
keys: options2.service.keys,
|
|
112126
|
-
config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType },
|
|
113216
|
+
config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
|
|
112127
113217
|
onProvideTxData: options2.onProvideTxData,
|
|
112128
113218
|
cohortTtlMs: options2.cohortTtlMs,
|
|
112129
113219
|
phaseTimeoutMs: options2.phaseTimeoutMs,
|
|
@@ -112253,8 +113343,7 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
|
112253
113343
|
async function buildAggregationBeaconTx(opts) {
|
|
112254
113344
|
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
112255
113345
|
const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
|
|
112256
|
-
const
|
|
112257
|
-
const witnessScript = tapOut.script;
|
|
113346
|
+
const witnessScript = OutScript.encode(Address(opts.network).decode(opts.beaconAddress));
|
|
112258
113347
|
const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
|
|
112259
113348
|
if (BigInt(utxo.value) <= feeSats) {
|
|
112260
113349
|
throw new BeaconError(
|
|
@@ -112333,7 +113422,7 @@ var SinglePartyBeacon = class {
|
|
|
112333
113422
|
*
|
|
112334
113423
|
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
112335
113424
|
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
112336
|
-
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead
|
|
113425
|
+
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead:
|
|
112337
113426
|
* the multi-party path can't share the signing phase, but the tx-construction
|
|
112338
113427
|
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
112339
113428
|
*
|
|
@@ -112365,7 +113454,7 @@ var SinglePartyBeacon = class {
|
|
|
112365
113454
|
*
|
|
112366
113455
|
* Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
|
|
112367
113456
|
* the input accordingly. Validates that the signer's pubkey produces the beacon
|
|
112368
|
-
* address under that script kind
|
|
113457
|
+
* address under that script kind: without this check, a misconfigured caller
|
|
112369
113458
|
* would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
|
|
112370
113459
|
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
|
|
112371
113460
|
* round-trip.
|
|
@@ -128252,7 +129341,7 @@ var Updater = class _Updater {
|
|
|
128252
129341
|
#verificationMethod;
|
|
128253
129342
|
#beaconService;
|
|
128254
129343
|
/**
|
|
128255
|
-
* @internal
|
|
129344
|
+
* @internal Use {@link DidBtcr2.update} to create instances.
|
|
128256
129345
|
*/
|
|
128257
129346
|
constructor(params) {
|
|
128258
129347
|
this.#sourceDocument = params.sourceDocument;
|
|
@@ -128289,7 +129378,7 @@ var Updater = class _Updater {
|
|
|
128289
129378
|
const targetDocument = JSONPatch.apply(sourceDocument, patches);
|
|
128290
129379
|
if (targetDocument.id !== sourceDocument.id) {
|
|
128291
129380
|
throw new UpdateError(
|
|
128292
|
-
`Patches must not change the DID document id (source "${sourceDocument.id}"
|
|
129381
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" to target "${targetDocument.id}").`,
|
|
128293
129382
|
INVALID_DID_UPDATE,
|
|
128294
129383
|
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
128295
129384
|
);
|
|
@@ -128373,8 +129462,8 @@ var Updater = class _Updater {
|
|
|
128373
129462
|
}
|
|
128374
129463
|
/**
|
|
128375
129464
|
* Advance the state machine. Returns either:
|
|
128376
|
-
* - `{ status: 'action-required', needs }`
|
|
128377
|
-
* - `{ status: 'complete', result }`
|
|
129465
|
+
* - `{ status: 'action-required', needs }` caller must provide data via {@link provide}
|
|
129466
|
+
* - `{ status: 'complete', result }` update is signed and broadcast
|
|
128378
129467
|
*/
|
|
128379
129468
|
advance() {
|
|
128380
129469
|
while (true) {
|
|
@@ -128387,7 +129476,7 @@ var Updater = class _Updater {
|
|
|
128387
129476
|
continue;
|
|
128388
129477
|
}
|
|
128389
129478
|
// Phase: Sign
|
|
128390
|
-
// Emit NeedSigningKey
|
|
129479
|
+
// Emit NeedSigningKey: the caller supplies the secret key (or a KMS signature).
|
|
128391
129480
|
case "Sign": {
|
|
128392
129481
|
return {
|
|
128393
129482
|
status: "action-required",
|
|
@@ -128570,8 +129659,8 @@ var DidBtcr2 = class {
|
|
|
128570
129659
|
*
|
|
128571
129660
|
* Factory method that validates the update parameters and returns a sans-I/O
|
|
128572
129661
|
* {@link Updater} state machine. The caller drives the updater through its
|
|
128573
|
-
* phases (Construct
|
|
128574
|
-
* `provide()`. The method package performs **zero I/O
|
|
129662
|
+
* phases (Construct -> Sign -> Broadcast -> Complete) by calling `advance()` and
|
|
129663
|
+
* `provide()`. The method package performs **zero I/O**: signing key retrieval
|
|
128575
129664
|
* (or KMS delegation) and the on-chain broadcast are the caller's responsibility.
|
|
128576
129665
|
*
|
|
128577
129666
|
* For a fully-wired version with Bitcoin broadcast and key handling, see
|
|
@@ -128689,7 +129778,7 @@ var Resolver = class _Resolver {
|
|
|
128689
129778
|
#unsortedUpdates = [];
|
|
128690
129779
|
#resolvedResponse = null;
|
|
128691
129780
|
/**
|
|
128692
|
-
* @internal
|
|
129781
|
+
* @internal Use {@link DidBtcr2.resolve} to create instances.
|
|
128693
129782
|
*/
|
|
128694
129783
|
constructor(didComponents, sidecarData, currentDocument, options2) {
|
|
128695
129784
|
this.#didComponents = didComponents;
|
|
@@ -128925,8 +130014,8 @@ var Resolver = class _Resolver {
|
|
|
128925
130014
|
}
|
|
128926
130015
|
/**
|
|
128927
130016
|
* Advance the state machine. Returns either:
|
|
128928
|
-
* - `{ status: 'action-required', needs }`
|
|
128929
|
-
* - `{ status: 'resolved', result }`
|
|
130017
|
+
* - `{ status: 'action-required', needs }` - caller must provide data via {@link provide}
|
|
130018
|
+
* - `{ status: 'resolved', result }` - resolution complete
|
|
128930
130019
|
*
|
|
128931
130020
|
* Analogous to Rust's `Resolver::resolve()`.
|
|
128932
130021
|
*/
|
|
@@ -129166,9 +130255,11 @@ export {
|
|
|
129166
130255
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
129167
130256
|
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
129168
130257
|
DEFAULT_CLOCK_SKEW_SEC,
|
|
130258
|
+
DEFAULT_FUNDING_MODEL,
|
|
129169
130259
|
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
129170
130260
|
DEFAULT_NONCE_LEN_BYTES,
|
|
129171
130261
|
DEFAULT_NOSTR_RELAYS,
|
|
130262
|
+
DEFAULT_RECOVERY_SEQUENCE,
|
|
129172
130263
|
DID_REGEX,
|
|
129173
130264
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
129174
130265
|
DidBtcr2,
|
|
@@ -129177,6 +130268,8 @@ export {
|
|
|
129177
130268
|
DidDocumentBuilder,
|
|
129178
130269
|
DidVerificationMethod,
|
|
129179
130270
|
Document,
|
|
130271
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
130272
|
+
FALLBACK_SIGNATURE,
|
|
129180
130273
|
GenesisDocument,
|
|
129181
130274
|
HTTP_ENVELOPE_VERSION,
|
|
129182
130275
|
HTTP_ROUTE,
|
|
@@ -129190,7 +130283,9 @@ export {
|
|
|
129190
130283
|
InMemoryTransport,
|
|
129191
130284
|
InboxBuffer,
|
|
129192
130285
|
KNOWN_BEACON_TYPES,
|
|
130286
|
+
KNOWN_FUNDING_MODELS,
|
|
129193
130287
|
KeyPairAggregationSigner,
|
|
130288
|
+
MAX_RECOVERY_SEQUENCE,
|
|
129194
130289
|
NONCE_CONTRIBUTION,
|
|
129195
130290
|
NonceCache,
|
|
129196
130291
|
NostrTransport,
|
|
@@ -129207,6 +130302,7 @@ export {
|
|
|
129207
130302
|
SMTBeacon,
|
|
129208
130303
|
SMTBeaconError,
|
|
129209
130304
|
SSE_EVENT,
|
|
130305
|
+
SUBMIT_NONINCLUDED,
|
|
129210
130306
|
SUBMIT_UPDATE,
|
|
129211
130307
|
ServiceCohortPhase,
|
|
129212
130308
|
SigningSessionError,
|
|
@@ -129215,6 +130311,7 @@ export {
|
|
|
129215
130311
|
SingletonBeacon,
|
|
129216
130312
|
SingletonBeaconError,
|
|
129217
130313
|
StaticFeeEstimator,
|
|
130314
|
+
TAPROOT_LEAF_VERSION,
|
|
129218
130315
|
TransportAdapterError,
|
|
129219
130316
|
TransportError,
|
|
129220
130317
|
TransportFactory,
|
|
@@ -129222,6 +130319,11 @@ export {
|
|
|
129222
130319
|
Updater,
|
|
129223
130320
|
VALIDATION_ACK,
|
|
129224
130321
|
buildAggregationBeaconTx,
|
|
130322
|
+
buildFallbackLeaf,
|
|
130323
|
+
buildFallbackSpend,
|
|
130324
|
+
buildRecoveryLeaves,
|
|
130325
|
+
buildRecoveryScript,
|
|
130326
|
+
buildRecoverySpend,
|
|
129225
130327
|
buildRequestAuth,
|
|
129226
130328
|
createAggregatedNonceMessage,
|
|
129227
130329
|
createAuthorizationRequestMessage,
|
|
@@ -129230,13 +130332,17 @@ export {
|
|
|
129230
130332
|
createCohortOptInMessage,
|
|
129231
130333
|
createCohortReadyMessage,
|
|
129232
130334
|
createDistributeAggregatedDataMessage,
|
|
130335
|
+
createFallbackAuthorizationRequestMessage,
|
|
130336
|
+
createFallbackSignatureMessage,
|
|
129233
130337
|
createNonceContributionMessage,
|
|
129234
130338
|
createSignatureAuthorizationMessage,
|
|
130339
|
+
createSubmitNonIncludedMessage,
|
|
129235
130340
|
createSubmitUpdateMessage,
|
|
129236
130341
|
createValidationAckMessage,
|
|
129237
130342
|
defaultReconnectBackoff,
|
|
129238
130343
|
deriveSingletonAddress,
|
|
129239
130344
|
detectSingletonScriptKind,
|
|
130345
|
+
fallbackSighash,
|
|
129240
130346
|
formatSseComment,
|
|
129241
130347
|
formatSseEvent,
|
|
129242
130348
|
getBeaconStrategy,
|
|
@@ -129248,10 +130354,13 @@ export {
|
|
|
129248
130354
|
isCohortOptInMessage,
|
|
129249
130355
|
isCohortReadyMessage,
|
|
129250
130356
|
isDistributeAggregatedDataMessage,
|
|
130357
|
+
isFallbackAuthorizationRequestMessage,
|
|
130358
|
+
isFallbackSignatureMessage,
|
|
129251
130359
|
isKeygenMessageType,
|
|
129252
130360
|
isNonceContributionMessage,
|
|
129253
130361
|
isSignMessageType,
|
|
129254
130362
|
isSignatureAuthorizationMessage,
|
|
130363
|
+
isSubmitNonIncludedMessage,
|
|
129255
130364
|
isSubmitUpdateMessage,
|
|
129256
130365
|
isUpdateMessageType,
|
|
129257
130366
|
isValidationAckMessage,
|
|
@@ -129260,8 +130369,10 @@ export {
|
|
|
129260
130369
|
parseRequestAuth,
|
|
129261
130370
|
parseSseStream,
|
|
129262
130371
|
registerBeaconStrategy,
|
|
130372
|
+
resolveFallbackThreshold,
|
|
129263
130373
|
reviveFromWire,
|
|
129264
130374
|
signEnvelope,
|
|
130375
|
+
tapLeafHash2 as tapLeafHash,
|
|
129265
130376
|
validateCohortConditions,
|
|
129266
130377
|
verifyEnvelope,
|
|
129267
130378
|
verifyRequestAuth
|