@did-btcr2/aggregation 0.1.0 → 0.3.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 +18107 -18096
- package/dist/browser.mjs +18107 -18096
- package/dist/cjs/core/index.js +2423 -0
- package/dist/cjs/index.js +3513 -3521
- package/dist/cjs/participant/index.js +2425 -0
- package/dist/cjs/service/index.js +3199 -0
- package/dist/esm/{runner/aggregation-runner.js → aggregation-runner.js} +4 -4
- package/dist/esm/aggregation-runner.js.map +1 -0
- package/dist/esm/core/beacon-strategy.js.map +1 -0
- package/dist/esm/core/cohort.js.map +1 -0
- package/dist/esm/core/conditions.js.map +1 -0
- package/dist/esm/core/errors.js.map +1 -0
- package/dist/esm/core/fallback-spend.js.map +1 -0
- package/dist/esm/core/index.js +23 -0
- package/dist/esm/core/index.js.map +1 -0
- package/dist/esm/core/logger.js.map +1 -0
- package/dist/esm/core/messages/base.js.map +1 -0
- package/dist/esm/core/messages/bodies.js.map +1 -0
- package/dist/esm/core/messages/constants.js.map +1 -0
- package/dist/esm/core/messages/factories.js.map +1 -0
- package/dist/esm/core/messages/guards.js.map +1 -0
- package/dist/esm/core/messages/index.js.map +1 -0
- package/dist/esm/core/phases.js.map +1 -0
- package/dist/esm/core/recovery-policy.js.map +1 -0
- package/dist/esm/core/recovery-spend.js.map +1 -0
- package/dist/esm/core/signer.js.map +1 -0
- package/dist/esm/core/signing-session.js.map +1 -0
- package/dist/esm/core/transport/didcomm.js.map +1 -0
- package/dist/esm/core/transport/error.js.map +1 -0
- package/dist/esm/core/transport/http/envelope.js.map +1 -0
- package/dist/esm/core/transport/http/errors.js.map +1 -0
- package/dist/esm/core/transport/http/index.js +9 -0
- package/dist/esm/core/transport/http/index.js.map +1 -0
- package/dist/esm/core/transport/http/protocol.js.map +1 -0
- package/dist/esm/core/transport/http/request-auth.js.map +1 -0
- package/dist/esm/core/transport/in-memory.js.map +1 -0
- package/dist/esm/core/transport/index.js +12 -0
- package/dist/esm/core/transport/index.js.map +1 -0
- package/dist/esm/core/transport/nostr.js.map +1 -0
- package/dist/esm/core/transport/transport.js.map +1 -0
- package/dist/esm/core/typed-emitter.js.map +1 -0
- package/dist/esm/index.js +13 -20
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/participant/events.js.map +1 -0
- package/dist/esm/{transport/http/client.js → participant/http-client.js} +6 -6
- package/dist/esm/participant/http-client.js.map +1 -0
- package/dist/esm/participant/index.js +13 -0
- package/dist/esm/participant/index.js.map +1 -0
- package/dist/esm/{runner → participant}/participant-runner.js +5 -5
- package/dist/esm/participant/participant-runner.js.map +1 -0
- package/dist/esm/{participant.js → participant/participant.js} +11 -11
- package/dist/esm/participant/participant.js.map +1 -0
- package/dist/esm/participant/sse-stream.js.map +1 -0
- package/dist/esm/service/events.js +2 -0
- package/dist/esm/{runner → service}/events.js.map +1 -1
- package/dist/esm/{transport/http/server.js → service/http-server.js} +6 -6
- package/dist/esm/service/http-server.js.map +1 -0
- package/dist/esm/service/inbox-buffer.js.map +1 -0
- package/dist/esm/service/index.js +16 -0
- package/dist/esm/service/index.js.map +1 -0
- package/dist/esm/service/nonce-cache.js.map +1 -0
- package/dist/esm/service/rate-limiter.js.map +1 -0
- package/dist/esm/{runner → service}/service-runner.js +5 -5
- package/dist/esm/service/service-runner.js.map +1 -0
- package/dist/esm/{service.js → service/service.js} +12 -12
- package/dist/esm/service/service.js.map +1 -0
- package/dist/esm/service/sse-writer.js.map +1 -0
- package/dist/esm/{transport/factory.js → transport-factory.js} +5 -5
- package/dist/esm/transport-factory.js.map +1 -0
- package/dist/types/{runner/aggregation-runner.d.ts → aggregation-runner.d.ts} +3 -3
- package/dist/types/aggregation-runner.d.ts.map +1 -0
- package/dist/types/{beacon-strategy.d.ts → core/beacon-strategy.d.ts} +2 -2
- package/dist/types/core/beacon-strategy.d.ts.map +1 -0
- package/dist/types/{cohort.d.ts → core/cohort.d.ts} +3 -3
- package/dist/types/core/cohort.d.ts.map +1 -0
- package/dist/types/core/conditions.d.ts.map +1 -0
- package/dist/types/core/errors.d.ts.map +1 -0
- package/dist/types/core/fallback-spend.d.ts.map +1 -0
- package/dist/types/core/index.d.ts +15 -0
- package/dist/types/core/index.d.ts.map +1 -0
- package/dist/types/core/logger.d.ts.map +1 -0
- package/dist/types/core/messages/base.d.ts.map +1 -0
- package/dist/types/core/messages/bodies.d.ts.map +1 -0
- package/dist/types/core/messages/constants.d.ts.map +1 -0
- package/dist/types/core/messages/factories.d.ts.map +1 -0
- package/dist/types/core/messages/guards.d.ts.map +1 -0
- package/dist/types/core/messages/index.d.ts.map +1 -0
- package/dist/types/core/phases.d.ts.map +1 -0
- package/dist/types/core/recovery-policy.d.ts.map +1 -0
- package/dist/types/core/recovery-spend.d.ts.map +1 -0
- package/dist/types/core/signer.d.ts.map +1 -0
- package/dist/types/core/signing-session.d.ts.map +1 -0
- package/dist/types/core/transport/didcomm.d.ts.map +1 -0
- package/dist/types/core/transport/error.d.ts.map +1 -0
- package/dist/types/core/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/core/transport/http/errors.d.ts.map +1 -0
- package/dist/types/core/transport/http/index.d.ts +5 -0
- package/dist/types/core/transport/http/index.d.ts.map +1 -0
- package/dist/types/core/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/core/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/core/transport/in-memory.d.ts.map +1 -0
- package/{src/transport/index.ts → dist/types/core/transport/index.d.ts} +1 -1
- package/dist/types/core/transport/index.d.ts.map +1 -0
- package/dist/types/core/transport/nostr.d.ts.map +1 -0
- package/dist/types/core/transport/transport.d.ts.map +1 -0
- package/dist/types/core/typed-emitter.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -16
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/{runner → participant}/events.d.ts +1 -76
- package/dist/types/participant/events.d.ts.map +1 -0
- package/dist/types/{transport/http/client.d.ts → participant/http-client.d.ts} +4 -4
- package/dist/types/participant/http-client.d.ts.map +1 -0
- package/dist/types/participant/index.d.ts +6 -0
- package/dist/types/participant/index.d.ts.map +1 -0
- package/dist/types/{runner → participant}/participant-runner.d.ts +6 -6
- package/dist/types/participant/participant-runner.d.ts.map +1 -0
- package/dist/types/{participant.d.ts → participant/participant.d.ts} +6 -6
- package/dist/types/participant/participant.d.ts.map +1 -0
- package/dist/types/participant/sse-stream.d.ts.map +1 -0
- package/dist/types/service/events.d.ts +76 -0
- package/dist/types/service/events.d.ts.map +1 -0
- package/dist/types/{transport/http/server.d.ts → service/http-server.d.ts} +4 -4
- package/dist/types/{transport/http/server.d.ts.map → service/http-server.d.ts.map} +1 -1
- package/dist/types/service/inbox-buffer.d.ts.map +1 -0
- package/dist/types/service/index.d.ts +9 -0
- package/dist/types/service/index.d.ts.map +1 -0
- package/dist/types/service/nonce-cache.d.ts.map +1 -0
- package/dist/types/service/rate-limiter.d.ts.map +1 -0
- package/dist/types/{runner → service}/service-runner.d.ts +4 -4
- package/dist/types/service/service-runner.d.ts.map +1 -0
- package/dist/types/{service.d.ts → service/service.d.ts} +6 -6
- package/dist/types/service/service.d.ts.map +1 -0
- package/dist/types/service/sse-writer.d.ts.map +1 -0
- package/dist/types/{transport/factory.d.ts → transport-factory.d.ts} +5 -5
- package/dist/types/transport-factory.d.ts.map +1 -0
- package/package.json +23 -6
- package/src/{runner/aggregation-runner.ts → aggregation-runner.ts} +7 -7
- package/src/{beacon-strategy.ts → core/beacon-strategy.ts} +2 -2
- package/src/{cohort.ts → core/cohort.ts} +3 -3
- package/src/core/index.ts +22 -0
- package/src/core/transport/http/index.ts +8 -0
- package/src/core/transport/index.ts +11 -0
- package/src/index.ts +13 -23
- package/src/{runner → participant}/events.ts +1 -59
- package/src/{transport/http/client.ts → participant/http-client.ts} +8 -8
- package/src/participant/index.ts +12 -0
- package/src/{runner → participant}/participant-runner.ts +10 -10
- package/src/{participant.ts → participant/participant.ts} +18 -18
- package/src/service/events.ts +58 -0
- package/src/{transport/http/server.ts → service/http-server.ts} +8 -8
- package/src/service/index.ts +15 -0
- package/src/{runner → service}/service-runner.ts +9 -9
- package/src/{service.ts → service/service.ts} +19 -19
- package/src/{transport/factory.ts → transport-factory.ts} +8 -8
- package/dist/esm/beacon-strategy.js.map +0 -1
- package/dist/esm/cohort.js.map +0 -1
- package/dist/esm/conditions.js.map +0 -1
- package/dist/esm/errors.js.map +0 -1
- package/dist/esm/fallback-spend.js.map +0 -1
- package/dist/esm/logger.js.map +0 -1
- package/dist/esm/messages/base.js.map +0 -1
- package/dist/esm/messages/bodies.js.map +0 -1
- package/dist/esm/messages/constants.js.map +0 -1
- package/dist/esm/messages/factories.js.map +0 -1
- package/dist/esm/messages/guards.js.map +0 -1
- package/dist/esm/messages/index.js.map +0 -1
- package/dist/esm/participant.js.map +0 -1
- package/dist/esm/phases.js.map +0 -1
- package/dist/esm/recovery-policy.js.map +0 -1
- package/dist/esm/recovery-spend.js.map +0 -1
- package/dist/esm/runner/aggregation-runner.js.map +0 -1
- package/dist/esm/runner/index.js +0 -6
- package/dist/esm/runner/index.js.map +0 -1
- package/dist/esm/runner/participant-runner.js.map +0 -1
- package/dist/esm/runner/service-runner.js.map +0 -1
- package/dist/esm/runner/typed-emitter.js.map +0 -1
- package/dist/esm/service.js.map +0 -1
- package/dist/esm/signer.js.map +0 -1
- package/dist/esm/signing-session.js.map +0 -1
- package/dist/esm/transport/didcomm.js.map +0 -1
- package/dist/esm/transport/error.js.map +0 -1
- package/dist/esm/transport/factory.js.map +0 -1
- package/dist/esm/transport/http/client.js.map +0 -1
- package/dist/esm/transport/http/envelope.js.map +0 -1
- package/dist/esm/transport/http/errors.js.map +0 -1
- package/dist/esm/transport/http/inbox-buffer.js.map +0 -1
- package/dist/esm/transport/http/index.js +0 -12
- package/dist/esm/transport/http/index.js.map +0 -1
- package/dist/esm/transport/http/nonce-cache.js.map +0 -1
- package/dist/esm/transport/http/protocol.js.map +0 -1
- package/dist/esm/transport/http/rate-limiter.js.map +0 -1
- package/dist/esm/transport/http/request-auth.js.map +0 -1
- package/dist/esm/transport/http/server.js.map +0 -1
- package/dist/esm/transport/http/sse-stream.js.map +0 -1
- package/dist/esm/transport/http/sse-writer.js.map +0 -1
- package/dist/esm/transport/in-memory.js.map +0 -1
- package/dist/esm/transport/index.js +0 -8
- package/dist/esm/transport/index.js.map +0 -1
- package/dist/esm/transport/nostr.js.map +0 -1
- package/dist/esm/transport/transport.js.map +0 -1
- package/dist/types/beacon-strategy.d.ts.map +0 -1
- package/dist/types/cohort.d.ts.map +0 -1
- package/dist/types/conditions.d.ts.map +0 -1
- package/dist/types/errors.d.ts.map +0 -1
- package/dist/types/fallback-spend.d.ts.map +0 -1
- package/dist/types/logger.d.ts.map +0 -1
- package/dist/types/messages/base.d.ts.map +0 -1
- package/dist/types/messages/bodies.d.ts.map +0 -1
- package/dist/types/messages/constants.d.ts.map +0 -1
- package/dist/types/messages/factories.d.ts.map +0 -1
- package/dist/types/messages/guards.d.ts.map +0 -1
- package/dist/types/messages/index.d.ts.map +0 -1
- package/dist/types/participant.d.ts.map +0 -1
- package/dist/types/phases.d.ts.map +0 -1
- package/dist/types/recovery-policy.d.ts.map +0 -1
- package/dist/types/recovery-spend.d.ts.map +0 -1
- package/dist/types/runner/aggregation-runner.d.ts.map +0 -1
- package/dist/types/runner/events.d.ts.map +0 -1
- package/dist/types/runner/index.d.ts +0 -6
- package/dist/types/runner/index.d.ts.map +0 -1
- package/dist/types/runner/participant-runner.d.ts.map +0 -1
- package/dist/types/runner/service-runner.d.ts.map +0 -1
- package/dist/types/runner/typed-emitter.d.ts.map +0 -1
- package/dist/types/service.d.ts.map +0 -1
- package/dist/types/signer.d.ts.map +0 -1
- package/dist/types/signing-session.d.ts.map +0 -1
- package/dist/types/transport/didcomm.d.ts.map +0 -1
- package/dist/types/transport/error.d.ts.map +0 -1
- package/dist/types/transport/factory.d.ts.map +0 -1
- package/dist/types/transport/http/client.d.ts.map +0 -1
- package/dist/types/transport/http/envelope.d.ts.map +0 -1
- package/dist/types/transport/http/errors.d.ts.map +0 -1
- package/dist/types/transport/http/inbox-buffer.d.ts.map +0 -1
- package/dist/types/transport/http/index.d.ts +0 -12
- package/dist/types/transport/http/index.d.ts.map +0 -1
- package/dist/types/transport/http/nonce-cache.d.ts.map +0 -1
- package/dist/types/transport/http/protocol.d.ts.map +0 -1
- package/dist/types/transport/http/rate-limiter.d.ts.map +0 -1
- package/dist/types/transport/http/request-auth.d.ts.map +0 -1
- package/dist/types/transport/http/sse-stream.d.ts.map +0 -1
- package/dist/types/transport/http/sse-writer.d.ts.map +0 -1
- package/dist/types/transport/in-memory.d.ts.map +0 -1
- package/dist/types/transport/index.d.ts +0 -8
- package/dist/types/transport/index.d.ts.map +0 -1
- package/dist/types/transport/nostr.d.ts.map +0 -1
- package/dist/types/transport/transport.d.ts.map +0 -1
- package/src/runner/index.ts +0 -5
- package/src/transport/http/index.ts +0 -11
- /package/dist/esm/{beacon-strategy.js → core/beacon-strategy.js} +0 -0
- /package/dist/esm/{cohort.js → core/cohort.js} +0 -0
- /package/dist/esm/{conditions.js → core/conditions.js} +0 -0
- /package/dist/esm/{errors.js → core/errors.js} +0 -0
- /package/dist/esm/{fallback-spend.js → core/fallback-spend.js} +0 -0
- /package/dist/esm/{logger.js → core/logger.js} +0 -0
- /package/dist/esm/{messages → core/messages}/base.js +0 -0
- /package/dist/esm/{messages → core/messages}/bodies.js +0 -0
- /package/dist/esm/{messages → core/messages}/constants.js +0 -0
- /package/dist/esm/{messages → core/messages}/factories.js +0 -0
- /package/dist/esm/{messages → core/messages}/guards.js +0 -0
- /package/dist/esm/{messages → core/messages}/index.js +0 -0
- /package/dist/esm/{phases.js → core/phases.js} +0 -0
- /package/dist/esm/{recovery-policy.js → core/recovery-policy.js} +0 -0
- /package/dist/esm/{recovery-spend.js → core/recovery-spend.js} +0 -0
- /package/dist/esm/{signer.js → core/signer.js} +0 -0
- /package/dist/esm/{signing-session.js → core/signing-session.js} +0 -0
- /package/dist/esm/{transport → core/transport}/didcomm.js +0 -0
- /package/dist/esm/{transport → core/transport}/error.js +0 -0
- /package/dist/esm/{transport → core/transport}/http/envelope.js +0 -0
- /package/dist/esm/{transport → core/transport}/http/errors.js +0 -0
- /package/dist/esm/{transport → core/transport}/http/protocol.js +0 -0
- /package/dist/esm/{transport → core/transport}/http/request-auth.js +0 -0
- /package/dist/esm/{transport → core/transport}/in-memory.js +0 -0
- /package/dist/esm/{transport → core/transport}/nostr.js +0 -0
- /package/dist/esm/{transport → core/transport}/transport.js +0 -0
- /package/dist/esm/{runner → core}/typed-emitter.js +0 -0
- /package/dist/esm/{runner → participant}/events.js +0 -0
- /package/dist/esm/{transport/http → participant}/sse-stream.js +0 -0
- /package/dist/esm/{transport/http → service}/inbox-buffer.js +0 -0
- /package/dist/esm/{transport/http → service}/nonce-cache.js +0 -0
- /package/dist/esm/{transport/http → service}/rate-limiter.js +0 -0
- /package/dist/esm/{transport/http → service}/sse-writer.js +0 -0
- /package/dist/types/{conditions.d.ts → core/conditions.d.ts} +0 -0
- /package/dist/types/{errors.d.ts → core/errors.d.ts} +0 -0
- /package/dist/types/{fallback-spend.d.ts → core/fallback-spend.d.ts} +0 -0
- /package/dist/types/{logger.d.ts → core/logger.d.ts} +0 -0
- /package/dist/types/{messages → core/messages}/base.d.ts +0 -0
- /package/dist/types/{messages → core/messages}/bodies.d.ts +0 -0
- /package/dist/types/{messages → core/messages}/constants.d.ts +0 -0
- /package/dist/types/{messages → core/messages}/factories.d.ts +0 -0
- /package/dist/types/{messages → core/messages}/guards.d.ts +0 -0
- /package/dist/types/{messages → core/messages}/index.d.ts +0 -0
- /package/dist/types/{phases.d.ts → core/phases.d.ts} +0 -0
- /package/dist/types/{recovery-policy.d.ts → core/recovery-policy.d.ts} +0 -0
- /package/dist/types/{recovery-spend.d.ts → core/recovery-spend.d.ts} +0 -0
- /package/dist/types/{signer.d.ts → core/signer.d.ts} +0 -0
- /package/dist/types/{signing-session.d.ts → core/signing-session.d.ts} +0 -0
- /package/dist/types/{transport → core/transport}/didcomm.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/error.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/http/envelope.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/http/errors.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/http/protocol.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/http/request-auth.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/in-memory.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/nostr.d.ts +0 -0
- /package/dist/types/{transport → core/transport}/transport.d.ts +0 -0
- /package/dist/types/{runner → core}/typed-emitter.d.ts +0 -0
- /package/dist/types/{transport/http → participant}/sse-stream.d.ts +0 -0
- /package/dist/types/{transport/http → service}/inbox-buffer.d.ts +0 -0
- /package/dist/types/{transport/http → service}/nonce-cache.d.ts +0 -0
- /package/dist/types/{transport/http → service}/rate-limiter.d.ts +0 -0
- /package/dist/types/{transport/http → service}/sse-writer.d.ts +0 -0
- /package/src/{conditions.ts → core/conditions.ts} +0 -0
- /package/src/{errors.ts → core/errors.ts} +0 -0
- /package/src/{fallback-spend.ts → core/fallback-spend.ts} +0 -0
- /package/src/{logger.ts → core/logger.ts} +0 -0
- /package/src/{messages → core/messages}/base.ts +0 -0
- /package/src/{messages → core/messages}/bodies.ts +0 -0
- /package/src/{messages → core/messages}/constants.ts +0 -0
- /package/src/{messages → core/messages}/factories.ts +0 -0
- /package/src/{messages → core/messages}/guards.ts +0 -0
- /package/src/{messages → core/messages}/index.ts +0 -0
- /package/src/{phases.ts → core/phases.ts} +0 -0
- /package/src/{recovery-policy.ts → core/recovery-policy.ts} +0 -0
- /package/src/{recovery-spend.ts → core/recovery-spend.ts} +0 -0
- /package/src/{signer.ts → core/signer.ts} +0 -0
- /package/src/{signing-session.ts → core/signing-session.ts} +0 -0
- /package/src/{transport → core/transport}/didcomm.ts +0 -0
- /package/src/{transport → core/transport}/error.ts +0 -0
- /package/src/{transport → core/transport}/http/envelope.ts +0 -0
- /package/src/{transport → core/transport}/http/errors.ts +0 -0
- /package/src/{transport → core/transport}/http/protocol.ts +0 -0
- /package/src/{transport → core/transport}/http/request-auth.ts +0 -0
- /package/src/{transport → core/transport}/in-memory.ts +0 -0
- /package/src/{transport → core/transport}/nostr.ts +0 -0
- /package/src/{transport → core/transport}/transport.ts +0 -0
- /package/src/{runner → core}/typed-emitter.ts +0 -0
- /package/src/{transport/http → participant}/sse-stream.ts +0 -0
- /package/src/{transport/http → service}/inbox-buffer.ts +0 -0
- /package/src/{transport/http → service}/nonce-cache.ts +0 -0
- /package/src/{transport/http → service}/rate-limiter.ts +0 -0
- /package/src/{transport/http → service}/sse-writer.ts +0 -0
|
@@ -0,0 +1,2423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/core/index.ts
|
|
31
|
+
var core_exports = {};
|
|
32
|
+
__export(core_exports, {
|
|
33
|
+
AGGREGATED_NONCE: () => AGGREGATED_NONCE,
|
|
34
|
+
AGGREGATION_MESSAGE_PREFIX: () => AGGREGATION_MESSAGE_PREFIX,
|
|
35
|
+
AGGREGATION_WIRE_VERSION: () => AGGREGATION_WIRE_VERSION,
|
|
36
|
+
AUTHORIZATION_REQUEST: () => AUTHORIZATION_REQUEST,
|
|
37
|
+
AggregationCohort: () => AggregationCohort,
|
|
38
|
+
AggregationCohortError: () => AggregationCohortError,
|
|
39
|
+
AggregationParticipantError: () => AggregationParticipantError,
|
|
40
|
+
AggregationServiceError: () => AggregationServiceError,
|
|
41
|
+
BaseMessage: () => BaseMessage,
|
|
42
|
+
BeaconSigningSession: () => BeaconSigningSession,
|
|
43
|
+
COHORT_ADVERT: () => COHORT_ADVERT,
|
|
44
|
+
COHORT_OPT_IN: () => COHORT_OPT_IN,
|
|
45
|
+
COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
|
|
46
|
+
COHORT_READY: () => COHORT_READY,
|
|
47
|
+
CONSOLE_LOGGER: () => CONSOLE_LOGGER,
|
|
48
|
+
DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
49
|
+
DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
|
|
50
|
+
DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
|
|
51
|
+
DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
|
|
52
|
+
DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
|
|
53
|
+
DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
|
|
54
|
+
DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
|
|
55
|
+
DidCommTransport: () => DidCommTransport,
|
|
56
|
+
FALLBACK_AUTHORIZATION_REQUEST: () => FALLBACK_AUTHORIZATION_REQUEST,
|
|
57
|
+
FALLBACK_SIGNATURE: () => FALLBACK_SIGNATURE,
|
|
58
|
+
HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
|
|
59
|
+
HTTP_ROUTE: () => HTTP_ROUTE,
|
|
60
|
+
HttpTransportError: () => HttpTransportError,
|
|
61
|
+
InMemoryBus: () => InMemoryBus,
|
|
62
|
+
InMemoryTransport: () => InMemoryTransport,
|
|
63
|
+
KNOWN_BEACON_TYPES: () => KNOWN_BEACON_TYPES,
|
|
64
|
+
KNOWN_FUNDING_MODELS: () => KNOWN_FUNDING_MODELS,
|
|
65
|
+
KeyPairAggregationSigner: () => KeyPairAggregationSigner,
|
|
66
|
+
MAX_RECOVERY_SEQUENCE: () => MAX_RECOVERY_SEQUENCE,
|
|
67
|
+
NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
|
|
68
|
+
NostrTransport: () => NostrTransport,
|
|
69
|
+
ParticipantCohortPhase: () => ParticipantCohortPhase,
|
|
70
|
+
REQUEST_AUTH_SCHEME: () => REQUEST_AUTH_SCHEME,
|
|
71
|
+
SIGNATURE_AUTHORIZATION: () => SIGNATURE_AUTHORIZATION,
|
|
72
|
+
SILENT_LOGGER: () => SILENT_LOGGER,
|
|
73
|
+
SSE_EVENT: () => SSE_EVENT,
|
|
74
|
+
SUBMIT_NONINCLUDED: () => SUBMIT_NONINCLUDED,
|
|
75
|
+
SUBMIT_UPDATE: () => SUBMIT_UPDATE,
|
|
76
|
+
ServiceCohortPhase: () => ServiceCohortPhase,
|
|
77
|
+
SigningSessionError: () => SigningSessionError,
|
|
78
|
+
SigningSessionPhase: () => SigningSessionPhase,
|
|
79
|
+
TAPROOT_LEAF_VERSION: () => TAPROOT_LEAF_VERSION,
|
|
80
|
+
TransportAdapterError: () => TransportAdapterError,
|
|
81
|
+
TransportError: () => TransportError,
|
|
82
|
+
TypedEventEmitter: () => TypedEventEmitter,
|
|
83
|
+
VALIDATION_ACK: () => VALIDATION_ACK,
|
|
84
|
+
buildFallbackLeaf: () => buildFallbackLeaf,
|
|
85
|
+
buildFallbackSpend: () => buildFallbackSpend,
|
|
86
|
+
buildRecoveryLeaves: () => buildRecoveryLeaves,
|
|
87
|
+
buildRecoveryScript: () => buildRecoveryScript,
|
|
88
|
+
buildRecoverySpend: () => buildRecoverySpend,
|
|
89
|
+
buildRequestAuth: () => buildRequestAuth,
|
|
90
|
+
createAggregatedNonceMessage: () => createAggregatedNonceMessage,
|
|
91
|
+
createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
|
|
92
|
+
createCohortAdvertMessage: () => createCohortAdvertMessage,
|
|
93
|
+
createCohortOptInAcceptMessage: () => createCohortOptInAcceptMessage,
|
|
94
|
+
createCohortOptInMessage: () => createCohortOptInMessage,
|
|
95
|
+
createCohortReadyMessage: () => createCohortReadyMessage,
|
|
96
|
+
createDistributeAggregatedDataMessage: () => createDistributeAggregatedDataMessage,
|
|
97
|
+
createFallbackAuthorizationRequestMessage: () => createFallbackAuthorizationRequestMessage,
|
|
98
|
+
createFallbackSignatureMessage: () => createFallbackSignatureMessage,
|
|
99
|
+
createNonceContributionMessage: () => createNonceContributionMessage,
|
|
100
|
+
createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
|
|
101
|
+
createSubmitNonIncludedMessage: () => createSubmitNonIncludedMessage,
|
|
102
|
+
createSubmitUpdateMessage: () => createSubmitUpdateMessage,
|
|
103
|
+
createValidationAckMessage: () => createValidationAckMessage,
|
|
104
|
+
fallbackSighash: () => fallbackSighash,
|
|
105
|
+
getBeaconStrategy: () => getBeaconStrategy,
|
|
106
|
+
isAggregatedNonceMessage: () => isAggregatedNonceMessage,
|
|
107
|
+
isAggregationMessageType: () => isAggregationMessageType,
|
|
108
|
+
isAuthorizationRequestMessage: () => isAuthorizationRequestMessage,
|
|
109
|
+
isCohortAdvertMessage: () => isCohortAdvertMessage,
|
|
110
|
+
isCohortOptInAcceptMessage: () => isCohortOptInAcceptMessage,
|
|
111
|
+
isCohortOptInMessage: () => isCohortOptInMessage,
|
|
112
|
+
isCohortReadyMessage: () => isCohortReadyMessage,
|
|
113
|
+
isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
|
|
114
|
+
isFallbackAuthorizationRequestMessage: () => isFallbackAuthorizationRequestMessage,
|
|
115
|
+
isFallbackSignatureMessage: () => isFallbackSignatureMessage,
|
|
116
|
+
isKeygenMessageType: () => isKeygenMessageType,
|
|
117
|
+
isNonceContributionMessage: () => isNonceContributionMessage,
|
|
118
|
+
isSignMessageType: () => isSignMessageType,
|
|
119
|
+
isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
|
|
120
|
+
isSubmitNonIncludedMessage: () => isSubmitNonIncludedMessage,
|
|
121
|
+
isSubmitUpdateMessage: () => isSubmitUpdateMessage,
|
|
122
|
+
isUpdateMessageType: () => isUpdateMessageType,
|
|
123
|
+
isValidationAckMessage: () => isValidationAckMessage,
|
|
124
|
+
normalizeForWire: () => normalizeForWire,
|
|
125
|
+
parseRequestAuth: () => parseRequestAuth,
|
|
126
|
+
registerBeaconStrategy: () => registerBeaconStrategy,
|
|
127
|
+
resolveFallbackThreshold: () => resolveFallbackThreshold,
|
|
128
|
+
reviveFromWire: () => reviveFromWire,
|
|
129
|
+
signEnvelope: () => signEnvelope,
|
|
130
|
+
tapLeafHash: () => tapLeafHash,
|
|
131
|
+
validateCohortConditions: () => validateCohortConditions,
|
|
132
|
+
verifyEnvelope: () => verifyEnvelope,
|
|
133
|
+
verifyRequestAuth: () => verifyRequestAuth
|
|
134
|
+
});
|
|
135
|
+
module.exports = __toCommonJS(core_exports);
|
|
136
|
+
|
|
137
|
+
// src/core/errors.ts
|
|
138
|
+
var import_common = require("@did-btcr2/common");
|
|
139
|
+
var AggregationServiceError = class extends import_common.MethodError {
|
|
140
|
+
constructor(message, type = "AggregationServiceError", data) {
|
|
141
|
+
super(message, type, data);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var AggregationParticipantError = class extends import_common.MethodError {
|
|
145
|
+
constructor(message, type = "AggregationParticipantError", data) {
|
|
146
|
+
super(message, type, data);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var AggregationCohortError = class extends import_common.MethodError {
|
|
150
|
+
constructor(message, type = "AggregationCohortError", data) {
|
|
151
|
+
super(message, type, data);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var SigningSessionError = class extends import_common.MethodError {
|
|
155
|
+
constructor(message, type = "SigningSessionError", data) {
|
|
156
|
+
super(message, type, data);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/core/logger.ts
|
|
161
|
+
var CONSOLE_LOGGER = {
|
|
162
|
+
debug: (msg, ...args) => console.debug(msg, ...args),
|
|
163
|
+
info: (msg, ...args) => console.info(msg, ...args),
|
|
164
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
165
|
+
error: (msg, ...args) => console.error(msg, ...args)
|
|
166
|
+
};
|
|
167
|
+
var SILENT_LOGGER = {
|
|
168
|
+
debug: () => {
|
|
169
|
+
},
|
|
170
|
+
info: () => {
|
|
171
|
+
},
|
|
172
|
+
warn: () => {
|
|
173
|
+
},
|
|
174
|
+
error: () => {
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/core/recovery-policy.ts
|
|
179
|
+
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
180
|
+
var import_utils = require("@noble/hashes/utils");
|
|
181
|
+
var import_btc_signer = require("@scure/btc-signer");
|
|
182
|
+
var import_musig2 = require("@scure/btc-signer/musig2");
|
|
183
|
+
var DEFAULT_FUNDING_MODEL = "operator-funded";
|
|
184
|
+
var DEFAULT_RECOVERY_SEQUENCE = 144;
|
|
185
|
+
var MAX_RECOVERY_SEQUENCE = 65535;
|
|
186
|
+
var TAPROOT_LEAF_VERSION = 192;
|
|
187
|
+
function compactSize(n) {
|
|
188
|
+
if (n < 253) return new Uint8Array([n]);
|
|
189
|
+
if (n <= 65535) return new Uint8Array([253, n & 255, n >> 8 & 255]);
|
|
190
|
+
return new Uint8Array([254, n & 255, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255]);
|
|
191
|
+
}
|
|
192
|
+
function tapLeafHash(script, leafVersion = TAPROOT_LEAF_VERSION) {
|
|
193
|
+
return import_secp256k1.schnorr.utils.taggedHash(
|
|
194
|
+
"TapLeaf",
|
|
195
|
+
(0, import_utils.concatBytes)(new Uint8Array([leafVersion]), compactSize(script.length), script)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
function resolveFallbackThreshold(advertised, n) {
|
|
199
|
+
return advertised ?? Math.max(1, n - 1);
|
|
200
|
+
}
|
|
201
|
+
function assertFallbackParams({ cohortKeys, fallbackThreshold }) {
|
|
202
|
+
const n = cohortKeys.length;
|
|
203
|
+
if (n === 0) {
|
|
204
|
+
throw new AggregationCohortError(
|
|
205
|
+
"Cannot build fallback leaf: no cohort keys.",
|
|
206
|
+
"NO_COHORT_KEYS"
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
for (const key of cohortKeys) {
|
|
210
|
+
if (key.length !== 33) {
|
|
211
|
+
throw new AggregationCohortError(
|
|
212
|
+
`Cohort key must be a 33-byte compressed public key, got ${key.length} bytes.`,
|
|
213
|
+
"INVALID_COHORT_KEY",
|
|
214
|
+
{ length: key.length }
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!Number.isInteger(fallbackThreshold) || fallbackThreshold < 1 || fallbackThreshold > n) {
|
|
219
|
+
throw new AggregationCohortError(
|
|
220
|
+
`Fallback threshold must be an integer in [1, ${n}] (k-of-n), got ${fallbackThreshold}.`,
|
|
221
|
+
"INVALID_FALLBACK_THRESHOLD",
|
|
222
|
+
{ fallbackThreshold, n }
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function buildFallbackLeaf(params) {
|
|
227
|
+
assertFallbackParams(params);
|
|
228
|
+
const xOnlyKeys = (0, import_musig2.sortKeys)(params.cohortKeys).map((k) => k.slice(1));
|
|
229
|
+
return (0, import_btc_signer.p2tr_ms)(params.fallbackThreshold, xOnlyKeys).script;
|
|
230
|
+
}
|
|
231
|
+
function assertRecoveryParams({ recoveryKey, recoverySequence }) {
|
|
232
|
+
if (recoveryKey.length !== 32) {
|
|
233
|
+
throw new AggregationCohortError(
|
|
234
|
+
`Recovery key must be a 32-byte x-only public key, got ${recoveryKey.length} bytes.`,
|
|
235
|
+
"INVALID_RECOVERY_KEY"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (!Number.isInteger(recoverySequence) || recoverySequence < 1 || recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
239
|
+
throw new AggregationCohortError(
|
|
240
|
+
`Recovery sequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}], got ${recoverySequence}.`,
|
|
241
|
+
"INVALID_RECOVERY_SEQUENCE"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function buildRecoveryScript(params) {
|
|
246
|
+
assertRecoveryParams(params);
|
|
247
|
+
return import_btc_signer.Script.encode([
|
|
248
|
+
params.recoverySequence,
|
|
249
|
+
"CHECKSEQUENCEVERIFY",
|
|
250
|
+
"DROP",
|
|
251
|
+
params.recoveryKey,
|
|
252
|
+
"CHECKSIG"
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
function buildRecoveryLeaves(fundingModel, params) {
|
|
256
|
+
switch (fundingModel) {
|
|
257
|
+
case "operator-funded":
|
|
258
|
+
return [
|
|
259
|
+
{ script: buildFallbackLeaf(params) },
|
|
260
|
+
{ script: buildRecoveryScript(params) }
|
|
261
|
+
];
|
|
262
|
+
case "participant-funded":
|
|
263
|
+
throw new AggregationCohortError(
|
|
264
|
+
"Funding model 'participant-funded' is reserved and not yet implemented.",
|
|
265
|
+
"UNSUPPORTED_FUNDING_MODEL",
|
|
266
|
+
{ fundingModel }
|
|
267
|
+
);
|
|
268
|
+
default:
|
|
269
|
+
throw new AggregationCohortError(
|
|
270
|
+
`Unknown funding model: ${fundingModel}.`,
|
|
271
|
+
"UNKNOWN_FUNDING_MODEL",
|
|
272
|
+
{ fundingModel }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/core/conditions.ts
|
|
278
|
+
var KNOWN_BEACON_TYPES = ["CASBeacon", "SMTBeacon"];
|
|
279
|
+
var KNOWN_FUNDING_MODELS = ["operator-funded", "participant-funded"];
|
|
280
|
+
function checkPair(problems, label, min, max) {
|
|
281
|
+
if (min !== void 0 && (!Number.isInteger(min) || min < 0)) {
|
|
282
|
+
problems.push(`min${label} must be an integer >= 0`);
|
|
283
|
+
}
|
|
284
|
+
if (max !== void 0 && (!Number.isInteger(max) || max < 0)) {
|
|
285
|
+
problems.push(`max${label} must be an integer >= 0`);
|
|
286
|
+
}
|
|
287
|
+
if (min !== void 0 && max !== void 0 && Number.isInteger(min) && Number.isInteger(max) && max < min) {
|
|
288
|
+
problems.push(`max${label} must be >= min${label}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function checkCost(problems, label, cost) {
|
|
292
|
+
if (cost === void 0) return;
|
|
293
|
+
if (typeof cost.amount !== "number" || !Number.isFinite(cost.amount) || cost.amount < 0) {
|
|
294
|
+
problems.push(`${label}.amount must be a finite number >= 0`);
|
|
295
|
+
}
|
|
296
|
+
if (typeof cost.unit !== "string" || cost.unit.length === 0) {
|
|
297
|
+
problems.push(`${label}.unit must be a non-empty string`);
|
|
298
|
+
}
|
|
299
|
+
if (cost.basis !== void 0 && cost.basis !== "per-did" && cost.basis !== "per-participant") {
|
|
300
|
+
problems.push(`${label}.basis must be 'per-did' or 'per-participant'`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function validateCohortConditions(c) {
|
|
304
|
+
const problems = [];
|
|
305
|
+
if (!KNOWN_BEACON_TYPES.includes(c.beaconType)) {
|
|
306
|
+
problems.push(`beaconType must be one of ${KNOWN_BEACON_TYPES.join(", ")}`);
|
|
307
|
+
}
|
|
308
|
+
if (!Number.isInteger(c.minParticipants) || c.minParticipants < 1) {
|
|
309
|
+
problems.push("minParticipants must be an integer >= 1");
|
|
310
|
+
}
|
|
311
|
+
if (c.maxParticipants !== void 0) {
|
|
312
|
+
if (!Number.isInteger(c.maxParticipants) || c.maxParticipants < 1) {
|
|
313
|
+
problems.push("maxParticipants must be an integer >= 1");
|
|
314
|
+
} else if (Number.isInteger(c.minParticipants) && c.maxParticipants < c.minParticipants) {
|
|
315
|
+
problems.push("maxParticipants must be >= minParticipants");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
checkPair(problems, "DidsPerParticipant", c.minDidsPerParticipant, c.maxDidsPerParticipant);
|
|
319
|
+
checkPair(problems, "SecondsBetweenAnnouncements", c.minSecondsBetweenAnnouncements, c.maxSecondsBetweenAnnouncements);
|
|
320
|
+
if (c.pendingUpdateTrigger !== void 0 && (!Number.isInteger(c.pendingUpdateTrigger) || c.pendingUpdateTrigger < 1)) {
|
|
321
|
+
problems.push("pendingUpdateTrigger must be an integer >= 1");
|
|
322
|
+
}
|
|
323
|
+
if (c.fallbackThreshold !== void 0) {
|
|
324
|
+
if (!Number.isInteger(c.fallbackThreshold) || c.fallbackThreshold < 1) {
|
|
325
|
+
problems.push("fallbackThreshold must be an integer >= 1");
|
|
326
|
+
} else if (c.maxParticipants !== void 0 && Number.isInteger(c.maxParticipants) && c.fallbackThreshold > c.maxParticipants) {
|
|
327
|
+
problems.push("fallbackThreshold must be <= maxParticipants");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
checkCost(problems, "costOfEnrollment", c.costOfEnrollment);
|
|
331
|
+
checkCost(problems, "costPerAnnouncement", c.costPerAnnouncement);
|
|
332
|
+
if (typeof c.recoveryKey !== "string" || !/^[0-9a-fA-F]{64}$/.test(c.recoveryKey)) {
|
|
333
|
+
problems.push("recoveryKey must be a 64-character hex string (x-only public key)");
|
|
334
|
+
}
|
|
335
|
+
if (!Number.isInteger(c.recoverySequence) || c.recoverySequence < 1 || c.recoverySequence > MAX_RECOVERY_SEQUENCE) {
|
|
336
|
+
problems.push(`recoverySequence must be a block-based BIP-68 relative timelock in [1, ${MAX_RECOVERY_SEQUENCE}]`);
|
|
337
|
+
}
|
|
338
|
+
if (c.fundingModel !== void 0 && !KNOWN_FUNDING_MODELS.includes(c.fundingModel)) {
|
|
339
|
+
problems.push(`fundingModel must be one of ${KNOWN_FUNDING_MODELS.join(", ")}`);
|
|
340
|
+
}
|
|
341
|
+
return problems;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/core/phases.ts
|
|
345
|
+
var ServiceCohortPhase = /* @__PURE__ */ ((ServiceCohortPhase2) => {
|
|
346
|
+
ServiceCohortPhase2["Created"] = "Created";
|
|
347
|
+
ServiceCohortPhase2["Advertised"] = "Advertised";
|
|
348
|
+
ServiceCohortPhase2["CohortSet"] = "CohortSet";
|
|
349
|
+
ServiceCohortPhase2["CollectingUpdates"] = "CollectingUpdates";
|
|
350
|
+
ServiceCohortPhase2["UpdatesCollected"] = "UpdatesCollected";
|
|
351
|
+
ServiceCohortPhase2["DataDistributed"] = "DataDistributed";
|
|
352
|
+
ServiceCohortPhase2["Validated"] = "Validated";
|
|
353
|
+
ServiceCohortPhase2["SigningStarted"] = "SigningStarted";
|
|
354
|
+
ServiceCohortPhase2["NoncesCollected"] = "NoncesCollected";
|
|
355
|
+
ServiceCohortPhase2["AwaitingPartialSigs"] = "AwaitingPartialSigs";
|
|
356
|
+
ServiceCohortPhase2["FallbackRequested"] = "FallbackRequested";
|
|
357
|
+
ServiceCohortPhase2["Complete"] = "Complete";
|
|
358
|
+
ServiceCohortPhase2["Failed"] = "Failed";
|
|
359
|
+
return ServiceCohortPhase2;
|
|
360
|
+
})(ServiceCohortPhase || {});
|
|
361
|
+
var ParticipantCohortPhase = /* @__PURE__ */ ((ParticipantCohortPhase2) => {
|
|
362
|
+
ParticipantCohortPhase2["Discovered"] = "Discovered";
|
|
363
|
+
ParticipantCohortPhase2["OptedIn"] = "OptedIn";
|
|
364
|
+
ParticipantCohortPhase2["CohortReady"] = "CohortReady";
|
|
365
|
+
ParticipantCohortPhase2["UpdateSubmitted"] = "UpdateSubmitted";
|
|
366
|
+
ParticipantCohortPhase2["NonIncluded"] = "NonIncluded";
|
|
367
|
+
ParticipantCohortPhase2["AwaitingValidation"] = "AwaitingValidation";
|
|
368
|
+
ParticipantCohortPhase2["ValidationSent"] = "ValidationSent";
|
|
369
|
+
ParticipantCohortPhase2["AwaitingSigning"] = "AwaitingSigning";
|
|
370
|
+
ParticipantCohortPhase2["NonceSent"] = "NonceSent";
|
|
371
|
+
ParticipantCohortPhase2["AwaitingPartialSig"] = "AwaitingPartialSig";
|
|
372
|
+
ParticipantCohortPhase2["AwaitingFallbackSig"] = "AwaitingFallbackSig";
|
|
373
|
+
ParticipantCohortPhase2["Complete"] = "Complete";
|
|
374
|
+
ParticipantCohortPhase2["Failed"] = "Failed";
|
|
375
|
+
return ParticipantCohortPhase2;
|
|
376
|
+
})(ParticipantCohortPhase || {});
|
|
377
|
+
var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
|
|
378
|
+
SigningSessionPhase2["AwaitingNonceContributions"] = "AwaitingNonceContributions";
|
|
379
|
+
SigningSessionPhase2["NonceContributionsReceived"] = "NonceContributionsReceived";
|
|
380
|
+
SigningSessionPhase2["AwaitingPartialSignatures"] = "AwaitingPartialSignatures";
|
|
381
|
+
SigningSessionPhase2["PartialSignaturesReceived"] = "PartialSignaturesReceived";
|
|
382
|
+
SigningSessionPhase2["Complete"] = "Complete";
|
|
383
|
+
SigningSessionPhase2["Failed"] = "Failed";
|
|
384
|
+
return SigningSessionPhase2;
|
|
385
|
+
})(SigningSessionPhase || {});
|
|
386
|
+
|
|
387
|
+
// src/core/cohort.ts
|
|
388
|
+
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
389
|
+
var import_common2 = require("@did-btcr2/common");
|
|
390
|
+
var import_smt = require("@did-btcr2/smt");
|
|
391
|
+
var import_secp256k12 = require("@noble/curves/secp256k1.js");
|
|
392
|
+
var import_utils2 = require("@noble/hashes/utils");
|
|
393
|
+
var import_btc_signer2 = require("@scure/btc-signer");
|
|
394
|
+
var import_musig22 = require("@scure/btc-signer/musig2");
|
|
395
|
+
var AggregationCohort = class {
|
|
396
|
+
/** Unique identifier for the cohort. */
|
|
397
|
+
id;
|
|
398
|
+
/** DID of the Aggregation Service managing this cohort. */
|
|
399
|
+
serviceDid;
|
|
400
|
+
/** Minimum number of participants required to finalize the cohort. */
|
|
401
|
+
minParticipants;
|
|
402
|
+
/** Network on which the cohort operates (mainnet, mutinynet, etc.). */
|
|
403
|
+
network;
|
|
404
|
+
/** Type of beacon used in the cohort: 'CASBeacon' or 'SMTBeacon'. */
|
|
405
|
+
beaconType;
|
|
406
|
+
/** List of participant DIDs that have been accepted into the cohort. */
|
|
407
|
+
participants = [];
|
|
408
|
+
/**
|
|
409
|
+
* Mapping from participant DID to their compressed secp256k1 public key.
|
|
410
|
+
* Distinct from {@link cohortKeys} (which is sorted per BIP-327): this lets
|
|
411
|
+
* callers look up a participant's key without knowing their position in the
|
|
412
|
+
* sorted array. Populated by the service at `acceptParticipant` time.
|
|
413
|
+
*/
|
|
414
|
+
participantKeys = /* @__PURE__ */ new Map();
|
|
415
|
+
/** Sorted list of cohort participants' compressed public keys. */
|
|
416
|
+
#cohortKeys = [];
|
|
417
|
+
/**
|
|
418
|
+
* BIP-341 TapTweak scalar: `taggedHash("TapTweak", internalPubkey || tapMerkleRoot)`.
|
|
419
|
+
* The beacon output is an internal key (the MuSig2 aggregate) plus a script
|
|
420
|
+
* tree (the recovery leaves), so the tweak commits to the tree's Merkle root.
|
|
421
|
+
* The MuSig2 signing session applies this as an x-only tweak; a value that does
|
|
422
|
+
* not match the root the funded address was derived from silently yields an
|
|
423
|
+
* invalid key-path signature.
|
|
424
|
+
*/
|
|
425
|
+
tapTweak = new Uint8Array();
|
|
426
|
+
/** The n-of-n MuSig2 aggregate internal key, x-only (32 bytes), set by computeBeaconAddress(). */
|
|
427
|
+
internalKey = new Uint8Array();
|
|
428
|
+
/** BIP-341 Taproot Merkle root of the recovery script tree, set by computeBeaconAddress(). */
|
|
429
|
+
tapMerkleRoot = new Uint8Array();
|
|
430
|
+
/** Operator recovery key, x-only (32 bytes). Used to build the CSV recovery leaf. */
|
|
431
|
+
recoveryKey;
|
|
432
|
+
/** Relative-timelock (BIP-68 nSequence) before the recovery leaf is spendable. */
|
|
433
|
+
recoverySequence;
|
|
434
|
+
/** Funding model governing the recovery leaves (default 'operator-funded'). */
|
|
435
|
+
fundingModel;
|
|
436
|
+
/**
|
|
437
|
+
* Advertised k of the k-of-n fallback leaf, or undefined to default to n-1 at
|
|
438
|
+
* address computation. Read the resolved value via {@link effectiveFallbackThreshold}.
|
|
439
|
+
*/
|
|
440
|
+
fallbackThreshold;
|
|
441
|
+
/** The Taproot beacon address: key path is the MuSig2 aggregate, script path is fallback + recovery. */
|
|
442
|
+
beaconAddress = "";
|
|
443
|
+
/** Pending DID updates submitted by participants, keyed by DID. */
|
|
444
|
+
pendingUpdates = /* @__PURE__ */ new Map();
|
|
445
|
+
/**
|
|
446
|
+
* Participant DIDs that explicitly declined to submit an update this round
|
|
447
|
+
* (cooperative non-inclusion). A decliner is absent from the CAS Announcement
|
|
448
|
+
* Map and carries a non-inclusion leaf in the SMT, yet still signs. Kept
|
|
449
|
+
* disjoint from {@link pendingUpdates} so CAS correctness holds by construction.
|
|
450
|
+
*/
|
|
451
|
+
nonIncluded = /* @__PURE__ */ new Set();
|
|
452
|
+
/** CAS Beacon Announcement Map (DID to updateHash), set by buildCASAnnouncement(). */
|
|
453
|
+
casAnnouncement;
|
|
454
|
+
/** Per-participant SMT proofs, set by buildSMTTree(). */
|
|
455
|
+
smtProofs;
|
|
456
|
+
/** Signal bytes (32 bytes) for OP_RETURN: SHA-256 of CAS announcement OR SMT root. */
|
|
457
|
+
signalBytes;
|
|
458
|
+
/** Set of participant DIDs that have approved the aggregated data. */
|
|
459
|
+
validationAcks = /* @__PURE__ */ new Set();
|
|
460
|
+
/** Set of participant DIDs that have rejected the aggregated data. */
|
|
461
|
+
validationRejections = /* @__PURE__ */ new Set();
|
|
462
|
+
constructor({ id, minParticipants, serviceDid, network, beaconType, recoveryKey, recoverySequence, fundingModel, fallbackThreshold }) {
|
|
463
|
+
this.id = id || crypto.randomUUID();
|
|
464
|
+
this.minParticipants = minParticipants ?? 2;
|
|
465
|
+
this.serviceDid = serviceDid || "";
|
|
466
|
+
this.network = network;
|
|
467
|
+
this.beaconType = beaconType || "CASBeacon";
|
|
468
|
+
this.recoveryKey = recoveryKey;
|
|
469
|
+
this.recoverySequence = recoverySequence;
|
|
470
|
+
this.fundingModel = fundingModel ?? DEFAULT_FUNDING_MODEL;
|
|
471
|
+
this.fallbackThreshold = fallbackThreshold;
|
|
472
|
+
}
|
|
473
|
+
/** Sorted cohort keys (sorted on assignment per BIP-327). */
|
|
474
|
+
get cohortKeys() {
|
|
475
|
+
return this.#cohortKeys;
|
|
476
|
+
}
|
|
477
|
+
set cohortKeys(keys) {
|
|
478
|
+
this.#cohortKeys = (0, import_musig22.sortKeys)(keys);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* The resolved k of the k-of-n fallback leaf: the advertised
|
|
482
|
+
* {@link fallbackThreshold}, or n-1 (floored at 1) when unadvertised, where n is
|
|
483
|
+
* the current cohort size. This is the value the beacon address commits to and
|
|
484
|
+
* the spend builders must reproduce. Returns 0 before any cohort keys are set.
|
|
485
|
+
*/
|
|
486
|
+
get effectiveFallbackThreshold() {
|
|
487
|
+
if (this.#cohortKeys.length === 0) return 0;
|
|
488
|
+
return resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Computes the Taproot beacon address from the cohort keys and recovery params.
|
|
492
|
+
*
|
|
493
|
+
* The output's key path is the n-of-n MuSig2 aggregate of the cohort keys; its
|
|
494
|
+
* script path is the recovery tree (a CSV recovery leaf so a missing signer
|
|
495
|
+
* cannot permanently lock the UTXO). Sets `internalKey`, `tapMerkleRoot`, and
|
|
496
|
+
* the `tapTweak` the MuSig2 session must apply for the key-path spend.
|
|
497
|
+
*
|
|
498
|
+
* The tweak is derived from the Merkle root the address was built with (read
|
|
499
|
+
* back from the payment), never recomputed by hand, so the MuSig2 key-path
|
|
500
|
+
* signature is guaranteed to validate against the funded address.
|
|
501
|
+
*/
|
|
502
|
+
computeBeaconAddress() {
|
|
503
|
+
if (this.#cohortKeys.length === 0) {
|
|
504
|
+
throw new AggregationCohortError(
|
|
505
|
+
"Cannot compute beacon address: no cohort keys.",
|
|
506
|
+
"NO_COHORT_KEYS",
|
|
507
|
+
{ cohortId: this.id }
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
if (!this.recoveryKey || this.recoveryKey.length === 0 || this.recoverySequence === void 0) {
|
|
511
|
+
throw new AggregationCohortError(
|
|
512
|
+
"Cannot compute beacon address: missing recovery key or sequence.",
|
|
513
|
+
"NO_RECOVERY_PARAMS",
|
|
514
|
+
{ cohortId: this.id }
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const keyAggContext = (0, import_musig22.keyAggregate)(this.#cohortKeys);
|
|
518
|
+
const aggPubkey = (0, import_musig22.keyAggExport)(keyAggContext);
|
|
519
|
+
const leaves = buildRecoveryLeaves(this.fundingModel, {
|
|
520
|
+
recoveryKey: this.recoveryKey,
|
|
521
|
+
recoverySequence: this.recoverySequence,
|
|
522
|
+
cohortKeys: this.#cohortKeys,
|
|
523
|
+
fallbackThreshold: resolveFallbackThreshold(this.fallbackThreshold, this.#cohortKeys.length)
|
|
524
|
+
});
|
|
525
|
+
const payment = (0, import_btc_signer2.p2tr)(aggPubkey, leaves, (0, import_bitcoin.getNetwork)(this.network), true);
|
|
526
|
+
this.internalKey = aggPubkey;
|
|
527
|
+
this.tapMerkleRoot = payment.tapMerkleRoot;
|
|
528
|
+
this.tapTweak = import_secp256k12.schnorr.utils.taggedHash("TapTweak", (0, import_utils2.concatBytes)(aggPubkey, payment.tapMerkleRoot));
|
|
529
|
+
if (!payment.address) {
|
|
530
|
+
throw new AggregationCohortError(
|
|
531
|
+
"Failed to compute Taproot address",
|
|
532
|
+
"BEACON_ADDRESS_ERROR",
|
|
533
|
+
{ cohortId: this.id }
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
this.beaconAddress = payment.address;
|
|
537
|
+
return payment.address;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Validates that the participant's key is in the cohort and the beacon address
|
|
541
|
+
* matches the locally-computed one. Used by participants to verify cohort ready
|
|
542
|
+
* messages from the service.
|
|
543
|
+
*/
|
|
544
|
+
validateMembership(participantPkHex, cohortKeysHex, expectedBeaconAddress) {
|
|
545
|
+
if (!cohortKeysHex.includes(participantPkHex)) {
|
|
546
|
+
throw new AggregationCohortError(
|
|
547
|
+
`Participant key not found in cohort ${this.id}.`,
|
|
548
|
+
"COHORT_VALIDATION_ERROR",
|
|
549
|
+
{ cohortId: this.id, participantPkHex }
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
this.cohortKeys = cohortKeysHex.map((k) => (0, import_utils2.hexToBytes)(k));
|
|
553
|
+
const computed = this.computeBeaconAddress();
|
|
554
|
+
if (computed !== expectedBeaconAddress) {
|
|
555
|
+
throw new AggregationCohortError(
|
|
556
|
+
`Computed beacon address ${computed} does not match expected ${expectedBeaconAddress}.`,
|
|
557
|
+
"BEACON_ADDRESS_MISMATCH",
|
|
558
|
+
{ cohortId: this.id, computed, expected: expectedBeaconAddress }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Returns the position of a participant's public key in the sorted
|
|
564
|
+
* {@link cohortKeys} array, or -1 if the participant is not in the cohort.
|
|
565
|
+
* Required by MuSig2 partial-sig verification which indexes by signer position.
|
|
566
|
+
*/
|
|
567
|
+
indexOfParticipant(did) {
|
|
568
|
+
const pk = this.participantKeys.get(did);
|
|
569
|
+
if (!pk) return -1;
|
|
570
|
+
return this.#cohortKeys.findIndex(
|
|
571
|
+
(k) => k.length === pk.length && k.every((b, i) => b === pk[i])
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
addUpdate(participantDid, signedUpdate) {
|
|
575
|
+
if (!this.participants.includes(participantDid)) {
|
|
576
|
+
throw new AggregationCohortError(
|
|
577
|
+
`Participant ${participantDid} is not in cohort ${this.id}.`,
|
|
578
|
+
"UNKNOWN_PARTICIPANT",
|
|
579
|
+
{ cohortId: this.id, participantDid }
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
this.pendingUpdates.set(participantDid, signedUpdate);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Record that a participant declined to submit an update this round
|
|
586
|
+
* (cooperative non-inclusion). The member stays in the cohort and still signs.
|
|
587
|
+
*/
|
|
588
|
+
addNonInclusion(participantDid) {
|
|
589
|
+
if (!this.participants.includes(participantDid)) {
|
|
590
|
+
throw new AggregationCohortError(
|
|
591
|
+
`Participant ${participantDid} is not in cohort ${this.id}.`,
|
|
592
|
+
"UNKNOWN_PARTICIPANT",
|
|
593
|
+
{ cohortId: this.id, participantDid }
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
if (this.pendingUpdates.has(participantDid)) {
|
|
597
|
+
throw new AggregationCohortError(
|
|
598
|
+
`Participant ${participantDid} already submitted an update; cannot also decline.`,
|
|
599
|
+
"CONFLICTING_RESPONSE",
|
|
600
|
+
{ cohortId: this.id, participantDid }
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
this.nonIncluded.add(participantDid);
|
|
604
|
+
}
|
|
605
|
+
hasAllUpdates() {
|
|
606
|
+
return this.pendingUpdates.size === this.participants.length;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* True when every participant has responded for this round, either with an
|
|
610
|
+
* update or with an explicit non-inclusion. This is the aggregation gate when
|
|
611
|
+
* non-inclusion is in play; it generalizes {@link hasAllUpdates} the same way
|
|
612
|
+
* {@link hasAllValidationResponses} generalizes a unanimous ack.
|
|
613
|
+
*/
|
|
614
|
+
hasAllResponses() {
|
|
615
|
+
return this.pendingUpdates.size + this.nonIncluded.size === this.participants.length;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Builds a CAS Announcement Map from collected updates.
|
|
619
|
+
* Maps each participant DID to base64url canonical hash of their signed update.
|
|
620
|
+
* Computes signal bytes as SHA-256 of canonicalized announcement.
|
|
621
|
+
*
|
|
622
|
+
* Members who declined (cooperative non-inclusion) are naturally absent from
|
|
623
|
+
* the map: the body iterates {@link pendingUpdates}, which never holds a
|
|
624
|
+
* decliner. Absence from the map is exactly the CAS non-inclusion signal.
|
|
625
|
+
*/
|
|
626
|
+
buildCASAnnouncement() {
|
|
627
|
+
if (!this.hasAllResponses()) {
|
|
628
|
+
throw new AggregationCohortError(
|
|
629
|
+
"Cannot build CAS Announcement: not all participants have responded.",
|
|
630
|
+
"INCOMPLETE_RESPONSES",
|
|
631
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const announcement = {};
|
|
635
|
+
for (const [did, signedUpdate] of this.pendingUpdates) {
|
|
636
|
+
announcement[did] = (0, import_common2.canonicalHash)(signedUpdate);
|
|
637
|
+
}
|
|
638
|
+
this.casAnnouncement = announcement;
|
|
639
|
+
this.signalBytes = (0, import_common2.hash)((0, import_common2.canonicalize)(announcement));
|
|
640
|
+
return announcement;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Builds an SMT tree with one leaf per participant.
|
|
644
|
+
*
|
|
645
|
+
* A member who submitted an update gets an inclusion leaf
|
|
646
|
+
* (SHA-256(SHA-256(nonce) || SHA-256(update))); a member who declined gets a
|
|
647
|
+
* non-inclusion leaf (SHA-256(SHA-256(nonce)), the `signedUpdate` entry field
|
|
648
|
+
* omitted). The cohort mints each member's nonce and returns it inside that
|
|
649
|
+
* member's serialized proof, so a decliner can self-validate its own
|
|
650
|
+
* non-inclusion slot and the resolver can recompute the leaf. Stores
|
|
651
|
+
* per-participant proofs and the SMT root as signalBytes.
|
|
652
|
+
*/
|
|
653
|
+
buildSMTTree() {
|
|
654
|
+
if (!this.hasAllResponses()) {
|
|
655
|
+
throw new AggregationCohortError(
|
|
656
|
+
"Cannot build SMT tree: not all participants have responded.",
|
|
657
|
+
"INCOMPLETE_RESPONSES",
|
|
658
|
+
{ cohortId: this.id, updates: this.pendingUpdates.size, declined: this.nonIncluded.size, total: this.participants.length }
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
const tree = new import_smt.BTCR2MerkleTree();
|
|
662
|
+
const entries = [];
|
|
663
|
+
const encoder = new TextEncoder();
|
|
664
|
+
for (const did of this.participants) {
|
|
665
|
+
const nonce = (0, import_utils2.randomBytes)(32);
|
|
666
|
+
const signedUpdate = this.pendingUpdates.get(did);
|
|
667
|
+
if (signedUpdate) {
|
|
668
|
+
entries.push({ did, nonce, signedUpdate: encoder.encode((0, import_common2.canonicalize)(signedUpdate)) });
|
|
669
|
+
} else {
|
|
670
|
+
entries.push({ did, nonce });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
tree.addEntries(entries);
|
|
674
|
+
tree.finalize();
|
|
675
|
+
this.signalBytes = tree.rootHash;
|
|
676
|
+
this.smtProofs = /* @__PURE__ */ new Map();
|
|
677
|
+
for (const did of this.participants) {
|
|
678
|
+
this.smtProofs.set(did, tree.proof(did));
|
|
679
|
+
}
|
|
680
|
+
return this.smtProofs;
|
|
681
|
+
}
|
|
682
|
+
addValidation(participantDid, approved) {
|
|
683
|
+
if (!this.participants.includes(participantDid)) {
|
|
684
|
+
throw new AggregationCohortError(
|
|
685
|
+
`Unknown participant ${participantDid} in cohort ${this.id}.`,
|
|
686
|
+
"UNKNOWN_PARTICIPANT",
|
|
687
|
+
{ cohortId: this.id, participantDid }
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
if (approved) {
|
|
691
|
+
this.validationAcks.add(participantDid);
|
|
692
|
+
} else {
|
|
693
|
+
this.validationRejections.add(participantDid);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* True when every participant has either approved or rejected the aggregated data.
|
|
698
|
+
*/
|
|
699
|
+
hasAllValidationResponses() {
|
|
700
|
+
return this.validationAcks.size + this.validationRejections.size === this.participants.length;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* True when all participants approved. Note: differs from {@link hasAllValidationResponses},
|
|
704
|
+
* this returns false if any participant rejected, even if all responses are in.
|
|
705
|
+
*/
|
|
706
|
+
isFullyValidated() {
|
|
707
|
+
return this.validationRejections.size === 0 && this.validationAcks.size === this.participants.length;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
// src/core/signing-session.ts
|
|
712
|
+
var import_keypair = require("@did-btcr2/keypair");
|
|
713
|
+
var import_btc_signer3 = require("@scure/btc-signer");
|
|
714
|
+
var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
|
|
715
|
+
var BeaconSigningSession = class {
|
|
716
|
+
/** Unique identifier for this signing session. */
|
|
717
|
+
id;
|
|
718
|
+
/** The cohort this session signs for. */
|
|
719
|
+
cohort;
|
|
720
|
+
/** The Bitcoin transaction being signed. */
|
|
721
|
+
pendingTx;
|
|
722
|
+
/** Previous output scripts for Taproot sighash computation (BIP-341). */
|
|
723
|
+
prevOutScripts;
|
|
724
|
+
/** Previous output values for Taproot sighash computation. */
|
|
725
|
+
prevOutValues;
|
|
726
|
+
/** Map of participant publicKey-hex to public nonce contribution. */
|
|
727
|
+
nonceContributions = /* @__PURE__ */ new Map();
|
|
728
|
+
/** Aggregated MuSig2 nonce (66 bytes). */
|
|
729
|
+
aggregatedNonce;
|
|
730
|
+
/** Map of participant DID to partial signature. */
|
|
731
|
+
partialSignatures = /* @__PURE__ */ new Map();
|
|
732
|
+
/** Final 64-byte Schnorr signature. */
|
|
733
|
+
signature;
|
|
734
|
+
/** Current signing session phase. */
|
|
735
|
+
phase;
|
|
736
|
+
/**
|
|
737
|
+
* Participant's MuSig2 secret nonce, held only on the participant side
|
|
738
|
+
* between {@link generateNonceContribution} and {@link generatePartialSignature}.
|
|
739
|
+
* Private and cleared on every terminal path (success, failure, or teardown
|
|
740
|
+
* via {@link clearSecrets}) so a spent or abandoned nonce cannot be reused or
|
|
741
|
+
* serialized.
|
|
742
|
+
*/
|
|
743
|
+
#secretNonce;
|
|
744
|
+
constructor({ id, cohort, pendingTx, prevOutScripts, prevOutValues }) {
|
|
745
|
+
this.id = id || crypto.randomUUID();
|
|
746
|
+
this.cohort = cohort;
|
|
747
|
+
this.pendingTx = pendingTx;
|
|
748
|
+
this.prevOutScripts = prevOutScripts || [];
|
|
749
|
+
this.prevOutValues = prevOutValues || [];
|
|
750
|
+
this.phase = "AwaitingNonceContributions" /* AwaitingNonceContributions */;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Computes the Taproot sighash (BIP-341) for the first input.
|
|
754
|
+
*/
|
|
755
|
+
get sigHash() {
|
|
756
|
+
if (!this.prevOutScripts.length || !this.prevOutValues.length) {
|
|
757
|
+
throw new SigningSessionError(
|
|
758
|
+
"Cannot compute sighash: missing prevOutScripts or prevOutValues.",
|
|
759
|
+
"SIGHASH_ERROR"
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
return this.pendingTx.preimageWitnessV1(
|
|
763
|
+
0,
|
|
764
|
+
this.prevOutScripts,
|
|
765
|
+
import_btc_signer3.SigHash.DEFAULT,
|
|
766
|
+
this.prevOutValues
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
addNonceContribution(participantDid, nonceContribution) {
|
|
770
|
+
if (this.phase !== "AwaitingNonceContributions" /* AwaitingNonceContributions */) {
|
|
771
|
+
throw new SigningSessionError(
|
|
772
|
+
`Nonce contributions not expected. Current phase: ${this.phase}`,
|
|
773
|
+
"INVALID_PHASE",
|
|
774
|
+
{ phase: this.phase }
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
if (!this.cohort.participants.includes(participantDid)) {
|
|
778
|
+
throw new SigningSessionError(
|
|
779
|
+
`Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
|
|
780
|
+
"UNKNOWN_PARTICIPANT",
|
|
781
|
+
{ cohortId: this.cohort.id, participantDid }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
if (nonceContribution.length !== 66) {
|
|
785
|
+
throw new SigningSessionError(
|
|
786
|
+
`Invalid nonce contribution: expected 66 bytes, got ${nonceContribution.length}.`,
|
|
787
|
+
"INVALID_NONCE_LENGTH"
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
if (this.nonceContributions.has(participantDid)) {
|
|
791
|
+
throw new SigningSessionError(
|
|
792
|
+
`Duplicate nonce contribution from ${participantDid}.`,
|
|
793
|
+
"DUPLICATE_NONCE"
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
this.nonceContributions.set(participantDid, nonceContribution);
|
|
797
|
+
if (this.nonceContributions.size === this.cohort.participants.length) {
|
|
798
|
+
this.phase = "NonceContributionsReceived" /* NonceContributionsReceived */;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
generateAggregatedNonce() {
|
|
802
|
+
if (this.phase !== "NonceContributionsReceived" /* NonceContributionsReceived */) {
|
|
803
|
+
throw new SigningSessionError(
|
|
804
|
+
`Cannot aggregate nonces: phase is ${this.phase}, expected NonceContributionsReceived.`,
|
|
805
|
+
"INVALID_PHASE"
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
this.aggregatedNonce = musig2.nonceAggregate([...this.nonceContributions.values()]);
|
|
809
|
+
this.phase = "AwaitingPartialSignatures" /* AwaitingPartialSignatures */;
|
|
810
|
+
return this.aggregatedNonce;
|
|
811
|
+
}
|
|
812
|
+
addPartialSignature(participantDid, partialSig) {
|
|
813
|
+
if (this.phase !== "AwaitingPartialSignatures" /* AwaitingPartialSignatures */) {
|
|
814
|
+
throw new SigningSessionError(
|
|
815
|
+
`Partial signatures not expected. Current phase: ${this.phase}`,
|
|
816
|
+
"INVALID_PHASE"
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
if (!this.cohort.participants.includes(participantDid)) {
|
|
820
|
+
throw new SigningSessionError(
|
|
821
|
+
`Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
|
|
822
|
+
"UNKNOWN_PARTICIPANT",
|
|
823
|
+
{ cohortId: this.cohort.id, participantDid }
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
if (this.partialSignatures.has(participantDid)) {
|
|
827
|
+
throw new SigningSessionError(
|
|
828
|
+
`Duplicate partial signature from ${participantDid}.`,
|
|
829
|
+
"DUPLICATE_PARTIAL_SIG"
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
this.partialSignatures.set(participantDid, partialSig);
|
|
833
|
+
if (this.partialSignatures.size === this.cohort.participants.length) {
|
|
834
|
+
this.phase = "PartialSignaturesReceived" /* PartialSignaturesReceived */;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
generateFinalSignature() {
|
|
838
|
+
if (this.phase !== "PartialSignaturesReceived" /* PartialSignaturesReceived */) {
|
|
839
|
+
throw new SigningSessionError(
|
|
840
|
+
`Cannot generate final signature: phase is ${this.phase}.`,
|
|
841
|
+
"INVALID_PHASE"
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
if (!this.aggregatedNonce) {
|
|
845
|
+
throw new SigningSessionError("Aggregated nonce missing.", "MISSING_AGGREGATED_NONCE");
|
|
846
|
+
}
|
|
847
|
+
const session = new musig2.Session(
|
|
848
|
+
this.aggregatedNonce,
|
|
849
|
+
this.cohort.cohortKeys,
|
|
850
|
+
this.sigHash,
|
|
851
|
+
[this.cohort.tapTweak],
|
|
852
|
+
[true]
|
|
853
|
+
);
|
|
854
|
+
const pubNoncesByIndex = new Array(this.cohort.cohortKeys.length);
|
|
855
|
+
for (const [did, nonce] of this.nonceContributions) {
|
|
856
|
+
const idx = this.cohort.indexOfParticipant(did);
|
|
857
|
+
if (idx < 0) {
|
|
858
|
+
throw new SigningSessionError(
|
|
859
|
+
`Cannot verify nonce from ${did}: participant key missing from cohort.`,
|
|
860
|
+
"UNKNOWN_PARTICIPANT_KEY",
|
|
861
|
+
{ participantDid: did }
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
pubNoncesByIndex[idx] = nonce;
|
|
865
|
+
}
|
|
866
|
+
for (const [did, partialSig] of this.partialSignatures) {
|
|
867
|
+
const idx = this.cohort.indexOfParticipant(did);
|
|
868
|
+
if (idx < 0) {
|
|
869
|
+
throw new SigningSessionError(
|
|
870
|
+
`Cannot verify partial signature from ${did}: participant key missing from cohort.`,
|
|
871
|
+
"UNKNOWN_PARTICIPANT_KEY",
|
|
872
|
+
{ participantDid: did }
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
const ok = session.partialSigVerify(partialSig, pubNoncesByIndex, idx);
|
|
876
|
+
if (!ok) {
|
|
877
|
+
throw new SigningSessionError(
|
|
878
|
+
`Bad partial signature from ${did}.`,
|
|
879
|
+
"BAD_PARTIAL_SIG",
|
|
880
|
+
{ participantDid: did, index: idx }
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
this.signature = session.partialSigAgg([...this.partialSignatures.values()]);
|
|
885
|
+
this.phase = "Complete" /* Complete */;
|
|
886
|
+
return this.signature;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Generates a fresh MuSig2 nonce contribution for the participant.
|
|
890
|
+
* Stores the secret nonce internally for use in `generatePartialSignature()`.
|
|
891
|
+
*/
|
|
892
|
+
generateNonceContribution(participantPublicKey, participantSecretKey) {
|
|
893
|
+
const aggPublicKey = musig2.keyAggExport(musig2.keyAggregate(this.cohort.cohortKeys));
|
|
894
|
+
const nonces = musig2.nonceGen(participantPublicKey, participantSecretKey, aggPublicKey);
|
|
895
|
+
this.#secretNonce = nonces.secret;
|
|
896
|
+
return nonces.public;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Generates a partial signature using the participant's secret key + secret nonce.
|
|
900
|
+
* Requires the aggregated nonce to have been set first (via the service).
|
|
901
|
+
*
|
|
902
|
+
* Clears the stored secret nonce after use on every path (success or throw)
|
|
903
|
+
* via {@link clearSecrets}. JS cannot truly erase memory (GC may relocate
|
|
904
|
+
* buffers), but overwriting the bytes shortens the exposure window and
|
|
905
|
+
* prevents accidental reuse or serialization of a spent nonce - reuse of a
|
|
906
|
+
* MuSig2 nonce leaks the secret key.
|
|
907
|
+
*/
|
|
908
|
+
generatePartialSignature(participantSecretKey) {
|
|
909
|
+
if (!this.aggregatedNonce) {
|
|
910
|
+
throw new SigningSessionError("Aggregated nonce not available.", "MISSING_AGGREGATED_NONCE");
|
|
911
|
+
}
|
|
912
|
+
if (!this.#secretNonce) {
|
|
913
|
+
throw new SigningSessionError("Secret nonce not available - generateNonceContribution() must be called first.", "MISSING_SECRET_NONCE");
|
|
914
|
+
}
|
|
915
|
+
const session = new musig2.Session(
|
|
916
|
+
this.aggregatedNonce,
|
|
917
|
+
this.cohort.cohortKeys,
|
|
918
|
+
this.sigHash,
|
|
919
|
+
[this.cohort.tapTweak],
|
|
920
|
+
[true]
|
|
921
|
+
);
|
|
922
|
+
try {
|
|
923
|
+
return session.sign(this.#secretNonce, participantSecretKey);
|
|
924
|
+
} finally {
|
|
925
|
+
this.clearSecrets();
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Zeroize any retained secret nonce. Safe to call repeatedly and on any path
|
|
930
|
+
* (completion, failure, or teardown of an abandoned session). Callers that
|
|
931
|
+
* drop a session before it reaches a partial signature should invoke this so
|
|
932
|
+
* the secret nonce does not linger on the live object.
|
|
933
|
+
*/
|
|
934
|
+
clearSecrets() {
|
|
935
|
+
if (this.#secretNonce) {
|
|
936
|
+
(0, import_keypair.wipe)(this.#secretNonce);
|
|
937
|
+
this.#secretNonce = void 0;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
isComplete() {
|
|
941
|
+
return this.phase === "Complete" /* Complete */;
|
|
942
|
+
}
|
|
943
|
+
isFailed() {
|
|
944
|
+
return this.phase === "Failed" /* Failed */;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// src/core/signer.ts
|
|
949
|
+
var import_keypair2 = require("@did-btcr2/keypair");
|
|
950
|
+
var KeyPairAggregationSigner = class {
|
|
951
|
+
publicKey;
|
|
952
|
+
#keys;
|
|
953
|
+
/** @param {SchnorrKeyPair} keys The keypair whose secret backs this signer. */
|
|
954
|
+
constructor(keys) {
|
|
955
|
+
this.#keys = keys;
|
|
956
|
+
this.publicKey = keys.publicKey.compressed;
|
|
957
|
+
}
|
|
958
|
+
withSecret(fn) {
|
|
959
|
+
const secretKey = this.#keys.secretKey.bytes;
|
|
960
|
+
try {
|
|
961
|
+
return fn(secretKey);
|
|
962
|
+
} finally {
|
|
963
|
+
(0, import_keypair2.wipe)(secretKey);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// src/core/beacon-strategy.ts
|
|
969
|
+
var import_common3 = require("@did-btcr2/common");
|
|
970
|
+
var import_smt2 = require("@did-btcr2/smt");
|
|
971
|
+
var CAS_STRATEGY = {
|
|
972
|
+
type: "CASBeacon",
|
|
973
|
+
buildAggregatedData(cohort) {
|
|
974
|
+
cohort.buildCASAnnouncement();
|
|
975
|
+
},
|
|
976
|
+
getDistributePayload(cohort) {
|
|
977
|
+
return { casAnnouncement: cohort.casAnnouncement };
|
|
978
|
+
},
|
|
979
|
+
validateParticipantView({ participantDid, included, expectedHash, body }) {
|
|
980
|
+
const casAnnouncement = body.casAnnouncement;
|
|
981
|
+
if (!casAnnouncement) return { matches: false };
|
|
982
|
+
if (!included) {
|
|
983
|
+
return { matches: !(participantDid in casAnnouncement), casAnnouncement };
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
matches: casAnnouncement[participantDid] === expectedHash,
|
|
987
|
+
casAnnouncement
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
var SMT_STRATEGY = {
|
|
992
|
+
type: "SMTBeacon",
|
|
993
|
+
buildAggregatedData(cohort) {
|
|
994
|
+
cohort.buildSMTTree();
|
|
995
|
+
},
|
|
996
|
+
getDistributePayload(cohort, participantDid) {
|
|
997
|
+
const proof = cohort.smtProofs?.get(participantDid);
|
|
998
|
+
return { smtProof: proof };
|
|
999
|
+
},
|
|
1000
|
+
validateParticipantView({ participantDid, included, submittedUpdate, body }) {
|
|
1001
|
+
const smtProof = body.smtProof;
|
|
1002
|
+
const index = (0, import_smt2.didToIndex)(participantDid);
|
|
1003
|
+
if (!included) {
|
|
1004
|
+
if (!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
|
|
1005
|
+
const candidateHash2 = (0, import_smt2.blockHash)((0, import_smt2.blockHash)((0, import_smt2.base64UrlToHash)(smtProof.nonce)));
|
|
1006
|
+
return { matches: (0, import_smt2.verifySerializedProof)(smtProof, index, candidateHash2), smtProof };
|
|
1007
|
+
}
|
|
1008
|
+
if (!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
|
|
1009
|
+
const canonicalBytes = new TextEncoder().encode((0, import_common3.canonicalize)(submittedUpdate));
|
|
1010
|
+
const expectedUpdateId = (0, import_smt2.hashToBase64Url)((0, import_smt2.blockHash)(canonicalBytes));
|
|
1011
|
+
if (smtProof.updateId !== expectedUpdateId) {
|
|
1012
|
+
return { matches: false, smtProof };
|
|
1013
|
+
}
|
|
1014
|
+
const candidateHash = (0, import_smt2.blockHash)((0, import_smt2.blockHash)((0, import_smt2.base64UrlToHash)(smtProof.nonce)), (0, import_smt2.base64UrlToHash)(smtProof.updateId));
|
|
1015
|
+
return {
|
|
1016
|
+
matches: (0, import_smt2.verifySerializedProof)(smtProof, index, candidateHash),
|
|
1017
|
+
smtProof
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
var STRATEGIES = /* @__PURE__ */ new Map([
|
|
1022
|
+
[CAS_STRATEGY.type, CAS_STRATEGY],
|
|
1023
|
+
[SMT_STRATEGY.type, SMT_STRATEGY]
|
|
1024
|
+
]);
|
|
1025
|
+
function registerBeaconStrategy(strategy) {
|
|
1026
|
+
STRATEGIES.set(strategy.type, strategy);
|
|
1027
|
+
}
|
|
1028
|
+
function getBeaconStrategy(type) {
|
|
1029
|
+
return STRATEGIES.get(type);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/core/recovery-spend.ts
|
|
1033
|
+
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
1034
|
+
var import_secp256k13 = require("@noble/curves/secp256k1.js");
|
|
1035
|
+
var import_btc_signer4 = require("@scure/btc-signer");
|
|
1036
|
+
var import_musig23 = require("@scure/btc-signer/musig2");
|
|
1037
|
+
var DUST_LIMIT_SATS = 546n;
|
|
1038
|
+
function bytesEqual(a, b) {
|
|
1039
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
1040
|
+
}
|
|
1041
|
+
function buildRecoverySpend(params) {
|
|
1042
|
+
const {
|
|
1043
|
+
cohortKeys,
|
|
1044
|
+
recoverySecretKey,
|
|
1045
|
+
recoveryKey,
|
|
1046
|
+
recoverySequence,
|
|
1047
|
+
fallbackThreshold,
|
|
1048
|
+
fundingModel,
|
|
1049
|
+
network,
|
|
1050
|
+
utxo,
|
|
1051
|
+
destinationAddress,
|
|
1052
|
+
fee,
|
|
1053
|
+
beaconAddress
|
|
1054
|
+
} = params;
|
|
1055
|
+
if (cohortKeys.length === 0) {
|
|
1056
|
+
throw new AggregationCohortError(
|
|
1057
|
+
"Cannot build recovery spend: no cohort keys.",
|
|
1058
|
+
"NO_COHORT_KEYS"
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
const derivedPub = import_secp256k13.schnorr.getPublicKey(recoverySecretKey);
|
|
1062
|
+
if (!bytesEqual(derivedPub, recoveryKey)) {
|
|
1063
|
+
throw new AggregationCohortError(
|
|
1064
|
+
"Recovery secret key does not correspond to the committed recovery key.",
|
|
1065
|
+
"RECOVERY_KEY_MISMATCH"
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
const out = utxo.value - fee;
|
|
1069
|
+
if (out <= 0n) {
|
|
1070
|
+
throw new AggregationCohortError(
|
|
1071
|
+
`Recovery fee ${fee} exceeds UTXO value ${utxo.value}.`,
|
|
1072
|
+
"FEE_EXCEEDS_VALUE",
|
|
1073
|
+
{ fee: fee.toString(), value: utxo.value.toString() }
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (out < DUST_LIMIT_SATS) {
|
|
1077
|
+
throw new AggregationCohortError(
|
|
1078
|
+
`Recovered output ${out} is below the dust limit ${DUST_LIMIT_SATS}; the spend would not relay.`,
|
|
1079
|
+
"DUST_OUTPUT",
|
|
1080
|
+
{ output: out.toString(), dustLimit: DUST_LIMIT_SATS.toString() }
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
const internalKey = (0, import_musig23.keyAggExport)((0, import_musig23.keyAggregate)((0, import_musig23.sortKeys)(cohortKeys)));
|
|
1084
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
1085
|
+
recoveryKey,
|
|
1086
|
+
recoverySequence,
|
|
1087
|
+
cohortKeys,
|
|
1088
|
+
fallbackThreshold: resolveFallbackThreshold(fallbackThreshold, cohortKeys.length)
|
|
1089
|
+
});
|
|
1090
|
+
const net = (0, import_bitcoin2.getNetwork)(network);
|
|
1091
|
+
const payment = (0, import_btc_signer4.p2tr)(internalKey, leaves, net, true);
|
|
1092
|
+
if (beaconAddress !== void 0 && payment.address !== beaconAddress) {
|
|
1093
|
+
throw new AggregationCohortError(
|
|
1094
|
+
`Reconstructed beacon address ${payment.address} does not match the cohort's funded address ${beaconAddress}.`,
|
|
1095
|
+
"BEACON_ADDRESS_MISMATCH",
|
|
1096
|
+
{ reconstructed: payment.address, expected: beaconAddress }
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const tx = new import_btc_signer4.Transaction({ version: 2, allowUnknownInputs: true });
|
|
1100
|
+
tx.addInput({
|
|
1101
|
+
txid: utxo.txid,
|
|
1102
|
+
index: utxo.vout,
|
|
1103
|
+
witnessUtxo: { script: payment.script, amount: utxo.value },
|
|
1104
|
+
tapLeafScript: payment.tapLeafScript,
|
|
1105
|
+
sequence: recoverySequence
|
|
1106
|
+
});
|
|
1107
|
+
tx.addOutputAddress(destinationAddress, out, net);
|
|
1108
|
+
tx.signIdx(recoverySecretKey, 0);
|
|
1109
|
+
tx.finalize();
|
|
1110
|
+
return tx;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// src/core/fallback-spend.ts
|
|
1114
|
+
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
1115
|
+
var import_secp256k14 = require("@noble/curves/secp256k1.js");
|
|
1116
|
+
var import_btc_signer5 = require("@scure/btc-signer");
|
|
1117
|
+
var import_musig24 = require("@scure/btc-signer/musig2");
|
|
1118
|
+
function bytesEqual2(a, b) {
|
|
1119
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
1120
|
+
}
|
|
1121
|
+
function fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeafScript) {
|
|
1122
|
+
return tx.preimageWitnessV1(
|
|
1123
|
+
inputIndex,
|
|
1124
|
+
[prevOutScript],
|
|
1125
|
+
import_btc_signer5.SigHash.DEFAULT,
|
|
1126
|
+
[prevOutValue],
|
|
1127
|
+
void 0,
|
|
1128
|
+
fallbackLeafScript,
|
|
1129
|
+
TAPROOT_LEAF_VERSION
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
function buildFallbackSpend(params) {
|
|
1133
|
+
const {
|
|
1134
|
+
pendingTx,
|
|
1135
|
+
cohortKeys,
|
|
1136
|
+
recoveryKey,
|
|
1137
|
+
recoverySequence,
|
|
1138
|
+
fundingModel,
|
|
1139
|
+
network,
|
|
1140
|
+
prevOutScript,
|
|
1141
|
+
prevOutValue,
|
|
1142
|
+
signatures
|
|
1143
|
+
} = params;
|
|
1144
|
+
const inputIndex = params.inputIndex ?? 0;
|
|
1145
|
+
if (cohortKeys.length === 0) {
|
|
1146
|
+
throw new AggregationCohortError(
|
|
1147
|
+
"Cannot build fallback spend: no cohort keys.",
|
|
1148
|
+
"NO_COHORT_KEYS"
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
const fallbackThreshold = resolveFallbackThreshold(params.fallbackThreshold, cohortKeys.length);
|
|
1152
|
+
const internalKey = (0, import_musig24.keyAggExport)((0, import_musig24.keyAggregate)((0, import_musig24.sortKeys)(cohortKeys)));
|
|
1153
|
+
const leaves = buildRecoveryLeaves(fundingModel ?? DEFAULT_FUNDING_MODEL, {
|
|
1154
|
+
recoveryKey,
|
|
1155
|
+
recoverySequence,
|
|
1156
|
+
cohortKeys,
|
|
1157
|
+
fallbackThreshold
|
|
1158
|
+
});
|
|
1159
|
+
const net = (0, import_bitcoin3.getNetwork)(network);
|
|
1160
|
+
const payment = (0, import_btc_signer5.p2tr)(internalKey, leaves, net, true);
|
|
1161
|
+
if (!bytesEqual2(payment.script, prevOutScript)) {
|
|
1162
|
+
throw new AggregationCohortError(
|
|
1163
|
+
"Reconstructed beacon output script does not match the spent prevout script.",
|
|
1164
|
+
"PREVOUT_SCRIPT_MISMATCH"
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
const fallbackLeaf = buildFallbackLeaf({ cohortKeys, fallbackThreshold });
|
|
1168
|
+
const leafEntry = payment.tapLeafScript?.find(([, scriptVer]) => {
|
|
1169
|
+
const script = scriptVer.slice(0, scriptVer.length - 1);
|
|
1170
|
+
return bytesEqual2(script, fallbackLeaf);
|
|
1171
|
+
});
|
|
1172
|
+
if (!leafEntry) {
|
|
1173
|
+
throw new AggregationCohortError(
|
|
1174
|
+
"Could not locate the fallback leaf in the reconstructed beacon output.",
|
|
1175
|
+
"FALLBACK_LEAF_NOT_FOUND"
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
const cohortXOnly = (0, import_musig24.sortKeys)(cohortKeys).map((k) => k.slice(1));
|
|
1179
|
+
const tx = pendingTx;
|
|
1180
|
+
const sighash = fallbackSighash(tx, inputIndex, prevOutScript, prevOutValue, fallbackLeaf);
|
|
1181
|
+
const leafHash = tapLeafHash(fallbackLeaf);
|
|
1182
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
1183
|
+
for (const { pubKey, signature } of signatures) {
|
|
1184
|
+
if (pubKey.length !== 32 || signature.length !== 64) continue;
|
|
1185
|
+
const isCohortKey = cohortXOnly.some((k) => bytesEqual2(k, pubKey));
|
|
1186
|
+
if (!isCohortKey) continue;
|
|
1187
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1188
|
+
if (accepted.has(hex)) continue;
|
|
1189
|
+
let ok = false;
|
|
1190
|
+
try {
|
|
1191
|
+
ok = import_secp256k14.schnorr.verify(signature, sighash, pubKey);
|
|
1192
|
+
} catch {
|
|
1193
|
+
ok = false;
|
|
1194
|
+
}
|
|
1195
|
+
if (ok) accepted.set(hex, signature);
|
|
1196
|
+
}
|
|
1197
|
+
if (accepted.size < fallbackThreshold) {
|
|
1198
|
+
throw new AggregationCohortError(
|
|
1199
|
+
`Not enough valid fallback signatures: have ${accepted.size}, need ${fallbackThreshold}.`,
|
|
1200
|
+
"NOT_ENOUGH_FALLBACK_SIGNATURES",
|
|
1201
|
+
{ have: accepted.size, need: fallbackThreshold }
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
const chosen = cohortXOnly.filter((k) => accepted.has(Array.from(k, (b) => b.toString(16).padStart(2, "0")).join(""))).slice(0, fallbackThreshold);
|
|
1205
|
+
tx.updateInput(inputIndex, {
|
|
1206
|
+
tapLeafScript: [leafEntry],
|
|
1207
|
+
tapScriptSig: chosen.map((pubKey) => {
|
|
1208
|
+
const hex = Array.from(pubKey, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1209
|
+
return [{ pubKey, leafHash }, accepted.get(hex)];
|
|
1210
|
+
})
|
|
1211
|
+
});
|
|
1212
|
+
tx.finalize();
|
|
1213
|
+
return tx;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// src/core/typed-emitter.ts
|
|
1217
|
+
var TypedEventEmitter = class {
|
|
1218
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
1219
|
+
/** Subscribe to an event. */
|
|
1220
|
+
on(event, listener) {
|
|
1221
|
+
let set = this.#listeners.get(event);
|
|
1222
|
+
if (!set) {
|
|
1223
|
+
set = /* @__PURE__ */ new Set();
|
|
1224
|
+
this.#listeners.set(event, set);
|
|
1225
|
+
}
|
|
1226
|
+
set.add(listener);
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1229
|
+
/** Subscribe to an event, automatically unsubscribing after the first call. */
|
|
1230
|
+
once(event, listener) {
|
|
1231
|
+
const wrapped = ((...args) => {
|
|
1232
|
+
this.off(event, wrapped);
|
|
1233
|
+
listener(...args);
|
|
1234
|
+
});
|
|
1235
|
+
return this.on(event, wrapped);
|
|
1236
|
+
}
|
|
1237
|
+
/** Unsubscribe a specific listener. */
|
|
1238
|
+
off(event, listener) {
|
|
1239
|
+
this.#listeners.get(event)?.delete(listener);
|
|
1240
|
+
return this;
|
|
1241
|
+
}
|
|
1242
|
+
/** Emit an event to all subscribed listeners. */
|
|
1243
|
+
emit(event, ...args) {
|
|
1244
|
+
const set = this.#listeners.get(event);
|
|
1245
|
+
if (!set || set.size === 0) return false;
|
|
1246
|
+
for (const listener of set) {
|
|
1247
|
+
try {
|
|
1248
|
+
listener(...args);
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
console.error(`Listener for event "${String(event)}" threw:`, err);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
/** Remove all listeners (optionally for a specific event). */
|
|
1256
|
+
removeAllListeners(event) {
|
|
1257
|
+
if (event === void 0) {
|
|
1258
|
+
this.#listeners.clear();
|
|
1259
|
+
} else {
|
|
1260
|
+
this.#listeners.delete(event);
|
|
1261
|
+
}
|
|
1262
|
+
return this;
|
|
1263
|
+
}
|
|
1264
|
+
/** Number of listeners for an event. */
|
|
1265
|
+
listenerCount(event) {
|
|
1266
|
+
return this.#listeners.get(event)?.size ?? 0;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// src/core/messages/base.ts
|
|
1271
|
+
var AGGREGATION_WIRE_VERSION = 1;
|
|
1272
|
+
var BaseMessage = class {
|
|
1273
|
+
type;
|
|
1274
|
+
version;
|
|
1275
|
+
to;
|
|
1276
|
+
from;
|
|
1277
|
+
body;
|
|
1278
|
+
constructor({ type, version, to, from, body }) {
|
|
1279
|
+
this.type = type;
|
|
1280
|
+
this.version = version ?? AGGREGATION_WIRE_VERSION;
|
|
1281
|
+
this.to = to;
|
|
1282
|
+
this.from = from;
|
|
1283
|
+
this.body = body;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Converts a BaseMessage to a JSON object.
|
|
1287
|
+
* @returns {Base} The JSON representation of the BaseMessage.
|
|
1288
|
+
*/
|
|
1289
|
+
toJSON() {
|
|
1290
|
+
return {
|
|
1291
|
+
type: this.type,
|
|
1292
|
+
version: this.version,
|
|
1293
|
+
to: this.to,
|
|
1294
|
+
from: this.from,
|
|
1295
|
+
body: this.body
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
// src/core/messages/constants.ts
|
|
1301
|
+
var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
|
|
1302
|
+
var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
|
|
1303
|
+
var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
1304
|
+
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
1305
|
+
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
1306
|
+
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
1307
|
+
var SUBMIT_NONINCLUDED = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_nonincluded`;
|
|
1308
|
+
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
1309
|
+
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
1310
|
+
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
1311
|
+
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
1312
|
+
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
1313
|
+
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
1314
|
+
var FALLBACK_AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_authorization_request`;
|
|
1315
|
+
var FALLBACK_SIGNATURE = `${AGGREGATION_MESSAGE_PREFIX}/sign/fallback_signature`;
|
|
1316
|
+
|
|
1317
|
+
// src/core/messages/bodies.ts
|
|
1318
|
+
var hasStr = (b, k) => !!b && typeof b[k] === "string";
|
|
1319
|
+
var hasIntMin = (b, k, min) => {
|
|
1320
|
+
const v = b ? b[k] : void 0;
|
|
1321
|
+
return typeof v === "number" && Number.isInteger(v) && v >= min;
|
|
1322
|
+
};
|
|
1323
|
+
var optIntMin = (b, k, min) => {
|
|
1324
|
+
const v = b ? b[k] : void 0;
|
|
1325
|
+
return v === void 0 || typeof v === "number" && Number.isInteger(v) && v >= min;
|
|
1326
|
+
};
|
|
1327
|
+
var intInRange = (b, k, min, max) => {
|
|
1328
|
+
const v = b ? b[k] : void 0;
|
|
1329
|
+
return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
1330
|
+
};
|
|
1331
|
+
var optStrOneOf = (b, k, allowed) => {
|
|
1332
|
+
const v = b ? b[k] : void 0;
|
|
1333
|
+
return v === void 0 || typeof v === "string" && allowed.includes(v);
|
|
1334
|
+
};
|
|
1335
|
+
var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
|
|
1336
|
+
var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
|
|
1337
|
+
var hasBytesArray = (b, k) => {
|
|
1338
|
+
const v = b ? b[k] : void 0;
|
|
1339
|
+
return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
|
|
1340
|
+
};
|
|
1341
|
+
function isCohortAdvertMessage(m) {
|
|
1342
|
+
return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasIntMin(m.body, "minParticipants", 1) && optIntMin(m.body, "maxParticipants", 1) && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasStr(m.body, "recoveryKey") && intInRange(m.body, "recoverySequence", 1, MAX_RECOVERY_SEQUENCE) && optStrOneOf(m.body, "fundingModel", KNOWN_FUNDING_MODELS) && optIntMin(m.body, "fallbackThreshold", 1) && hasBytes(m.body, "communicationPk");
|
|
1343
|
+
}
|
|
1344
|
+
function isCohortOptInMessage(m) {
|
|
1345
|
+
return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
|
|
1346
|
+
}
|
|
1347
|
+
function isCohortOptInAcceptMessage(m) {
|
|
1348
|
+
return m.type === COHORT_OPT_IN_ACCEPT && hasStr(m.body, "cohortId");
|
|
1349
|
+
}
|
|
1350
|
+
function isCohortReadyMessage(m) {
|
|
1351
|
+
return m.type === COHORT_READY && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconAddress") && hasBytesArray(m.body, "cohortKeys");
|
|
1352
|
+
}
|
|
1353
|
+
function isSubmitUpdateMessage(m) {
|
|
1354
|
+
return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
|
|
1355
|
+
}
|
|
1356
|
+
function isSubmitNonIncludedMessage(m) {
|
|
1357
|
+
return m.type === SUBMIT_NONINCLUDED && hasStr(m.body, "cohortId");
|
|
1358
|
+
}
|
|
1359
|
+
function isDistributeAggregatedDataMessage(m) {
|
|
1360
|
+
return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
|
|
1361
|
+
}
|
|
1362
|
+
function isValidationAckMessage(m) {
|
|
1363
|
+
return m.type === VALIDATION_ACK && hasStr(m.body, "cohortId") && hasBool(m.body, "approved");
|
|
1364
|
+
}
|
|
1365
|
+
function isAuthorizationRequestMessage(m) {
|
|
1366
|
+
return m.type === AUTHORIZATION_REQUEST && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasStr(m.body, "pendingTx") && hasStr(m.body, "prevOutScriptHex") && hasStr(m.body, "prevOutValue");
|
|
1367
|
+
}
|
|
1368
|
+
function isNonceContributionMessage(m) {
|
|
1369
|
+
return m.type === NONCE_CONTRIBUTION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "nonceContribution");
|
|
1370
|
+
}
|
|
1371
|
+
function isAggregatedNonceMessage(m) {
|
|
1372
|
+
return m.type === AGGREGATED_NONCE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "aggregatedNonce");
|
|
1373
|
+
}
|
|
1374
|
+
function isSignatureAuthorizationMessage(m) {
|
|
1375
|
+
return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
|
|
1376
|
+
}
|
|
1377
|
+
function isFallbackAuthorizationRequestMessage(m) {
|
|
1378
|
+
return m.type === FALLBACK_AUTHORIZATION_REQUEST && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasStr(m.body, "pendingTx") && hasStr(m.body, "prevOutScriptHex") && hasStr(m.body, "prevOutValue") && hasStr(m.body, "fallbackLeafScriptHex");
|
|
1379
|
+
}
|
|
1380
|
+
function isFallbackSignatureMessage(m) {
|
|
1381
|
+
return m.type === FALLBACK_SIGNATURE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "signerPk") && hasBytes(m.body, "fallbackSignature");
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/core/messages/factories.ts
|
|
1385
|
+
function createCohortAdvertMessage(fields) {
|
|
1386
|
+
const { from, ...body } = fields;
|
|
1387
|
+
return new BaseMessage({ type: COHORT_ADVERT, from, body });
|
|
1388
|
+
}
|
|
1389
|
+
function createCohortOptInMessage(fields) {
|
|
1390
|
+
const { from, to, ...body } = fields;
|
|
1391
|
+
return new BaseMessage({ type: COHORT_OPT_IN, from, to, body });
|
|
1392
|
+
}
|
|
1393
|
+
function createCohortOptInAcceptMessage(fields) {
|
|
1394
|
+
const { from, to, ...body } = fields;
|
|
1395
|
+
return new BaseMessage({ type: COHORT_OPT_IN_ACCEPT, from, to, body });
|
|
1396
|
+
}
|
|
1397
|
+
function createCohortReadyMessage(fields) {
|
|
1398
|
+
const { from, to, ...body } = fields;
|
|
1399
|
+
return new BaseMessage({ type: COHORT_READY, from, to, body });
|
|
1400
|
+
}
|
|
1401
|
+
function createSubmitUpdateMessage(fields) {
|
|
1402
|
+
const { from, to, ...body } = fields;
|
|
1403
|
+
return new BaseMessage({ type: SUBMIT_UPDATE, from, to, body });
|
|
1404
|
+
}
|
|
1405
|
+
function createSubmitNonIncludedMessage(fields) {
|
|
1406
|
+
const { from, to, ...body } = fields;
|
|
1407
|
+
return new BaseMessage({ type: SUBMIT_NONINCLUDED, from, to, body });
|
|
1408
|
+
}
|
|
1409
|
+
function createDistributeAggregatedDataMessage(fields) {
|
|
1410
|
+
const { from, to, ...body } = fields;
|
|
1411
|
+
return new BaseMessage({ type: DISTRIBUTE_AGGREGATED_DATA, from, to, body });
|
|
1412
|
+
}
|
|
1413
|
+
function createValidationAckMessage(fields) {
|
|
1414
|
+
const { from, to, ...body } = fields;
|
|
1415
|
+
return new BaseMessage({ type: VALIDATION_ACK, from, to, body });
|
|
1416
|
+
}
|
|
1417
|
+
function createAuthorizationRequestMessage(fields) {
|
|
1418
|
+
const { from, to, ...body } = fields;
|
|
1419
|
+
return new BaseMessage({ type: AUTHORIZATION_REQUEST, from, to, body });
|
|
1420
|
+
}
|
|
1421
|
+
function createNonceContributionMessage(fields) {
|
|
1422
|
+
const { from, to, ...body } = fields;
|
|
1423
|
+
return new BaseMessage({ type: NONCE_CONTRIBUTION, from, to, body });
|
|
1424
|
+
}
|
|
1425
|
+
function createAggregatedNonceMessage(fields) {
|
|
1426
|
+
const { from, to, ...body } = fields;
|
|
1427
|
+
return new BaseMessage({ type: AGGREGATED_NONCE, from, to, body });
|
|
1428
|
+
}
|
|
1429
|
+
function createSignatureAuthorizationMessage(fields) {
|
|
1430
|
+
const { from, to, ...body } = fields;
|
|
1431
|
+
return new BaseMessage({ type: SIGNATURE_AUTHORIZATION, from, to, body });
|
|
1432
|
+
}
|
|
1433
|
+
function createFallbackAuthorizationRequestMessage(fields) {
|
|
1434
|
+
const { from, to, ...body } = fields;
|
|
1435
|
+
return new BaseMessage({ type: FALLBACK_AUTHORIZATION_REQUEST, from, to, body });
|
|
1436
|
+
}
|
|
1437
|
+
function createFallbackSignatureMessage(fields) {
|
|
1438
|
+
const { from, to, ...body } = fields;
|
|
1439
|
+
return new BaseMessage({ type: FALLBACK_SIGNATURE, from, to, body });
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/core/messages/guards.ts
|
|
1443
|
+
var KEYGEN_VALUES = /* @__PURE__ */ new Set([
|
|
1444
|
+
COHORT_ADVERT,
|
|
1445
|
+
COHORT_OPT_IN,
|
|
1446
|
+
COHORT_OPT_IN_ACCEPT,
|
|
1447
|
+
COHORT_READY
|
|
1448
|
+
]);
|
|
1449
|
+
var UPDATE_VALUES = /* @__PURE__ */ new Set([
|
|
1450
|
+
SUBMIT_UPDATE,
|
|
1451
|
+
SUBMIT_NONINCLUDED,
|
|
1452
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
1453
|
+
VALIDATION_ACK
|
|
1454
|
+
]);
|
|
1455
|
+
var SIGN_VALUES = /* @__PURE__ */ new Set([
|
|
1456
|
+
AUTHORIZATION_REQUEST,
|
|
1457
|
+
NONCE_CONTRIBUTION,
|
|
1458
|
+
AGGREGATED_NONCE,
|
|
1459
|
+
SIGNATURE_AUTHORIZATION,
|
|
1460
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
1461
|
+
FALLBACK_SIGNATURE
|
|
1462
|
+
]);
|
|
1463
|
+
function isAggregationMessageType(type) {
|
|
1464
|
+
return KEYGEN_VALUES.has(type) || UPDATE_VALUES.has(type) || SIGN_VALUES.has(type);
|
|
1465
|
+
}
|
|
1466
|
+
function isKeygenMessageType(type) {
|
|
1467
|
+
return KEYGEN_VALUES.has(type);
|
|
1468
|
+
}
|
|
1469
|
+
function isUpdateMessageType(type) {
|
|
1470
|
+
return UPDATE_VALUES.has(type);
|
|
1471
|
+
}
|
|
1472
|
+
function isSignMessageType(type) {
|
|
1473
|
+
return SIGN_VALUES.has(type);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// src/core/transport/error.ts
|
|
1477
|
+
var import_common4 = require("@did-btcr2/common");
|
|
1478
|
+
var TransportError = class extends import_common4.MethodError {
|
|
1479
|
+
constructor(message, type = "TransportError", data) {
|
|
1480
|
+
super(message, type, data);
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
var TransportAdapterError = class extends import_common4.MethodError {
|
|
1484
|
+
constructor(message, type = "TransportAdapterError", data) {
|
|
1485
|
+
super(message, type, data);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
// src/core/transport/in-memory.ts
|
|
1490
|
+
var import_utils3 = require("@noble/hashes/utils");
|
|
1491
|
+
var InMemoryBus = class {
|
|
1492
|
+
#transports = /* @__PURE__ */ new Set();
|
|
1493
|
+
/** Attach a transport to this bus. Called by the transport's constructor. */
|
|
1494
|
+
register(transport) {
|
|
1495
|
+
this.#transports.add(transport);
|
|
1496
|
+
}
|
|
1497
|
+
/** Detach a transport from this bus. */
|
|
1498
|
+
unregister(transport) {
|
|
1499
|
+
this.#transports.delete(transport);
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Deliver a message. With no `recipient` the message is broadcast to every
|
|
1503
|
+
* actor on the bus; otherwise it is routed to the single transport that owns
|
|
1504
|
+
* the recipient DID.
|
|
1505
|
+
*/
|
|
1506
|
+
async deliver(message, _sender, recipient) {
|
|
1507
|
+
const type = message.type;
|
|
1508
|
+
if (!type) return;
|
|
1509
|
+
const replacer = (_k, v) => v instanceof Uint8Array ? { __bytes: (0, import_utils3.bytesToHex)(v) } : v;
|
|
1510
|
+
const reviver = (_k, v) => v && typeof v === "object" && "__bytes" in v ? (0, import_utils3.hexToBytes)(v.__bytes) : v;
|
|
1511
|
+
const raw = JSON.parse(JSON.stringify(message, replacer), reviver);
|
|
1512
|
+
const serialized = { ...raw, ...raw.body ?? {} };
|
|
1513
|
+
if (!recipient) {
|
|
1514
|
+
for (const t of this.#transports) {
|
|
1515
|
+
await t.dispatchBroadcast(type, serialized);
|
|
1516
|
+
}
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
for (const t of this.#transports) {
|
|
1520
|
+
if (t.hasActor(recipient)) {
|
|
1521
|
+
await t.dispatchDirected(recipient, type, serialized);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
var InMemoryTransport = class {
|
|
1528
|
+
name = "in-memory";
|
|
1529
|
+
bus;
|
|
1530
|
+
#actors = /* @__PURE__ */ new Map();
|
|
1531
|
+
#peers = /* @__PURE__ */ new Map();
|
|
1532
|
+
/** @param bus Shared bus. Pass the same bus to connect multiple transports. */
|
|
1533
|
+
constructor(bus = new InMemoryBus()) {
|
|
1534
|
+
this.bus = bus;
|
|
1535
|
+
this.bus.register(this);
|
|
1536
|
+
}
|
|
1537
|
+
start() {
|
|
1538
|
+
}
|
|
1539
|
+
registerActor(did, keys) {
|
|
1540
|
+
this.#actors.set(did, { publicKey: keys.publicKey.compressed, handlers: /* @__PURE__ */ new Map() });
|
|
1541
|
+
}
|
|
1542
|
+
getActorPk(did) {
|
|
1543
|
+
return this.#actors.get(did)?.publicKey;
|
|
1544
|
+
}
|
|
1545
|
+
/** True if `did` is registered on this transport. Used by the bus for routing. */
|
|
1546
|
+
hasActor(did) {
|
|
1547
|
+
return this.#actors.has(did);
|
|
1548
|
+
}
|
|
1549
|
+
registerPeer(did, communicationPk) {
|
|
1550
|
+
this.#peers.set(did, communicationPk);
|
|
1551
|
+
}
|
|
1552
|
+
getPeerPk(did) {
|
|
1553
|
+
return this.#peers.get(did);
|
|
1554
|
+
}
|
|
1555
|
+
registerMessageHandler(actorDid, messageType, handler) {
|
|
1556
|
+
const actor = this.#actors.get(actorDid);
|
|
1557
|
+
if (actor) actor.handlers.set(messageType, handler);
|
|
1558
|
+
}
|
|
1559
|
+
unregisterMessageHandler(actorDid, messageType) {
|
|
1560
|
+
const actor = this.#actors.get(actorDid);
|
|
1561
|
+
if (actor) actor.handlers.delete(messageType);
|
|
1562
|
+
}
|
|
1563
|
+
unregisterActor(did) {
|
|
1564
|
+
const actor = this.#actors.get(did);
|
|
1565
|
+
if (!actor) return;
|
|
1566
|
+
actor.handlers.clear();
|
|
1567
|
+
this.#actors.delete(did);
|
|
1568
|
+
this.#peers.delete(did);
|
|
1569
|
+
}
|
|
1570
|
+
async sendMessage(message, sender, recipient) {
|
|
1571
|
+
await this.bus.deliver(message, sender, recipient);
|
|
1572
|
+
}
|
|
1573
|
+
publishRepeating(message, sender, intervalMs, recipient) {
|
|
1574
|
+
let stopped = false;
|
|
1575
|
+
void this.sendMessage(message, sender, recipient).catch(() => {
|
|
1576
|
+
});
|
|
1577
|
+
const timer = setInterval(() => {
|
|
1578
|
+
if (stopped) return;
|
|
1579
|
+
void this.sendMessage(message, sender, recipient).catch(() => {
|
|
1580
|
+
});
|
|
1581
|
+
}, intervalMs);
|
|
1582
|
+
return () => {
|
|
1583
|
+
if (stopped) return;
|
|
1584
|
+
stopped = true;
|
|
1585
|
+
clearInterval(timer);
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
/** Deliver a broadcast message to every actor on this transport that handles `type`. */
|
|
1589
|
+
async dispatchBroadcast(type, message) {
|
|
1590
|
+
for (const actor of this.#actors.values()) {
|
|
1591
|
+
const handler = actor.handlers.get(type);
|
|
1592
|
+
if (handler) await handler(message);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
/** Deliver a directed message to the recipient actor's handler for `type`. */
|
|
1596
|
+
async dispatchDirected(recipientDid, type, message) {
|
|
1597
|
+
const handler = this.#actors.get(recipientDid)?.handlers.get(type);
|
|
1598
|
+
if (handler) await handler(message);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
// src/core/transport/nostr.ts
|
|
1603
|
+
var import_keypair3 = require("@did-btcr2/keypair");
|
|
1604
|
+
var import_utils4 = require("@noble/hashes/utils");
|
|
1605
|
+
var import_nostr_tools = require("nostr-tools");
|
|
1606
|
+
var import_pool = require("nostr-tools/pool");
|
|
1607
|
+
var DEFAULT_NOSTR_RELAYS = [
|
|
1608
|
+
"wss://relay.damus.io",
|
|
1609
|
+
"wss://nos.lol",
|
|
1610
|
+
"wss://relay.snort.social",
|
|
1611
|
+
"wss://nostr-pub.wellorder.net"
|
|
1612
|
+
];
|
|
1613
|
+
var DEFAULT_BROADCAST_LOOKBACK_MS = 5 * 60 * 1e3;
|
|
1614
|
+
var NostrTransport = class _NostrTransport {
|
|
1615
|
+
name = "nostr";
|
|
1616
|
+
pool;
|
|
1617
|
+
#relays;
|
|
1618
|
+
#actors = /* @__PURE__ */ new Map();
|
|
1619
|
+
#peerRegistry = /* @__PURE__ */ new Map();
|
|
1620
|
+
#started = false;
|
|
1621
|
+
#logger;
|
|
1622
|
+
#broadcastLookbackMs;
|
|
1623
|
+
constructor(config) {
|
|
1624
|
+
this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
|
|
1625
|
+
this.#logger = config?.logger ?? CONSOLE_LOGGER;
|
|
1626
|
+
this.#broadcastLookbackMs = config?.broadcastLookbackMs ?? DEFAULT_BROADCAST_LOOKBACK_MS;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Registers an actor (DID + keys) to send/receive messages with.
|
|
1630
|
+
* Must be called before start() to ensure subscriptions are created for the actor.
|
|
1631
|
+
* @param {string} did - The DID of the actor.
|
|
1632
|
+
* @param {SchnorrKeyPair} keys - The Schnorr key pair for the actor.
|
|
1633
|
+
* @throws {TransportAdapterError} If the actor is already registered or if the transport has already started.
|
|
1634
|
+
* @example
|
|
1635
|
+
* const transport = new NostrTransport();
|
|
1636
|
+
* const keys = SchnorrKeyPair.generate();
|
|
1637
|
+
* const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
|
|
1638
|
+
* transport.registerActor(did, keys);
|
|
1639
|
+
* transport.start();
|
|
1640
|
+
*/
|
|
1641
|
+
registerActor(did, keys) {
|
|
1642
|
+
const entry = { keys, handlers: /* @__PURE__ */ new Map(), subscriptions: [] };
|
|
1643
|
+
this.#actors.set(did, entry);
|
|
1644
|
+
if (this.#started && this.pool) {
|
|
1645
|
+
this.#subscribeDirected(did, entry);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Detach an actor: drop its handlers, close its relay subscriptions, and remove its keys + peer
|
|
1650
|
+
* mapping. Idempotent.
|
|
1651
|
+
* @param {string} did - The DID of the actor to unregister.
|
|
1652
|
+
* @returns {void}
|
|
1653
|
+
* @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
|
|
1654
|
+
* @example
|
|
1655
|
+
* transport.unregisterActor(did);
|
|
1656
|
+
*/
|
|
1657
|
+
unregisterActor(did) {
|
|
1658
|
+
const entry = this.#actors.get(did);
|
|
1659
|
+
if (!entry) return;
|
|
1660
|
+
for (const sub of entry.subscriptions) {
|
|
1661
|
+
try {
|
|
1662
|
+
sub.close();
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
this.#logger.debug(`Error closing subscription for ${did}:`, err);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
entry.subscriptions.length = 0;
|
|
1668
|
+
entry.handlers.clear();
|
|
1669
|
+
this.#actors.delete(did);
|
|
1670
|
+
this.#peerRegistry.delete(did);
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Remove a single (actor, messageType) handler. Idempotent.
|
|
1674
|
+
* @param {string} actorDid - The DID of the actor.
|
|
1675
|
+
* @param {string} messageType - The type of message to unregister the handler for.
|
|
1676
|
+
* @returns {void}
|
|
1677
|
+
* @example
|
|
1678
|
+
* transport.unregisterMessageHandler(actorDid, messageType);
|
|
1679
|
+
*/
|
|
1680
|
+
unregisterMessageHandler(actorDid, messageType) {
|
|
1681
|
+
const actor = this.#actors.get(actorDid);
|
|
1682
|
+
if (!actor) return;
|
|
1683
|
+
actor.handlers.delete(messageType);
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Gets the public key for a registered actor by their DID.
|
|
1687
|
+
* @param {string} did - The DID of the registered actor to get the public key for.
|
|
1688
|
+
* @returns {Uint8Array | undefined} The compressed public key bytes for the actor's DID, or
|
|
1689
|
+
* undefined if the DID is not registered.
|
|
1690
|
+
*/
|
|
1691
|
+
getActorPk(did) {
|
|
1692
|
+
return this.#actors.get(did)?.keys.publicKey.compressed;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Registers a peer's communication public key for encrypted messages.
|
|
1696
|
+
* @param {string} did - The DID of the peer to register.
|
|
1697
|
+
* @param {Uint8Array} communicationPk - The compressed secp256k1 public key bytes for the peer.
|
|
1698
|
+
*/
|
|
1699
|
+
registerPeer(did, communicationPk) {
|
|
1700
|
+
try {
|
|
1701
|
+
new import_keypair3.CompressedSecp256k1PublicKey(communicationPk);
|
|
1702
|
+
} catch {
|
|
1703
|
+
throw new TransportAdapterError(
|
|
1704
|
+
`Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
|
|
1705
|
+
"INVALID_PEER_KEY",
|
|
1706
|
+
{ adapter: this.name, did, keyLength: communicationPk.length }
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
this.#peerRegistry.set(did, communicationPk);
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Gets the registered communication public key for a peer by their DID.
|
|
1713
|
+
* @param {string} did - The DID of the peer to get the communication public key for.
|
|
1714
|
+
* @returns {Uint8Array | undefined} The compressed secp256k1 public key bytes for the peer, or
|
|
1715
|
+
* undefined if the peer is not registered.
|
|
1716
|
+
*/
|
|
1717
|
+
getPeerPk(did) {
|
|
1718
|
+
return this.#peerRegistry.get(did);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Registers a message handler function for a specific actor and message type. The handler will be called
|
|
1722
|
+
* when a message of the specified type is received for the actor's DID. The transport must have been
|
|
1723
|
+
* started for handlers to be invoked. If the transport is already started, the handler will be registered
|
|
1724
|
+
* immediately; otherwise, it will be registered when the transport starts and the actor's subscription is created.
|
|
1725
|
+
* @param {string} actorDid - The DID of the actor to register the message handler for.
|
|
1726
|
+
* @param {string} messageType - The type of message to handle.
|
|
1727
|
+
* @param {MessageHandler} handler - The function to handle incoming messages of the specified type.
|
|
1728
|
+
* @throws {TransportAdapterError} If the actor DID is not registered or if the handler is invalid.
|
|
1729
|
+
*/
|
|
1730
|
+
registerMessageHandler(actorDid, messageType, handler) {
|
|
1731
|
+
const actor = this.#actors.get(actorDid);
|
|
1732
|
+
if (!actor) {
|
|
1733
|
+
throw new TransportAdapterError(
|
|
1734
|
+
`Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
|
|
1735
|
+
"UNKNOWN_ACTOR_ERROR",
|
|
1736
|
+
{ adapter: this.name, did: actorDid }
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
actor.handlers.set(messageType, handler);
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Starts the transport by connecting to the configured Nostr relays and setting up subscriptions
|
|
1743
|
+
* for all registered actors. This method must be called after registering actors via registerActor()
|
|
1744
|
+
* and before sending or receiving messages. The transport will subscribe to broadcast events (kind 1)
|
|
1745
|
+
* for cohort adverts and directed events (kinds 1 and 1059) for each registered actor based on their
|
|
1746
|
+
* public keys. Incoming events are processed and dispatched to the appropriate handlers based on
|
|
1747
|
+
* message type. If the transport is already started, this method has no effect.
|
|
1748
|
+
* @returns {NostrTransport}
|
|
1749
|
+
*/
|
|
1750
|
+
start() {
|
|
1751
|
+
if (this.#started) return this;
|
|
1752
|
+
this.#started = true;
|
|
1753
|
+
this.pool = new import_pool.SimplePool();
|
|
1754
|
+
const broadcastFilter = {
|
|
1755
|
+
kinds: [1],
|
|
1756
|
+
"#t": [COHORT_ADVERT]
|
|
1757
|
+
};
|
|
1758
|
+
if (this.#broadcastLookbackMs > 0) {
|
|
1759
|
+
broadcastFilter.since = Math.floor((Date.now() - this.#broadcastLookbackMs) / 1e3);
|
|
1760
|
+
}
|
|
1761
|
+
this.pool.subscribeMany(this.#relays, broadcastFilter, {
|
|
1762
|
+
onclose: (reasons) => this.#logger.debug("Nostr broadcast subscription closed", reasons),
|
|
1763
|
+
onevent: this.#handleBroadcastEvent.bind(this)
|
|
1764
|
+
});
|
|
1765
|
+
for (const [did, entry] of this.#actors) {
|
|
1766
|
+
this.#subscribeDirected(did, entry);
|
|
1767
|
+
}
|
|
1768
|
+
this.#logger.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
|
|
1769
|
+
return this;
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Sends a message by publishing a Nostr event to the configured relays. The message is serialized
|
|
1773
|
+
* as JSON and included in the event content.
|
|
1774
|
+
* @param {BaseMessage} message - The aggregation message to send. Must include a valid `type` property.
|
|
1775
|
+
* @param {Did} sender - The DID of the registered actor sending the message. Must have been
|
|
1776
|
+
* registered via registerActor().
|
|
1777
|
+
* @param {Did} [to] - Optional recipient DID for directed messages. Required for encrypted message
|
|
1778
|
+
* types. If provided, must have been registered via registerPeer().
|
|
1779
|
+
* @returns {Promise<void>} Resolves when the message has been published to the relays. Note that
|
|
1780
|
+
* publication is best-effort: the method will resolve as long as at least one relay accepts the
|
|
1781
|
+
* event, even if others reject it.
|
|
1782
|
+
*/
|
|
1783
|
+
async sendMessage(message, sender, to) {
|
|
1784
|
+
const type = message.type;
|
|
1785
|
+
if (!type) {
|
|
1786
|
+
throw new TransportAdapterError(
|
|
1787
|
+
"Message type is undefined",
|
|
1788
|
+
"SEND_MESSAGE_ERROR",
|
|
1789
|
+
{ adapter: this.name, type }
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
const actor = this.#actors.get(sender);
|
|
1793
|
+
if (!actor) {
|
|
1794
|
+
throw new TransportAdapterError(
|
|
1795
|
+
`Unknown sender: ${sender}. Call registerActor() before sending messages.`,
|
|
1796
|
+
"UNKNOWN_ACTOR_ERROR",
|
|
1797
|
+
{ adapter: this.name, did: sender }
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
const senderKeys = actor.keys;
|
|
1801
|
+
const tags = [
|
|
1802
|
+
["p", (0, import_utils4.bytesToHex)(senderKeys.publicKey.x)],
|
|
1803
|
+
["t", type]
|
|
1804
|
+
];
|
|
1805
|
+
if (to) {
|
|
1806
|
+
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
1807
|
+
if (recipientPkBytes) {
|
|
1808
|
+
const recipientPk = new import_keypair3.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
1809
|
+
tags.push(["p", (0, import_utils4.bytesToHex)(recipientPk.x)]);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (isKeygenMessageType(type)) {
|
|
1813
|
+
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
1814
|
+
kind: 1,
|
|
1815
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
1816
|
+
tags,
|
|
1817
|
+
content: JSON.stringify(message, _NostrTransport.#jsonReplacer)
|
|
1818
|
+
}, senderKeys.secretKey.bytes);
|
|
1819
|
+
this.#logger.debug(`Publishing kind 1 [${type}]`);
|
|
1820
|
+
await this.#publishToRelays(event);
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
if (isUpdateMessageType(type) || isSignMessageType(type)) {
|
|
1824
|
+
if (!to) {
|
|
1825
|
+
throw new TransportAdapterError(
|
|
1826
|
+
`Encrypted messages require a recipient DID, got undefined for type: ${type}`,
|
|
1827
|
+
"SEND_MESSAGE_ERROR",
|
|
1828
|
+
{ adapter: this.name }
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
1832
|
+
if (!recipientPkBytes) {
|
|
1833
|
+
throw new TransportAdapterError(
|
|
1834
|
+
`Unknown peer DID: ${to}. Register peer via registerPeer() before sending encrypted messages.`,
|
|
1835
|
+
"UNKNOWN_PEER_ERROR",
|
|
1836
|
+
{ adapter: this.name, did: to }
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
const recipientPk = new import_keypair3.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
1840
|
+
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
1841
|
+
senderKeys.secretKey.bytes,
|
|
1842
|
+
(0, import_utils4.bytesToHex)(recipientPk.x)
|
|
1843
|
+
);
|
|
1844
|
+
const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
|
|
1845
|
+
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
1846
|
+
kind: 1059,
|
|
1847
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
1848
|
+
tags,
|
|
1849
|
+
content
|
|
1850
|
+
}, senderKeys.secretKey.bytes);
|
|
1851
|
+
this.#logger.debug(`Publishing kind 1059 [${type}]`);
|
|
1852
|
+
await this.#publishToRelays(event);
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
this.#logger.warn(`Unsupported message type: ${type}`);
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Publish the message once immediately and then re-publish on an interval
|
|
1859
|
+
* until the returned stop function is invoked.
|
|
1860
|
+
*
|
|
1861
|
+
* Useful for broadcast messages (COHORT_ADVERT) on relays that don't
|
|
1862
|
+
* backfill historical events to late subscribers: republishing gives late
|
|
1863
|
+
* joiners a window to discover the message without requiring protocol
|
|
1864
|
+
* changes. Relay rate-limit / publish failures inside the interval are
|
|
1865
|
+
* caught and logged rather than propagated - the caller should stop the
|
|
1866
|
+
* repeater once the protocol condition is satisfied.
|
|
1867
|
+
*/
|
|
1868
|
+
publishRepeating(message, sender, intervalMs, recipient) {
|
|
1869
|
+
let stopped = false;
|
|
1870
|
+
void this.sendMessage(message, sender, recipient).catch((err) => {
|
|
1871
|
+
this.#logger.debug("publishRepeating first send failed:", err);
|
|
1872
|
+
});
|
|
1873
|
+
const timer = setInterval(() => {
|
|
1874
|
+
if (stopped) return;
|
|
1875
|
+
void this.sendMessage(message, sender, recipient).catch((err) => {
|
|
1876
|
+
this.#logger.debug("publishRepeating retry failed:", err);
|
|
1877
|
+
});
|
|
1878
|
+
}, intervalMs);
|
|
1879
|
+
return () => {
|
|
1880
|
+
if (stopped) return;
|
|
1881
|
+
stopped = true;
|
|
1882
|
+
clearInterval(timer);
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Creates a directed subscription for the given actor, filtering for messages that match the
|
|
1887
|
+
* actor's public key. Messages received on this subscription are dispatched to the actor's
|
|
1888
|
+
* registered handlers based on message type.
|
|
1889
|
+
* @param {string} did - The DID of the actor to create the subscription for.
|
|
1890
|
+
* @param {ActorEntry} entry - The actor's registration entry containing keys and handlers.
|
|
1891
|
+
* @returns {void}
|
|
1892
|
+
* @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
|
|
1893
|
+
*/
|
|
1894
|
+
#subscribeDirected(did, entry) {
|
|
1895
|
+
if (!this.pool) return;
|
|
1896
|
+
const pkHex = (0, import_utils4.bytesToHex)(entry.keys.publicKey.x);
|
|
1897
|
+
const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
|
|
1898
|
+
onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
1899
|
+
onevent: this.#makeActorEventHandler(did)
|
|
1900
|
+
});
|
|
1901
|
+
entry.subscriptions.push(sub);
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Creates an event handler function for a specific actor that processes incoming events, decrypts
|
|
1905
|
+
* if necessary, and dispatches messages to the actor's registered handlers based on message type.
|
|
1906
|
+
* @param {string} actorDid - The DID of the actor to create the event handler for.
|
|
1907
|
+
* @returns {(event: Event) => Promise<void>} An asynchronous event handler function that
|
|
1908
|
+
* processes incoming events for the specified actor.
|
|
1909
|
+
*/
|
|
1910
|
+
#makeActorEventHandler(actorDid) {
|
|
1911
|
+
return async (event) => {
|
|
1912
|
+
const actor = this.#actors.get(actorDid);
|
|
1913
|
+
if (!actor) return;
|
|
1914
|
+
if (event.pubkey === (0, import_utils4.bytesToHex)(actor.keys.publicKey.x)) return;
|
|
1915
|
+
let message;
|
|
1916
|
+
try {
|
|
1917
|
+
if (event.kind === 1) {
|
|
1918
|
+
message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
|
|
1919
|
+
} else if (event.kind === 1059) {
|
|
1920
|
+
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
1921
|
+
actor.keys.secretKey.bytes,
|
|
1922
|
+
event.pubkey
|
|
1923
|
+
);
|
|
1924
|
+
const plaintext = import_nostr_tools.nip44.v2.decrypt(event.content, conversationKey);
|
|
1925
|
+
message = JSON.parse(plaintext, _NostrTransport.#jsonReviver);
|
|
1926
|
+
} else {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
this.#logger.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
this.#dispatchMessage(message, actor);
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Handles incoming broadcast events (kind 1) by parsing the event content, validating it as an
|
|
1938
|
+
* aggregation message, and dispatching it to all registered actors that have handlers for the
|
|
1939
|
+
* message type. This is used for COHORT_ADVERT messages that need to be received by all actors
|
|
1940
|
+
* regardless of DID.
|
|
1941
|
+
* @param {Event} event - The Nostr event to handle, expected to be a kind 1 broadcast containing
|
|
1942
|
+
* a COHORT_ADVERT message. The event content is parsed and dispatched to all registered actors
|
|
1943
|
+
* that have handlers for the
|
|
1944
|
+
* @returns
|
|
1945
|
+
*/
|
|
1946
|
+
async #handleBroadcastEvent(event) {
|
|
1947
|
+
if (event.kind !== 1) return;
|
|
1948
|
+
let message;
|
|
1949
|
+
try {
|
|
1950
|
+
message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
this.#logger.debug(`Failed to parse broadcast event ${event.id}:`, err);
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
if (message.body && typeof message.body === "object") {
|
|
1956
|
+
message = { ...message, ...message.body };
|
|
1957
|
+
}
|
|
1958
|
+
const messageType = message.type;
|
|
1959
|
+
if (!messageType || !isAggregationMessageType(messageType)) return;
|
|
1960
|
+
for (const actor of this.#actors.values()) {
|
|
1961
|
+
const handler = actor.handlers.get(messageType);
|
|
1962
|
+
if (handler) await handler(message);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Dispatches a parsed message to the appropriate handler of a registered actor based on message type.
|
|
1967
|
+
* The message is expected to have already been parsed from the Nostr event content and validated as
|
|
1968
|
+
* an aggregation message. If the message has a body, its properties are merged into the top-level
|
|
1969
|
+
* message object for easier handler access. The message is then dispatched to the handler registered
|
|
1970
|
+
* for its type, if one exists.
|
|
1971
|
+
* @param {Record<string, unknown>} message - The message object parsed from a Nostr event, expected to
|
|
1972
|
+
* @param {ActorEntry} actor - The registered actor entry containing keys and handlers to dispatch the message to.
|
|
1973
|
+
* @returns {void}
|
|
1974
|
+
* @throws {TransportAdapterError} If the message type is unsupported or if no handler is registered
|
|
1975
|
+
* for the message type.
|
|
1976
|
+
*/
|
|
1977
|
+
#dispatchMessage(message, actor) {
|
|
1978
|
+
if (message.body && typeof message.body === "object") {
|
|
1979
|
+
message = { ...message, ...message.body };
|
|
1980
|
+
}
|
|
1981
|
+
const messageType = message.type;
|
|
1982
|
+
if (!messageType || !isAggregationMessageType(messageType)) return;
|
|
1983
|
+
const handler = actor.handlers.get(messageType);
|
|
1984
|
+
if (handler) handler(message);
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Publishes a Nostr event to the configured relays and handles the results. The method waits for all
|
|
1988
|
+
* relay promises to settle and checks how many accepted or rejected the event. If all relays reject the event,
|
|
1989
|
+
* an error is thrown. Otherwise, the method completes successfully even if some relays rejected the event,
|
|
1990
|
+
* as long as at least one relay accepted it. Relay rejections are logged for debugging purposes.
|
|
1991
|
+
* @param {Event} event - The Nostr event to publish to the configured relays. The event should already
|
|
1992
|
+
* @returns {Promise<void>} A promise that resolves if the event was accepted by at least one relay, or rejects
|
|
1993
|
+
* with a TransportAdapterError if all relays rejected the event.
|
|
1994
|
+
* @throws {TransportAdapterError} If the pool is not initialized or if all relays reject the event.
|
|
1995
|
+
*/
|
|
1996
|
+
async #publishToRelays(event) {
|
|
1997
|
+
const relayPromises = this.pool?.publish(this.#relays, event);
|
|
1998
|
+
if (!relayPromises?.length) return;
|
|
1999
|
+
const results = await Promise.allSettled(relayPromises);
|
|
2000
|
+
const accepted = results.filter((r) => r.status === "fulfilled").length;
|
|
2001
|
+
const rejected = results.filter((r) => r.status === "rejected");
|
|
2002
|
+
for (const r of rejected) {
|
|
2003
|
+
this.#logger.debug(`Relay rejected event ${event.id}: ${r.reason}`);
|
|
2004
|
+
}
|
|
2005
|
+
if (accepted === 0) {
|
|
2006
|
+
throw new TransportAdapterError(
|
|
2007
|
+
`All ${results.length} relay(s) rejected event ${event.id}`,
|
|
2008
|
+
"PUBLISH_ERROR",
|
|
2009
|
+
{ adapter: this.name, reasons: rejected.map((r) => String(r.reason)) }
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Custom JSON replacer to handle serialization of Uint8Array values as hex strings in message
|
|
2015
|
+
* content. This allows messages containing binary data (e.g. public keys, signatures) to be correctly
|
|
2016
|
+
* serialized to JSON for Nostr event content. The replacer checks if a value is a Uint8Array and, if so,
|
|
2017
|
+
* converts it to a hex string wrapped in an object with a __bytes property. The corresponding reviver
|
|
2018
|
+
* can then convert this back to a Uint8Array when parsing the message content from the event.
|
|
2019
|
+
* @param {string} _key - The key of the property being processed.
|
|
2020
|
+
* @param {unknown} value - The value to check if the message type is valid.
|
|
2021
|
+
* @returns {unknown} The transformed value for JSON serialization. If the value is a Uint8Array,
|
|
2022
|
+
* it returns an object with a __bytes property containing the hex string. Otherwise, it returns
|
|
2023
|
+
* the value unchanged.
|
|
2024
|
+
*/
|
|
2025
|
+
static #jsonReplacer(_key, value) {
|
|
2026
|
+
if (value instanceof Uint8Array) {
|
|
2027
|
+
return { __bytes: (0, import_utils4.bytesToHex)(value) };
|
|
2028
|
+
}
|
|
2029
|
+
return value;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Custom JSON reviver to handle deserialization of hex strings back into Uint8Array values in message
|
|
2033
|
+
* content. This complements the custom replacer used during serialization, allowing messages that contain
|
|
2034
|
+
* binary data (e.g. public keys, signatures) to be correctly reconstructed when parsing JSON from
|
|
2035
|
+
* Nostr event content. The reviver checks if a value is an object with a __bytes property and,
|
|
2036
|
+
* if so, converts the hex string back into a Uint8Array. Otherwise, it returns the value unchanged.
|
|
2037
|
+
* @param {string} _key - The key of the property being processed.
|
|
2038
|
+
* @param {unknown} value - The value to check if it is an object containing a __bytes property for
|
|
2039
|
+
* hex string conversion.
|
|
2040
|
+
* @returns {unknown} The transformed value for JSON deserialization. If the value is an object
|
|
2041
|
+
* with a __bytes property, it returns a Uint8Array. Otherwise, it returns the value unchanged.
|
|
2042
|
+
*/
|
|
2043
|
+
static #jsonReviver(_key, value) {
|
|
2044
|
+
if (value && typeof value === "object" && "__bytes" in value) {
|
|
2045
|
+
return (0, import_utils4.hexToBytes)(value.__bytes);
|
|
2046
|
+
}
|
|
2047
|
+
return value;
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
// src/core/transport/didcomm.ts
|
|
2052
|
+
var import_common5 = require("@did-btcr2/common");
|
|
2053
|
+
var DidCommTransport = class {
|
|
2054
|
+
name = "didcomm";
|
|
2055
|
+
start() {
|
|
2056
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
|
|
2057
|
+
}
|
|
2058
|
+
registerActor(_did, _keys) {
|
|
2059
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2060
|
+
}
|
|
2061
|
+
getActorPk(_did) {
|
|
2062
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2063
|
+
}
|
|
2064
|
+
registerPeer(_did, _communicationPk) {
|
|
2065
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2066
|
+
}
|
|
2067
|
+
getPeerPk(_did) {
|
|
2068
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2069
|
+
}
|
|
2070
|
+
registerMessageHandler(_actorDid, _messageType, _handler) {
|
|
2071
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2072
|
+
}
|
|
2073
|
+
unregisterMessageHandler(_actorDid, _messageType) {
|
|
2074
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2075
|
+
}
|
|
2076
|
+
unregisterActor(_did) {
|
|
2077
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2078
|
+
}
|
|
2079
|
+
async sendMessage(_message, _sender, _recipient) {
|
|
2080
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2081
|
+
}
|
|
2082
|
+
publishRepeating(_message, _sender, _intervalMs, _recipient) {
|
|
2083
|
+
throw new import_common5.NotImplementedError("DidCommTransport not implemented.");
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
// src/core/transport/http/envelope.ts
|
|
2088
|
+
var import_common6 = require("@did-btcr2/common");
|
|
2089
|
+
var import_utils5 = require("@noble/hashes/utils");
|
|
2090
|
+
|
|
2091
|
+
// src/core/transport/http/errors.ts
|
|
2092
|
+
var HttpTransportError = class extends TransportAdapterError {
|
|
2093
|
+
constructor(message, type = "HttpTransportError", data) {
|
|
2094
|
+
super(message, type, { adapter: "http", ...data ?? {} });
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
|
|
2098
|
+
// src/core/transport/http/protocol.ts
|
|
2099
|
+
var HTTP_ENVELOPE_VERSION = 1;
|
|
2100
|
+
var HTTP_ROUTE = {
|
|
2101
|
+
ADVERTS: "/v1/adverts",
|
|
2102
|
+
MESSAGES: "/v1/messages",
|
|
2103
|
+
ACTOR_INBOX: "/v1/actors/{did}/inbox",
|
|
2104
|
+
WELL_KNOWN: "/v1/.well-known/aggregation"
|
|
2105
|
+
};
|
|
2106
|
+
var SSE_EVENT = {
|
|
2107
|
+
ADVERT: "advert",
|
|
2108
|
+
MESSAGE: "message",
|
|
2109
|
+
HEARTBEAT: "heartbeat"
|
|
2110
|
+
};
|
|
2111
|
+
var DEFAULT_CLOCK_SKEW_SEC = 60;
|
|
2112
|
+
var DEFAULT_NONCE_LEN_BYTES = 16;
|
|
2113
|
+
|
|
2114
|
+
// src/core/transport/http/envelope.ts
|
|
2115
|
+
function signEnvelope(message, sender, opts = {}) {
|
|
2116
|
+
const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2117
|
+
const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2118
|
+
const messageJson = normalizeForWire(normalizeMessage(message));
|
|
2119
|
+
const unsigned = {
|
|
2120
|
+
v: HTTP_ENVELOPE_VERSION,
|
|
2121
|
+
from: sender.did,
|
|
2122
|
+
...opts.to !== void 0 ? { to: opts.to } : {},
|
|
2123
|
+
timestamp,
|
|
2124
|
+
nonce,
|
|
2125
|
+
message: messageJson
|
|
2126
|
+
};
|
|
2127
|
+
const digest = (0, import_common6.canonicalHashBytes)(unsigned);
|
|
2128
|
+
const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2129
|
+
return { ...unsigned, sig: (0, import_utils5.bytesToHex)(sig) };
|
|
2130
|
+
}
|
|
2131
|
+
function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
2132
|
+
if (envelope.v !== HTTP_ENVELOPE_VERSION) {
|
|
2133
|
+
throw new HttpTransportError(
|
|
2134
|
+
`Unsupported envelope version: ${envelope.v}`,
|
|
2135
|
+
"ENVELOPE_VERSION_MISMATCH",
|
|
2136
|
+
{ version: envelope.v, expected: HTTP_ENVELOPE_VERSION }
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
if (opts.expectedFrom !== void 0 && envelope.from !== opts.expectedFrom) {
|
|
2140
|
+
throw new HttpTransportError(
|
|
2141
|
+
`Envelope from mismatch: expected ${opts.expectedFrom}, got ${envelope.from}`,
|
|
2142
|
+
"ENVELOPE_FROM_MISMATCH",
|
|
2143
|
+
{ expected: opts.expectedFrom, got: envelope.from }
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
if ("expectedTo" in opts && envelope.to !== opts.expectedTo) {
|
|
2147
|
+
throw new HttpTransportError(
|
|
2148
|
+
`Envelope to mismatch: expected ${opts.expectedTo ?? "<broadcast>"}, got ${envelope.to ?? "<broadcast>"}`,
|
|
2149
|
+
"ENVELOPE_TO_MISMATCH",
|
|
2150
|
+
{ expected: opts.expectedTo, got: envelope.to }
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2154
|
+
const nowMs = opts.now ? opts.now() : Date.now();
|
|
2155
|
+
const nowSec = Math.floor(nowMs / 1e3);
|
|
2156
|
+
const diff = Math.abs(nowSec - envelope.timestamp);
|
|
2157
|
+
if (diff > skewSec) {
|
|
2158
|
+
throw new HttpTransportError(
|
|
2159
|
+
`Envelope timestamp out of skew: ${diff}s > ${skewSec}s`,
|
|
2160
|
+
"ENVELOPE_TIMESTAMP_SKEW",
|
|
2161
|
+
{ diff, skewSec, timestamp: envelope.timestamp, now: nowSec }
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
let sigBytes;
|
|
2165
|
+
try {
|
|
2166
|
+
sigBytes = (0, import_utils5.hexToBytes)(envelope.sig);
|
|
2167
|
+
} catch {
|
|
2168
|
+
throw new HttpTransportError(
|
|
2169
|
+
"Envelope signature is not valid hex",
|
|
2170
|
+
"ENVELOPE_SIG_HEX"
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
if (sigBytes.length !== 64) {
|
|
2174
|
+
throw new HttpTransportError(
|
|
2175
|
+
`Invalid signature length: ${sigBytes.length} (expected 64)`,
|
|
2176
|
+
"ENVELOPE_SIG_LENGTH",
|
|
2177
|
+
{ length: sigBytes.length }
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
const { sig: _sig, ...unsigned } = envelope;
|
|
2181
|
+
const digest = (0, import_common6.canonicalHashBytes)(unsigned);
|
|
2182
|
+
const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
|
|
2183
|
+
if (!ok) {
|
|
2184
|
+
throw new HttpTransportError(
|
|
2185
|
+
"Envelope signature verification failed",
|
|
2186
|
+
"ENVELOPE_SIG_INVALID"
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function normalizeMessage(message) {
|
|
2191
|
+
const maybeToJSON = message.toJSON;
|
|
2192
|
+
if (typeof maybeToJSON === "function") {
|
|
2193
|
+
return maybeToJSON.call(message);
|
|
2194
|
+
}
|
|
2195
|
+
return message;
|
|
2196
|
+
}
|
|
2197
|
+
function normalizeForWire(value) {
|
|
2198
|
+
if (value instanceof Uint8Array) {
|
|
2199
|
+
return { __bytes: (0, import_utils5.bytesToHex)(value) };
|
|
2200
|
+
}
|
|
2201
|
+
if (Array.isArray(value)) {
|
|
2202
|
+
return value.map((v) => normalizeForWire(v));
|
|
2203
|
+
}
|
|
2204
|
+
if (value && typeof value === "object") {
|
|
2205
|
+
const out = {};
|
|
2206
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2207
|
+
out[k] = normalizeForWire(v);
|
|
2208
|
+
}
|
|
2209
|
+
return out;
|
|
2210
|
+
}
|
|
2211
|
+
return value;
|
|
2212
|
+
}
|
|
2213
|
+
function reviveFromWire(value) {
|
|
2214
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2215
|
+
const rec = value;
|
|
2216
|
+
const keys = Object.keys(rec);
|
|
2217
|
+
if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
|
|
2218
|
+
return (0, import_utils5.hexToBytes)(rec.__bytes);
|
|
2219
|
+
}
|
|
2220
|
+
const out = {};
|
|
2221
|
+
for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
|
|
2222
|
+
return out;
|
|
2223
|
+
}
|
|
2224
|
+
if (Array.isArray(value)) {
|
|
2225
|
+
return value.map((v) => reviveFromWire(v));
|
|
2226
|
+
}
|
|
2227
|
+
return value;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// src/core/transport/http/request-auth.ts
|
|
2231
|
+
var import_common7 = require("@did-btcr2/common");
|
|
2232
|
+
var import_utils6 = require("@noble/hashes/utils");
|
|
2233
|
+
var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
|
|
2234
|
+
function buildRequestAuth(did, keys, path, opts = {}) {
|
|
2235
|
+
const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2236
|
+
const nonce = opts.nonce ?? (0, import_utils6.bytesToHex)((0, import_utils6.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2237
|
+
const digest = (0, import_common7.canonicalHashBytes)({
|
|
2238
|
+
v: HTTP_ENVELOPE_VERSION,
|
|
2239
|
+
did,
|
|
2240
|
+
ts,
|
|
2241
|
+
nonce,
|
|
2242
|
+
path
|
|
2243
|
+
});
|
|
2244
|
+
const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2245
|
+
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils6.bytesToHex)(sig)}`;
|
|
2246
|
+
}
|
|
2247
|
+
function parseRequestAuth(headerValue) {
|
|
2248
|
+
const prefix = `${REQUEST_AUTH_SCHEME} `;
|
|
2249
|
+
if (!headerValue.startsWith(prefix)) {
|
|
2250
|
+
throw new HttpTransportError(
|
|
2251
|
+
`Unexpected auth scheme (want ${REQUEST_AUTH_SCHEME})`,
|
|
2252
|
+
"REQUEST_AUTH_SCHEME"
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
const params = {};
|
|
2256
|
+
for (const piece of headerValue.slice(prefix.length).split(",")) {
|
|
2257
|
+
const eq = piece.indexOf("=");
|
|
2258
|
+
if (eq === -1) continue;
|
|
2259
|
+
const key = piece.slice(0, eq).trim();
|
|
2260
|
+
const val = piece.slice(eq + 1).trim();
|
|
2261
|
+
if (key.length > 0) params[key] = val;
|
|
2262
|
+
}
|
|
2263
|
+
const v = Number(params.v);
|
|
2264
|
+
const ts = Number(params.ts);
|
|
2265
|
+
if (!Number.isInteger(v) || !Number.isInteger(ts) || !params.did || !params.nonce || !params.sig) {
|
|
2266
|
+
throw new HttpTransportError(
|
|
2267
|
+
"Malformed auth header (missing or invalid field)",
|
|
2268
|
+
"REQUEST_AUTH_MALFORMED",
|
|
2269
|
+
{ received: Object.keys(params) }
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
return { v, did: params.did, ts, nonce: params.nonce, sig: params.sig };
|
|
2273
|
+
}
|
|
2274
|
+
function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
|
|
2275
|
+
const parsed = parseRequestAuth(headerValue);
|
|
2276
|
+
if (parsed.v !== HTTP_ENVELOPE_VERSION) {
|
|
2277
|
+
throw new HttpTransportError(
|
|
2278
|
+
`Unsupported auth version: ${parsed.v}`,
|
|
2279
|
+
"REQUEST_AUTH_VERSION_MISMATCH",
|
|
2280
|
+
{ version: parsed.v, expected: HTTP_ENVELOPE_VERSION }
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2284
|
+
const nowMs = opts.now ? opts.now() : Date.now();
|
|
2285
|
+
const nowSec = Math.floor(nowMs / 1e3);
|
|
2286
|
+
const diff = Math.abs(nowSec - parsed.ts);
|
|
2287
|
+
if (diff > skewSec) {
|
|
2288
|
+
throw new HttpTransportError(
|
|
2289
|
+
`Auth timestamp out of skew: ${diff}s > ${skewSec}s`,
|
|
2290
|
+
"REQUEST_AUTH_TIMESTAMP_SKEW",
|
|
2291
|
+
{ diff, skewSec }
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
let sigBytes;
|
|
2295
|
+
try {
|
|
2296
|
+
sigBytes = (0, import_utils6.hexToBytes)(parsed.sig);
|
|
2297
|
+
} catch {
|
|
2298
|
+
throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
|
|
2299
|
+
}
|
|
2300
|
+
if (sigBytes.length !== 64) {
|
|
2301
|
+
throw new HttpTransportError(
|
|
2302
|
+
`Invalid auth signature length: ${sigBytes.length}`,
|
|
2303
|
+
"REQUEST_AUTH_SIG_LENGTH",
|
|
2304
|
+
{ length: sigBytes.length }
|
|
2305
|
+
);
|
|
2306
|
+
}
|
|
2307
|
+
const digest = (0, import_common7.canonicalHashBytes)({
|
|
2308
|
+
v: parsed.v,
|
|
2309
|
+
did: parsed.did,
|
|
2310
|
+
ts: parsed.ts,
|
|
2311
|
+
nonce: parsed.nonce,
|
|
2312
|
+
path: expectedPath
|
|
2313
|
+
});
|
|
2314
|
+
const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
|
|
2315
|
+
if (!ok) {
|
|
2316
|
+
throw new HttpTransportError("Auth signature verification failed", "REQUEST_AUTH_SIG_INVALID");
|
|
2317
|
+
}
|
|
2318
|
+
return parsed;
|
|
2319
|
+
}
|
|
2320
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2321
|
+
0 && (module.exports = {
|
|
2322
|
+
AGGREGATED_NONCE,
|
|
2323
|
+
AGGREGATION_MESSAGE_PREFIX,
|
|
2324
|
+
AGGREGATION_WIRE_VERSION,
|
|
2325
|
+
AUTHORIZATION_REQUEST,
|
|
2326
|
+
AggregationCohort,
|
|
2327
|
+
AggregationCohortError,
|
|
2328
|
+
AggregationParticipantError,
|
|
2329
|
+
AggregationServiceError,
|
|
2330
|
+
BaseMessage,
|
|
2331
|
+
BeaconSigningSession,
|
|
2332
|
+
COHORT_ADVERT,
|
|
2333
|
+
COHORT_OPT_IN,
|
|
2334
|
+
COHORT_OPT_IN_ACCEPT,
|
|
2335
|
+
COHORT_READY,
|
|
2336
|
+
CONSOLE_LOGGER,
|
|
2337
|
+
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
2338
|
+
DEFAULT_CLOCK_SKEW_SEC,
|
|
2339
|
+
DEFAULT_FUNDING_MODEL,
|
|
2340
|
+
DEFAULT_NONCE_LEN_BYTES,
|
|
2341
|
+
DEFAULT_NOSTR_RELAYS,
|
|
2342
|
+
DEFAULT_RECOVERY_SEQUENCE,
|
|
2343
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
2344
|
+
DidCommTransport,
|
|
2345
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
2346
|
+
FALLBACK_SIGNATURE,
|
|
2347
|
+
HTTP_ENVELOPE_VERSION,
|
|
2348
|
+
HTTP_ROUTE,
|
|
2349
|
+
HttpTransportError,
|
|
2350
|
+
InMemoryBus,
|
|
2351
|
+
InMemoryTransport,
|
|
2352
|
+
KNOWN_BEACON_TYPES,
|
|
2353
|
+
KNOWN_FUNDING_MODELS,
|
|
2354
|
+
KeyPairAggregationSigner,
|
|
2355
|
+
MAX_RECOVERY_SEQUENCE,
|
|
2356
|
+
NONCE_CONTRIBUTION,
|
|
2357
|
+
NostrTransport,
|
|
2358
|
+
ParticipantCohortPhase,
|
|
2359
|
+
REQUEST_AUTH_SCHEME,
|
|
2360
|
+
SIGNATURE_AUTHORIZATION,
|
|
2361
|
+
SILENT_LOGGER,
|
|
2362
|
+
SSE_EVENT,
|
|
2363
|
+
SUBMIT_NONINCLUDED,
|
|
2364
|
+
SUBMIT_UPDATE,
|
|
2365
|
+
ServiceCohortPhase,
|
|
2366
|
+
SigningSessionError,
|
|
2367
|
+
SigningSessionPhase,
|
|
2368
|
+
TAPROOT_LEAF_VERSION,
|
|
2369
|
+
TransportAdapterError,
|
|
2370
|
+
TransportError,
|
|
2371
|
+
TypedEventEmitter,
|
|
2372
|
+
VALIDATION_ACK,
|
|
2373
|
+
buildFallbackLeaf,
|
|
2374
|
+
buildFallbackSpend,
|
|
2375
|
+
buildRecoveryLeaves,
|
|
2376
|
+
buildRecoveryScript,
|
|
2377
|
+
buildRecoverySpend,
|
|
2378
|
+
buildRequestAuth,
|
|
2379
|
+
createAggregatedNonceMessage,
|
|
2380
|
+
createAuthorizationRequestMessage,
|
|
2381
|
+
createCohortAdvertMessage,
|
|
2382
|
+
createCohortOptInAcceptMessage,
|
|
2383
|
+
createCohortOptInMessage,
|
|
2384
|
+
createCohortReadyMessage,
|
|
2385
|
+
createDistributeAggregatedDataMessage,
|
|
2386
|
+
createFallbackAuthorizationRequestMessage,
|
|
2387
|
+
createFallbackSignatureMessage,
|
|
2388
|
+
createNonceContributionMessage,
|
|
2389
|
+
createSignatureAuthorizationMessage,
|
|
2390
|
+
createSubmitNonIncludedMessage,
|
|
2391
|
+
createSubmitUpdateMessage,
|
|
2392
|
+
createValidationAckMessage,
|
|
2393
|
+
fallbackSighash,
|
|
2394
|
+
getBeaconStrategy,
|
|
2395
|
+
isAggregatedNonceMessage,
|
|
2396
|
+
isAggregationMessageType,
|
|
2397
|
+
isAuthorizationRequestMessage,
|
|
2398
|
+
isCohortAdvertMessage,
|
|
2399
|
+
isCohortOptInAcceptMessage,
|
|
2400
|
+
isCohortOptInMessage,
|
|
2401
|
+
isCohortReadyMessage,
|
|
2402
|
+
isDistributeAggregatedDataMessage,
|
|
2403
|
+
isFallbackAuthorizationRequestMessage,
|
|
2404
|
+
isFallbackSignatureMessage,
|
|
2405
|
+
isKeygenMessageType,
|
|
2406
|
+
isNonceContributionMessage,
|
|
2407
|
+
isSignMessageType,
|
|
2408
|
+
isSignatureAuthorizationMessage,
|
|
2409
|
+
isSubmitNonIncludedMessage,
|
|
2410
|
+
isSubmitUpdateMessage,
|
|
2411
|
+
isUpdateMessageType,
|
|
2412
|
+
isValidationAckMessage,
|
|
2413
|
+
normalizeForWire,
|
|
2414
|
+
parseRequestAuth,
|
|
2415
|
+
registerBeaconStrategy,
|
|
2416
|
+
resolveFallbackThreshold,
|
|
2417
|
+
reviveFromWire,
|
|
2418
|
+
signEnvelope,
|
|
2419
|
+
tapLeafHash,
|
|
2420
|
+
validateCohortConditions,
|
|
2421
|
+
verifyEnvelope,
|
|
2422
|
+
verifyRequestAuth
|
|
2423
|
+
});
|