@did-btcr2/method 0.28.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +34125 -44647
- package/dist/browser.mjs +26409 -36931
- package/dist/cjs/index.js +2869 -679
- package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
- package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
- package/dist/esm/core/aggregation/cohort.js +31 -8
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/logger.js +15 -0
- package/dist/esm/core/aggregation/logger.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js +12 -1
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +90 -0
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/messages/index.js +1 -0
- package/dist/esm/core/aggregation/messages/index.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +39 -46
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +143 -15
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/core/aggregation/signing-session.js +44 -5
- package/dist/esm/core/aggregation/signing-session.js.map +1 -1
- package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
- package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
- package/dist/esm/core/aggregation/transport/factory.js +15 -6
- package/dist/esm/core/aggregation/transport/factory.js.map +1 -1
- package/dist/esm/core/aggregation/transport/http/client.js +350 -0
- package/dist/esm/core/aggregation/transport/http/client.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js +126 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/errors.js +11 -0
- package/dist/esm/core/aggregation/transport/http/errors.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/index.js +12 -0
- package/dist/esm/core/aggregation/transport/http/index.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js +38 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js +28 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js +45 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js +100 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/server.js +481 -0
- package/dist/esm/core/aggregation/transport/http/server.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js +110 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js +25 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/index.js +1 -0
- package/dist/esm/core/aggregation/transport/index.js.map +1 -1
- package/dist/esm/core/aggregation/transport/nostr.js +245 -16
- package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +295 -63
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +3 -3
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/singleton-beacon.js +3 -3
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +3 -3
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +14 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/core/updater.js +63 -55
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +0 -4
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/did-document.js +2 -2
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
- package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
- package/dist/types/core/aggregation/cohort.d.ts +20 -3
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/logger.d.ts +22 -0
- package/dist/types/core/aggregation/logger.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/base.d.ts +13 -1
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/index.d.ts +1 -0
- package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +2 -0
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +32 -6
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +33 -2
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/core/aggregation/signing-session.d.ts +5 -1
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/factory.d.ts +22 -7
- package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/http/client.d.ts +48 -0
- package/dist/types/core/aggregation/transport/http/client.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts +64 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts +9 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts +53 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts +50 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts +110 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/transport.d.ts +26 -1
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +149 -22
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +3 -3
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +3 -3
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts +2 -2
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts +27 -12
- package/dist/types/core/updater.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -7
- package/src/core/aggregation/beacon-strategy.ts +123 -0
- package/src/core/aggregation/cohort.ts +34 -8
- package/src/core/aggregation/logger.ts +33 -0
- package/src/core/aggregation/messages/base.ts +20 -5
- package/src/core/aggregation/messages/bodies.ts +223 -0
- package/src/core/aggregation/messages/factories.ts +1 -0
- package/src/core/aggregation/messages/index.ts +1 -0
- package/src/core/aggregation/participant.ts +40 -46
- package/src/core/aggregation/runner/events.ts +27 -3
- package/src/core/aggregation/runner/participant-runner.ts +41 -7
- package/src/core/aggregation/runner/service-runner.ts +227 -19
- package/src/core/aggregation/service.ts +189 -20
- package/src/core/aggregation/signing-session.ts +65 -7
- package/src/core/aggregation/transport/didcomm.ts +17 -0
- package/src/core/aggregation/transport/factory.ts +48 -12
- package/src/core/aggregation/transport/http/client.ts +409 -0
- package/src/core/aggregation/transport/http/envelope.ts +204 -0
- package/src/core/aggregation/transport/http/errors.ts +11 -0
- package/src/core/aggregation/transport/http/inbox-buffer.ts +53 -0
- package/src/core/aggregation/transport/http/index.ts +11 -0
- package/src/core/aggregation/transport/http/nonce-cache.ts +43 -0
- package/src/core/aggregation/transport/http/protocol.ts +57 -0
- package/src/core/aggregation/transport/http/rate-limiter.ts +75 -0
- package/src/core/aggregation/transport/http/request-auth.ts +164 -0
- package/src/core/aggregation/transport/http/server.ts +615 -0
- package/src/core/aggregation/transport/http/sse-stream.ts +121 -0
- package/src/core/aggregation/transport/http/sse-writer.ts +23 -0
- package/src/core/aggregation/transport/index.ts +1 -0
- package/src/core/aggregation/transport/nostr.ts +266 -23
- package/src/core/aggregation/transport/transport.ts +34 -1
- package/src/core/beacon/beacon.ts +411 -79
- package/src/core/beacon/cas-beacon.ts +4 -4
- package/src/core/beacon/singleton-beacon.ts +4 -4
- package/src/core/beacon/smt-beacon.ts +4 -4
- package/src/core/beacon/utils.ts +16 -11
- package/src/core/updater.ts +113 -67
- package/src/did-btcr2.ts +0 -5
- package/src/index.ts +2 -0
- package/src/utils/did-document.ts +2 -2
package/dist/cjs/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AGGREGATED_NONCE: () => AGGREGATED_NONCE,
|
|
34
34
|
AGGREGATION_MESSAGE_PREFIX: () => AGGREGATION_MESSAGE_PREFIX,
|
|
35
|
+
AGGREGATION_WIRE_VERSION: () => AGGREGATION_WIRE_VERSION,
|
|
35
36
|
AUTHORIZATION_REQUEST: () => AUTHORIZATION_REQUEST,
|
|
36
37
|
AggregateBeaconError: () => AggregateBeaconError,
|
|
37
38
|
AggregationCohort: () => AggregationCohort,
|
|
@@ -58,6 +59,12 @@ __export(index_exports, {
|
|
|
58
59
|
COHORT_OPT_IN: () => COHORT_OPT_IN,
|
|
59
60
|
COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
|
|
60
61
|
COHORT_READY: () => COHORT_READY,
|
|
62
|
+
CONSOLE_LOGGER: () => CONSOLE_LOGGER,
|
|
63
|
+
DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
64
|
+
DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
65
|
+
DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
|
|
66
|
+
DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
67
|
+
DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
|
|
61
68
|
DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
|
|
62
69
|
DID_REGEX: () => DID_REGEX,
|
|
63
70
|
DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
|
|
@@ -68,15 +75,31 @@ __export(index_exports, {
|
|
|
68
75
|
DidVerificationMethod: () => DidVerificationMethod,
|
|
69
76
|
Document: () => Document,
|
|
70
77
|
GenesisDocument: () => GenesisDocument,
|
|
78
|
+
HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
|
|
79
|
+
HTTP_ROUTE: () => HTTP_ROUTE,
|
|
80
|
+
HttpClientTransport: () => HttpClientTransport,
|
|
81
|
+
HttpServerTransport: () => HttpServerTransport,
|
|
82
|
+
HttpTransportError: () => HttpTransportError,
|
|
71
83
|
ID_PLACEHOLDER_VALUE: () => ID_PLACEHOLDER_VALUE,
|
|
72
84
|
Identifier: () => Identifier,
|
|
85
|
+
InMemoryRateLimitStore: () => InMemoryRateLimitStore,
|
|
86
|
+
InboxBuffer: () => InboxBuffer,
|
|
73
87
|
NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
|
|
88
|
+
NonceCache: () => NonceCache,
|
|
74
89
|
NostrTransport: () => NostrTransport,
|
|
90
|
+
P2PKH_BEACON_TX_VSIZE: () => P2PKH_BEACON_TX_VSIZE,
|
|
91
|
+
P2TR_BEACON_TX_VSIZE: () => P2TR_BEACON_TX_VSIZE,
|
|
92
|
+
P2WPKH_BEACON_TX_VSIZE: () => P2WPKH_BEACON_TX_VSIZE,
|
|
75
93
|
ParticipantCohortPhase: () => ParticipantCohortPhase,
|
|
94
|
+
REQUEST_AUTH_SCHEME: () => REQUEST_AUTH_SCHEME,
|
|
95
|
+
RateLimiter: () => RateLimiter,
|
|
76
96
|
Resolver: () => Resolver,
|
|
77
97
|
SIGNATURE_AUTHORIZATION: () => SIGNATURE_AUTHORIZATION,
|
|
98
|
+
SILENT_LOGGER: () => SILENT_LOGGER,
|
|
99
|
+
SINGLETON_BEACON_TX_VSIZE: () => SINGLETON_BEACON_TX_VSIZE,
|
|
78
100
|
SMTBeacon: () => SMTBeacon,
|
|
79
101
|
SMTBeaconError: () => SMTBeaconError,
|
|
102
|
+
SSE_EVENT: () => SSE_EVENT,
|
|
80
103
|
SUBMIT_UPDATE: () => SUBMIT_UPDATE,
|
|
81
104
|
ServiceCohortPhase: () => ServiceCohortPhase,
|
|
82
105
|
SigningSessionError: () => SigningSessionError,
|
|
@@ -90,6 +113,8 @@ __export(index_exports, {
|
|
|
90
113
|
TypedEventEmitter: () => TypedEventEmitter,
|
|
91
114
|
Updater: () => Updater,
|
|
92
115
|
VALIDATION_ACK: () => VALIDATION_ACK,
|
|
116
|
+
buildAggregationBeaconTx: () => buildAggregationBeaconTx,
|
|
117
|
+
buildRequestAuth: () => buildRequestAuth,
|
|
93
118
|
createAggregatedNonceMessage: () => createAggregatedNonceMessage,
|
|
94
119
|
createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
|
|
95
120
|
createCohortAdvertMessage: () => createCohortAdvertMessage,
|
|
@@ -101,41 +126,126 @@ __export(index_exports, {
|
|
|
101
126
|
createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
|
|
102
127
|
createSubmitUpdateMessage: () => createSubmitUpdateMessage,
|
|
103
128
|
createValidationAckMessage: () => createValidationAckMessage,
|
|
129
|
+
defaultReconnectBackoff: () => defaultReconnectBackoff,
|
|
130
|
+
deriveSingletonAddress: () => deriveSingletonAddress,
|
|
131
|
+
detectSingletonScriptKind: () => detectSingletonScriptKind,
|
|
132
|
+
formatSseComment: () => formatSseComment,
|
|
133
|
+
formatSseEvent: () => formatSseEvent,
|
|
134
|
+
getBeaconStrategy: () => getBeaconStrategy,
|
|
135
|
+
isAggregatedNonceMessage: () => isAggregatedNonceMessage,
|
|
104
136
|
isAggregationMessageType: () => isAggregationMessageType,
|
|
137
|
+
isAuthorizationRequestMessage: () => isAuthorizationRequestMessage,
|
|
138
|
+
isCohortAdvertMessage: () => isCohortAdvertMessage,
|
|
139
|
+
isCohortOptInAcceptMessage: () => isCohortOptInAcceptMessage,
|
|
140
|
+
isCohortOptInMessage: () => isCohortOptInMessage,
|
|
141
|
+
isCohortReadyMessage: () => isCohortReadyMessage,
|
|
142
|
+
isDistributeAggregatedDataMessage: () => isDistributeAggregatedDataMessage,
|
|
105
143
|
isKeygenMessageType: () => isKeygenMessageType,
|
|
144
|
+
isNonceContributionMessage: () => isNonceContributionMessage,
|
|
106
145
|
isSignMessageType: () => isSignMessageType,
|
|
107
|
-
|
|
146
|
+
isSignatureAuthorizationMessage: () => isSignatureAuthorizationMessage,
|
|
147
|
+
isSubmitUpdateMessage: () => isSubmitUpdateMessage,
|
|
148
|
+
isUpdateMessageType: () => isUpdateMessageType,
|
|
149
|
+
isValidationAckMessage: () => isValidationAckMessage,
|
|
150
|
+
normalizeForWire: () => normalizeForWire,
|
|
151
|
+
opReturnScript: () => opReturnScript,
|
|
152
|
+
parseRequestAuth: () => parseRequestAuth,
|
|
153
|
+
parseSseStream: () => parseSseStream,
|
|
154
|
+
registerBeaconStrategy: () => registerBeaconStrategy,
|
|
155
|
+
reviveFromWire: () => reviveFromWire,
|
|
156
|
+
signEnvelope: () => signEnvelope,
|
|
157
|
+
verifyEnvelope: () => verifyEnvelope,
|
|
158
|
+
verifyRequestAuth: () => verifyRequestAuth
|
|
108
159
|
});
|
|
109
160
|
module.exports = __toCommonJS(index_exports);
|
|
110
161
|
|
|
111
162
|
// src/core/aggregation/service.ts
|
|
163
|
+
var import_common4 = require("@did-btcr2/common");
|
|
164
|
+
var import_cryptosuite = require("@did-btcr2/cryptosuite");
|
|
112
165
|
var import_utils2 = require("@noble/hashes/utils");
|
|
113
166
|
|
|
114
|
-
// src/core/aggregation/
|
|
115
|
-
var
|
|
167
|
+
// src/core/aggregation/beacon-strategy.ts
|
|
168
|
+
var import_common = require("@did-btcr2/common");
|
|
116
169
|
var import_smt = require("@did-btcr2/smt");
|
|
170
|
+
var CAS_STRATEGY = {
|
|
171
|
+
type: "CASBeacon",
|
|
172
|
+
buildAggregatedData(cohort) {
|
|
173
|
+
cohort.buildCASAnnouncement();
|
|
174
|
+
},
|
|
175
|
+
getDistributePayload(cohort) {
|
|
176
|
+
return { casAnnouncement: cohort.casAnnouncement };
|
|
177
|
+
},
|
|
178
|
+
validateParticipantView({ participantDid, expectedHash, body }) {
|
|
179
|
+
const casAnnouncement = body.casAnnouncement;
|
|
180
|
+
if (!casAnnouncement) return { matches: false };
|
|
181
|
+
return {
|
|
182
|
+
matches: casAnnouncement[participantDid] === expectedHash,
|
|
183
|
+
casAnnouncement
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var SMT_STRATEGY = {
|
|
188
|
+
type: "SMTBeacon",
|
|
189
|
+
buildAggregatedData(cohort) {
|
|
190
|
+
cohort.buildSMTTree();
|
|
191
|
+
},
|
|
192
|
+
getDistributePayload(cohort, participantDid) {
|
|
193
|
+
const proof = cohort.smtProofs?.get(participantDid);
|
|
194
|
+
return { smtProof: proof };
|
|
195
|
+
},
|
|
196
|
+
validateParticipantView({ participantDid, submittedUpdate, body }) {
|
|
197
|
+
const smtProof = body.smtProof;
|
|
198
|
+
if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
|
|
199
|
+
const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
|
|
200
|
+
const expectedUpdateId = (0, import_smt.hashToHex)((0, import_smt.blockHash)(canonicalBytes));
|
|
201
|
+
if (smtProof.updateId !== expectedUpdateId) {
|
|
202
|
+
return { matches: false, smtProof };
|
|
203
|
+
}
|
|
204
|
+
const index = (0, import_smt.didToIndex)(participantDid);
|
|
205
|
+
const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.hexToHash)(smtProof.nonce)), (0, import_smt.hexToHash)(smtProof.updateId));
|
|
206
|
+
return {
|
|
207
|
+
matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
|
|
208
|
+
smtProof
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var STRATEGIES = /* @__PURE__ */ new Map([
|
|
213
|
+
[CAS_STRATEGY.type, CAS_STRATEGY],
|
|
214
|
+
[SMT_STRATEGY.type, SMT_STRATEGY]
|
|
215
|
+
]);
|
|
216
|
+
function registerBeaconStrategy(strategy) {
|
|
217
|
+
STRATEGIES.set(strategy.type, strategy);
|
|
218
|
+
}
|
|
219
|
+
function getBeaconStrategy(type) {
|
|
220
|
+
return STRATEGIES.get(type);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/core/aggregation/cohort.ts
|
|
224
|
+
var import_common3 = require("@did-btcr2/common");
|
|
225
|
+
var import_smt2 = require("@did-btcr2/smt");
|
|
226
|
+
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
117
227
|
var import_utils = require("@noble/hashes/utils");
|
|
228
|
+
var import_btc_signer = require("@scure/btc-signer");
|
|
118
229
|
var import_musig2 = require("@scure/btc-signer/musig2");
|
|
119
|
-
var import_bitcoinjs_lib = require("bitcoinjs-lib");
|
|
120
230
|
|
|
121
231
|
// src/core/aggregation/errors.ts
|
|
122
|
-
var
|
|
123
|
-
var AggregationServiceError = class extends
|
|
232
|
+
var import_common2 = require("@did-btcr2/common");
|
|
233
|
+
var AggregationServiceError = class extends import_common2.MethodError {
|
|
124
234
|
constructor(message, type = "AggregationServiceError", data) {
|
|
125
235
|
super(message, type, data);
|
|
126
236
|
}
|
|
127
237
|
};
|
|
128
|
-
var AggregationParticipantError = class extends
|
|
238
|
+
var AggregationParticipantError = class extends import_common2.MethodError {
|
|
129
239
|
constructor(message, type = "AggregationParticipantError", data) {
|
|
130
240
|
super(message, type, data);
|
|
131
241
|
}
|
|
132
242
|
};
|
|
133
|
-
var AggregationCohortError = class extends
|
|
243
|
+
var AggregationCohortError = class extends import_common2.MethodError {
|
|
134
244
|
constructor(message, type = "AggregationCohortError", data) {
|
|
135
245
|
super(message, type, data);
|
|
136
246
|
}
|
|
137
247
|
};
|
|
138
|
-
var SigningSessionError = class extends
|
|
248
|
+
var SigningSessionError = class extends import_common2.MethodError {
|
|
139
249
|
constructor(message, type = "SigningSessionError", data) {
|
|
140
250
|
super(message, type, data);
|
|
141
251
|
}
|
|
@@ -155,10 +265,21 @@ var AggregationCohort = class {
|
|
|
155
265
|
beaconType;
|
|
156
266
|
/** List of participant DIDs that have been accepted into the cohort. */
|
|
157
267
|
participants = [];
|
|
268
|
+
/**
|
|
269
|
+
* Mapping from participant DID → their compressed secp256k1 public key.
|
|
270
|
+
* Distinct from {@link cohortKeys} (which is sorted per BIP-327) — this lets
|
|
271
|
+
* callers look up a participant's key without knowing their position in the
|
|
272
|
+
* sorted array. Populated by the service at `acceptParticipant` time.
|
|
273
|
+
*/
|
|
274
|
+
participantKeys = /* @__PURE__ */ new Map();
|
|
158
275
|
/** Sorted list of cohort participants' compressed public keys. */
|
|
159
276
|
#cohortKeys = [];
|
|
160
|
-
/**
|
|
161
|
-
|
|
277
|
+
/**
|
|
278
|
+
* BIP-341 TapTweak — `taggedHash("TapTweak", internalPubkey)` for a key-path-only
|
|
279
|
+
* Taproot output. Despite prior naming, this is NOT a Merkle root: key-path-only
|
|
280
|
+
* spends have no script tree.
|
|
281
|
+
*/
|
|
282
|
+
tapTweak = new Uint8Array();
|
|
162
283
|
/** The n-of-n MuSig2 Taproot beacon address. */
|
|
163
284
|
beaconAddress = "";
|
|
164
285
|
/** Pending DID updates submitted by participants, keyed by DID. */
|
|
@@ -189,7 +310,7 @@ var AggregationCohort = class {
|
|
|
189
310
|
}
|
|
190
311
|
/**
|
|
191
312
|
* Computes the n-of-n MuSig2 Taproot beacon address from cohort keys.
|
|
192
|
-
* Sets `
|
|
313
|
+
* Sets `tapTweak` to the BIP-341 key-path-only tweak.
|
|
193
314
|
*/
|
|
194
315
|
computeBeaconAddress() {
|
|
195
316
|
if (this.#cohortKeys.length === 0) {
|
|
@@ -201,8 +322,8 @@ var AggregationCohort = class {
|
|
|
201
322
|
}
|
|
202
323
|
const keyAggContext = (0, import_musig2.keyAggregate)(this.#cohortKeys);
|
|
203
324
|
const aggPubkey = (0, import_musig2.keyAggExport)(keyAggContext);
|
|
204
|
-
const payment =
|
|
205
|
-
this.
|
|
325
|
+
const payment = (0, import_btc_signer.p2tr)(aggPubkey);
|
|
326
|
+
this.tapTweak = import_secp256k1.schnorr.utils.taggedHash("TapTweak", aggPubkey);
|
|
206
327
|
if (!payment.address) {
|
|
207
328
|
throw new AggregationCohortError(
|
|
208
329
|
"Failed to compute Taproot address",
|
|
@@ -236,6 +357,18 @@ var AggregationCohort = class {
|
|
|
236
357
|
);
|
|
237
358
|
}
|
|
238
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Returns the position of a participant's public key in the sorted
|
|
362
|
+
* {@link cohortKeys} array, or -1 if the participant is not in the cohort.
|
|
363
|
+
* Required by MuSig2 partial-sig verification which indexes by signer position.
|
|
364
|
+
*/
|
|
365
|
+
indexOfParticipant(did) {
|
|
366
|
+
const pk = this.participantKeys.get(did);
|
|
367
|
+
if (!pk) return -1;
|
|
368
|
+
return this.#cohortKeys.findIndex(
|
|
369
|
+
(k) => k.length === pk.length && k.every((b, i) => b === pk[i])
|
|
370
|
+
);
|
|
371
|
+
}
|
|
239
372
|
addUpdate(participantDid, signedUpdate) {
|
|
240
373
|
if (!this.participants.includes(participantDid)) {
|
|
241
374
|
throw new AggregationCohortError(
|
|
@@ -264,10 +397,10 @@ var AggregationCohort = class {
|
|
|
264
397
|
}
|
|
265
398
|
const announcement = {};
|
|
266
399
|
for (const [did, signedUpdate] of this.pendingUpdates) {
|
|
267
|
-
announcement[did] = (0,
|
|
400
|
+
announcement[did] = (0, import_common3.canonicalHash)(signedUpdate);
|
|
268
401
|
}
|
|
269
402
|
this.casAnnouncement = announcement;
|
|
270
|
-
this.signalBytes = (0,
|
|
403
|
+
this.signalBytes = (0, import_common3.hash)((0, import_common3.canonicalize)(announcement));
|
|
271
404
|
return announcement;
|
|
272
405
|
}
|
|
273
406
|
/**
|
|
@@ -283,11 +416,11 @@ var AggregationCohort = class {
|
|
|
283
416
|
{ cohortId: this.id }
|
|
284
417
|
);
|
|
285
418
|
}
|
|
286
|
-
const tree = new
|
|
419
|
+
const tree = new import_smt2.BTCR2MerkleTree();
|
|
287
420
|
const entries = [];
|
|
288
421
|
const encoder = new TextEncoder();
|
|
289
422
|
for (const [did, signedUpdate] of this.pendingUpdates) {
|
|
290
|
-
const canonicalBytes = encoder.encode((0,
|
|
423
|
+
const canonicalBytes = encoder.encode((0, import_common3.canonicalize)(signedUpdate));
|
|
291
424
|
const nonce = (0, import_utils.randomBytes)(32);
|
|
292
425
|
entries.push({ did, nonce, signedUpdate: canonicalBytes });
|
|
293
426
|
}
|
|
@@ -329,28 +462,17 @@ var AggregationCohort = class {
|
|
|
329
462
|
}
|
|
330
463
|
};
|
|
331
464
|
|
|
332
|
-
// src/core/aggregation/messages/constants.ts
|
|
333
|
-
var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
|
|
334
|
-
var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
|
|
335
|
-
var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
336
|
-
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
337
|
-
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
338
|
-
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
339
|
-
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
340
|
-
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
341
|
-
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
342
|
-
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
343
|
-
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
344
|
-
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
345
|
-
|
|
346
465
|
// src/core/aggregation/messages/base.ts
|
|
466
|
+
var AGGREGATION_WIRE_VERSION = 1;
|
|
347
467
|
var BaseMessage = class {
|
|
348
468
|
type;
|
|
469
|
+
version;
|
|
349
470
|
to;
|
|
350
471
|
from;
|
|
351
472
|
body;
|
|
352
|
-
constructor({ type, to, from: from2, body }) {
|
|
473
|
+
constructor({ type, version, to, from: from2, body }) {
|
|
353
474
|
this.type = type;
|
|
475
|
+
this.version = version ?? AGGREGATION_WIRE_VERSION;
|
|
354
476
|
this.to = to;
|
|
355
477
|
this.from = from2;
|
|
356
478
|
this.body = body;
|
|
@@ -362,6 +484,7 @@ var BaseMessage = class {
|
|
|
362
484
|
toJSON() {
|
|
363
485
|
return {
|
|
364
486
|
type: this.type,
|
|
487
|
+
version: this.version,
|
|
365
488
|
to: this.to,
|
|
366
489
|
from: this.from,
|
|
367
490
|
body: this.body
|
|
@@ -369,6 +492,20 @@ var BaseMessage = class {
|
|
|
369
492
|
}
|
|
370
493
|
};
|
|
371
494
|
|
|
495
|
+
// src/core/aggregation/messages/constants.ts
|
|
496
|
+
var AGGREGATION_MESSAGE_PREFIX = "https://btcr2.dev/aggregation";
|
|
497
|
+
var COHORT_ADVERT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_advert`;
|
|
498
|
+
var COHORT_OPT_IN = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in`;
|
|
499
|
+
var COHORT_OPT_IN_ACCEPT = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_opt_in_accept`;
|
|
500
|
+
var COHORT_READY = `${AGGREGATION_MESSAGE_PREFIX}/keygen/cohort_ready`;
|
|
501
|
+
var SUBMIT_UPDATE = `${AGGREGATION_MESSAGE_PREFIX}/update/submit_update`;
|
|
502
|
+
var DISTRIBUTE_AGGREGATED_DATA = `${AGGREGATION_MESSAGE_PREFIX}/update/distribute_aggregated_data`;
|
|
503
|
+
var VALIDATION_ACK = `${AGGREGATION_MESSAGE_PREFIX}/update/validation_ack`;
|
|
504
|
+
var AUTHORIZATION_REQUEST = `${AGGREGATION_MESSAGE_PREFIX}/sign/authorization_request`;
|
|
505
|
+
var NONCE_CONTRIBUTION = `${AGGREGATION_MESSAGE_PREFIX}/sign/nonce_contribution`;
|
|
506
|
+
var AGGREGATED_NONCE = `${AGGREGATION_MESSAGE_PREFIX}/sign/aggregated_nonce`;
|
|
507
|
+
var SIGNATURE_AUTHORIZATION = `${AGGREGATION_MESSAGE_PREFIX}/sign/signature_authorization`;
|
|
508
|
+
|
|
372
509
|
// src/core/aggregation/messages/factories.ts
|
|
373
510
|
function createCohortAdvertMessage(fields) {
|
|
374
511
|
const { from: from2, ...body } = fields;
|
|
@@ -456,8 +593,8 @@ var SigningSessionPhase = /* @__PURE__ */ ((SigningSessionPhase2) => {
|
|
|
456
593
|
})(SigningSessionPhase || {});
|
|
457
594
|
|
|
458
595
|
// src/core/aggregation/signing-session.ts
|
|
596
|
+
var import_btc_signer2 = require("@scure/btc-signer");
|
|
459
597
|
var musig2 = __toESM(require("@scure/btc-signer/musig2"), 1);
|
|
460
|
-
var import_bitcoinjs_lib2 = require("bitcoinjs-lib");
|
|
461
598
|
var BeaconSigningSession = class {
|
|
462
599
|
/** Unique identifier for this signing session. */
|
|
463
600
|
id;
|
|
@@ -499,11 +636,11 @@ var BeaconSigningSession = class {
|
|
|
499
636
|
"SIGHASH_ERROR"
|
|
500
637
|
);
|
|
501
638
|
}
|
|
502
|
-
return this.pendingTx.
|
|
639
|
+
return this.pendingTx.preimageWitnessV1(
|
|
503
640
|
0,
|
|
504
641
|
this.prevOutScripts,
|
|
505
|
-
|
|
506
|
-
|
|
642
|
+
import_btc_signer2.SigHash.DEFAULT,
|
|
643
|
+
this.prevOutValues
|
|
507
644
|
);
|
|
508
645
|
}
|
|
509
646
|
addNonceContribution(participantDid, nonceContribution) {
|
|
@@ -514,6 +651,13 @@ var BeaconSigningSession = class {
|
|
|
514
651
|
{ phase: this.phase }
|
|
515
652
|
);
|
|
516
653
|
}
|
|
654
|
+
if (!this.cohort.participants.includes(participantDid)) {
|
|
655
|
+
throw new SigningSessionError(
|
|
656
|
+
`Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
|
|
657
|
+
"UNKNOWN_PARTICIPANT",
|
|
658
|
+
{ cohortId: this.cohort.id, participantDid }
|
|
659
|
+
);
|
|
660
|
+
}
|
|
517
661
|
if (nonceContribution.length !== 66) {
|
|
518
662
|
throw new SigningSessionError(
|
|
519
663
|
`Invalid nonce contribution: expected 66 bytes, got ${nonceContribution.length}.`,
|
|
@@ -549,6 +693,13 @@ var BeaconSigningSession = class {
|
|
|
549
693
|
"INVALID_PHASE"
|
|
550
694
|
);
|
|
551
695
|
}
|
|
696
|
+
if (!this.cohort.participants.includes(participantDid)) {
|
|
697
|
+
throw new SigningSessionError(
|
|
698
|
+
`Participant ${participantDid} is not a member of cohort ${this.cohort.id}.`,
|
|
699
|
+
"UNKNOWN_PARTICIPANT",
|
|
700
|
+
{ cohortId: this.cohort.id, participantDid }
|
|
701
|
+
);
|
|
702
|
+
}
|
|
552
703
|
if (this.partialSignatures.has(participantDid)) {
|
|
553
704
|
throw new SigningSessionError(
|
|
554
705
|
`Duplicate partial signature from ${participantDid}.`,
|
|
@@ -574,9 +725,39 @@ var BeaconSigningSession = class {
|
|
|
574
725
|
this.aggregatedNonce,
|
|
575
726
|
this.cohort.cohortKeys,
|
|
576
727
|
this.sigHash,
|
|
577
|
-
[this.cohort.
|
|
728
|
+
[this.cohort.tapTweak],
|
|
578
729
|
[true]
|
|
579
730
|
);
|
|
731
|
+
const pubNoncesByIndex = new Array(this.cohort.cohortKeys.length);
|
|
732
|
+
for (const [did, nonce] of this.nonceContributions) {
|
|
733
|
+
const idx = this.cohort.indexOfParticipant(did);
|
|
734
|
+
if (idx < 0) {
|
|
735
|
+
throw new SigningSessionError(
|
|
736
|
+
`Cannot verify nonce from ${did}: participant key missing from cohort.`,
|
|
737
|
+
"UNKNOWN_PARTICIPANT_KEY",
|
|
738
|
+
{ participantDid: did }
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
pubNoncesByIndex[idx] = nonce;
|
|
742
|
+
}
|
|
743
|
+
for (const [did, partialSig] of this.partialSignatures) {
|
|
744
|
+
const idx = this.cohort.indexOfParticipant(did);
|
|
745
|
+
if (idx < 0) {
|
|
746
|
+
throw new SigningSessionError(
|
|
747
|
+
`Cannot verify partial signature from ${did}: participant key missing from cohort.`,
|
|
748
|
+
"UNKNOWN_PARTICIPANT_KEY",
|
|
749
|
+
{ participantDid: did }
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const ok = session.partialSigVerify(partialSig, pubNoncesByIndex, idx);
|
|
753
|
+
if (!ok) {
|
|
754
|
+
throw new SigningSessionError(
|
|
755
|
+
`Bad partial signature from ${did}.`,
|
|
756
|
+
"BAD_PARTIAL_SIG",
|
|
757
|
+
{ participantDid: did, index: idx }
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
580
761
|
this.signature = session.partialSigAgg([...this.partialSignatures.values()]);
|
|
581
762
|
this.phase = "Complete" /* Complete */;
|
|
582
763
|
return this.signature;
|
|
@@ -594,6 +775,10 @@ var BeaconSigningSession = class {
|
|
|
594
775
|
/**
|
|
595
776
|
* Generates a partial signature using the participant's secret key + secret nonce.
|
|
596
777
|
* Requires the aggregated nonce to have been set first (via the service).
|
|
778
|
+
*
|
|
779
|
+
* Zeros the stored `secretNonce` after use. JS cannot truly erase memory (GC
|
|
780
|
+
* and immutable strings), but overwriting the bytes shortens the exposure
|
|
781
|
+
* window and prevents accidental reuse or serialization of a spent nonce.
|
|
597
782
|
*/
|
|
598
783
|
generatePartialSignature(participantSecretKey) {
|
|
599
784
|
if (!this.aggregatedNonce) {
|
|
@@ -606,10 +791,13 @@ var BeaconSigningSession = class {
|
|
|
606
791
|
this.aggregatedNonce,
|
|
607
792
|
this.cohort.cohortKeys,
|
|
608
793
|
this.sigHash,
|
|
609
|
-
[this.cohort.
|
|
794
|
+
[this.cohort.tapTweak],
|
|
610
795
|
[true]
|
|
611
796
|
);
|
|
612
|
-
|
|
797
|
+
const partialSig = session.sign(this.secretNonce, participantSecretKey);
|
|
798
|
+
this.secretNonce.fill(0);
|
|
799
|
+
this.secretNonce = void 0;
|
|
800
|
+
return partialSig;
|
|
613
801
|
}
|
|
614
802
|
isComplete() {
|
|
615
803
|
return this.phase === "Complete" /* Complete */;
|
|
@@ -620,16 +808,32 @@ var BeaconSigningSession = class {
|
|
|
620
808
|
};
|
|
621
809
|
|
|
622
810
|
// src/core/aggregation/service.ts
|
|
811
|
+
var DEFAULT_MAX_UPDATE_SIZE_BYTES = 256 * 1024;
|
|
623
812
|
var AggregationService = class {
|
|
624
813
|
did;
|
|
625
814
|
keys;
|
|
815
|
+
maxUpdateSizeBytes;
|
|
626
816
|
/** Per-cohort state, keyed by cohortId. */
|
|
627
817
|
#cohortStates = /* @__PURE__ */ new Map();
|
|
628
|
-
constructor({ did, keys }) {
|
|
818
|
+
constructor({ did, keys, maxUpdateSizeBytes }) {
|
|
629
819
|
this.did = did;
|
|
630
820
|
this.keys = keys;
|
|
821
|
+
this.maxUpdateSizeBytes = maxUpdateSizeBytes ?? DEFAULT_MAX_UPDATE_SIZE_BYTES;
|
|
631
822
|
}
|
|
632
823
|
receive(message) {
|
|
824
|
+
const version = message.version;
|
|
825
|
+
if (version === void 0 || version !== AGGREGATION_WIRE_VERSION) {
|
|
826
|
+
const cohortId = message.body?.cohortId;
|
|
827
|
+
const state = cohortId ? this.#cohortStates.get(cohortId) : void 0;
|
|
828
|
+
if (state) {
|
|
829
|
+
state.rejections.push({
|
|
830
|
+
from: message.from,
|
|
831
|
+
code: "WRONG_VERSION",
|
|
832
|
+
reason: `Expected wire version ${AGGREGATION_WIRE_VERSION}, got ${String(version)}`
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
633
837
|
const type = message.type;
|
|
634
838
|
switch (type) {
|
|
635
839
|
case COHORT_OPT_IN:
|
|
@@ -651,6 +855,18 @@ var AggregationService = class {
|
|
|
651
855
|
break;
|
|
652
856
|
}
|
|
653
857
|
}
|
|
858
|
+
/**
|
|
859
|
+
* Drain the rejection log for a cohort. Used by runners to surface silent
|
|
860
|
+
* drops (bad proof, oversized update, wrong version, etc.) as structured
|
|
861
|
+
* events without breaking the sans-I/O state machine contract.
|
|
862
|
+
*/
|
|
863
|
+
drainRejections(cohortId) {
|
|
864
|
+
const state = this.#cohortStates.get(cohortId);
|
|
865
|
+
if (!state) return [];
|
|
866
|
+
const out = state.rejections;
|
|
867
|
+
state.rejections = [];
|
|
868
|
+
return out;
|
|
869
|
+
}
|
|
654
870
|
/**
|
|
655
871
|
* Create a new cohort with the given config. Returns the cohort ID.
|
|
656
872
|
* Cohort starts in `Created` phase — call `advertise()` to broadcast.
|
|
@@ -667,7 +883,8 @@ var AggregationService = class {
|
|
|
667
883
|
cohort,
|
|
668
884
|
config,
|
|
669
885
|
pendingOptIns: /* @__PURE__ */ new Map(),
|
|
670
|
-
acceptedParticipants: /* @__PURE__ */ new Set()
|
|
886
|
+
acceptedParticipants: /* @__PURE__ */ new Set(),
|
|
887
|
+
rejections: []
|
|
671
888
|
});
|
|
672
889
|
return cohort.id;
|
|
673
890
|
}
|
|
@@ -720,6 +937,7 @@ var AggregationService = class {
|
|
|
720
937
|
const participantPk = message.body?.participantPk;
|
|
721
938
|
const communicationPk = message.body?.communicationPk;
|
|
722
939
|
if (!participantPk || !communicationPk) return;
|
|
940
|
+
if (state.acceptedParticipants.has(participantDid)) return;
|
|
723
941
|
state.pendingOptIns.set(participantDid, {
|
|
724
942
|
cohortId,
|
|
725
943
|
participantDid,
|
|
@@ -753,6 +971,7 @@ var AggregationService = class {
|
|
|
753
971
|
}
|
|
754
972
|
state.acceptedParticipants.add(participantDid);
|
|
755
973
|
state.cohort.participants.push(participantDid);
|
|
974
|
+
state.cohort.participantKeys.set(participantDid, optIn.participantPk);
|
|
756
975
|
state.cohort.cohortKeys = [...state.cohort.cohortKeys, optIn.participantPk];
|
|
757
976
|
return [createCohortOptInAcceptMessage({
|
|
758
977
|
from: this.did,
|
|
@@ -803,6 +1022,13 @@ var AggregationService = class {
|
|
|
803
1022
|
if (!state) return /* @__PURE__ */ new Map();
|
|
804
1023
|
return state.cohort.pendingUpdates;
|
|
805
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Handle an incoming SUBMIT_UPDATE message from a participant containing their signed update to
|
|
1027
|
+
* submit for aggregation.
|
|
1028
|
+
* @param {BaseMessage} message - incoming SUBMIT_UPDATE message containing a participant's signed
|
|
1029
|
+
* update to submit for aggregation
|
|
1030
|
+
* @returns {void} - no return value; updates the service state with the submitted update if valid
|
|
1031
|
+
*/
|
|
806
1032
|
#handleSubmitUpdate(message) {
|
|
807
1033
|
const cohortId = message.body?.cohortId;
|
|
808
1034
|
if (!cohortId) return;
|
|
@@ -810,7 +1036,31 @@ var AggregationService = class {
|
|
|
810
1036
|
if (!state) return;
|
|
811
1037
|
if (state.phase !== "CohortSet" /* CohortSet */ && state.phase !== "CollectingUpdates" /* CollectingUpdates */) return;
|
|
812
1038
|
const signedUpdate = message.body?.signedUpdate;
|
|
813
|
-
if (!signedUpdate)
|
|
1039
|
+
if (!signedUpdate) {
|
|
1040
|
+
state.rejections.push({
|
|
1041
|
+
from: message.from,
|
|
1042
|
+
code: "UPDATE_MALFORMED",
|
|
1043
|
+
reason: "SUBMIT_UPDATE missing signedUpdate body"
|
|
1044
|
+
});
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const canonicalSize = (0, import_common4.canonicalize)(signedUpdate).length;
|
|
1048
|
+
if (canonicalSize > this.maxUpdateSizeBytes) {
|
|
1049
|
+
state.rejections.push({
|
|
1050
|
+
from: message.from,
|
|
1051
|
+
code: "UPDATE_TOO_LARGE",
|
|
1052
|
+
reason: `Canonicalized update is ${canonicalSize} bytes; max allowed is ${this.maxUpdateSizeBytes}`
|
|
1053
|
+
});
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (!this.#verifySubmittedUpdate(state, message.from, signedUpdate)) {
|
|
1057
|
+
state.rejections.push({
|
|
1058
|
+
from: message.from,
|
|
1059
|
+
code: "UPDATE_VERIFICATION_FAILED",
|
|
1060
|
+
reason: "BIP-340 Data Integrity proof verification failed"
|
|
1061
|
+
});
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
814
1064
|
state.cohort.addUpdate(message.from, signedUpdate);
|
|
815
1065
|
if (state.phase === "CohortSet" /* CohortSet */) {
|
|
816
1066
|
state.phase = "CollectingUpdates" /* CollectingUpdates */;
|
|
@@ -819,6 +1069,36 @@ var AggregationService = class {
|
|
|
819
1069
|
state.phase = "UpdatesCollected" /* UpdatesCollected */;
|
|
820
1070
|
}
|
|
821
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Verify the BIP-340 Schnorr Data Integrity proof on a submitted update using the
|
|
1074
|
+
* participant's public key from their cohort opt-in. Returns `false` (and the
|
|
1075
|
+
* update is silently dropped) if the proof is missing, the verificationMethod does
|
|
1076
|
+
* not name the sender's DID, the participant has no opt-in on record, or the
|
|
1077
|
+
* signature fails verification.
|
|
1078
|
+
* @param {ServiceCohortState} state - the current state of the cohort to which the update was submitted
|
|
1079
|
+
* @param {string} sender - the DID of the participant who submitted the update
|
|
1080
|
+
* @param {SignedBTCR2Update} signedUpdate - the signed update containing the proof to verify
|
|
1081
|
+
* @returns {boolean} - `true` if the proof is valid and the update can be accepted; `false` otherwise
|
|
1082
|
+
*/
|
|
1083
|
+
#verifySubmittedUpdate(state, sender, signedUpdate) {
|
|
1084
|
+
const proof = signedUpdate.proof;
|
|
1085
|
+
if (!proof?.verificationMethod || !proof.proofValue) return false;
|
|
1086
|
+
const vmDid = proof.verificationMethod.split("#")[0];
|
|
1087
|
+
if (vmDid !== sender) return false;
|
|
1088
|
+
const optIn = state.pendingOptIns.get(sender);
|
|
1089
|
+
if (!optIn) return false;
|
|
1090
|
+
try {
|
|
1091
|
+
const multikey = import_cryptosuite.SchnorrMultikey.fromPublicKey({
|
|
1092
|
+
id: proof.verificationMethod,
|
|
1093
|
+
controller: sender,
|
|
1094
|
+
publicKeyBytes: optIn.participantPk
|
|
1095
|
+
});
|
|
1096
|
+
const suite = new import_cryptosuite.BIP340Cryptosuite(multikey);
|
|
1097
|
+
return suite.verifyProof(signedUpdate).verified === true;
|
|
1098
|
+
} catch {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
822
1102
|
/**
|
|
823
1103
|
* Build the aggregated data structure (CAS Announcement or SMT tree) and
|
|
824
1104
|
* return distribute messages to send to all participants for validation.
|
|
@@ -835,29 +1115,28 @@ var AggregationService = class {
|
|
|
835
1115
|
{ cohortId, phase: state.phase }
|
|
836
1116
|
);
|
|
837
1117
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
} else if (state.config.beaconType === "SMTBeacon") {
|
|
841
|
-
state.cohort.buildSMTTree();
|
|
842
|
-
} else {
|
|
1118
|
+
const strategy = getBeaconStrategy(state.config.beaconType);
|
|
1119
|
+
if (!strategy) {
|
|
843
1120
|
throw new AggregationServiceError(
|
|
844
1121
|
`Unsupported beacon type: ${state.config.beaconType}`,
|
|
845
1122
|
"UNSUPPORTED_BEACON_TYPE",
|
|
846
1123
|
{ cohortId, beaconType: state.config.beaconType }
|
|
847
1124
|
);
|
|
848
1125
|
}
|
|
1126
|
+
strategy.buildAggregatedData(state.cohort);
|
|
849
1127
|
const signalBytesHex = (0, import_utils2.bytesToHex)(state.cohort.signalBytes);
|
|
850
1128
|
state.phase = "DataDistributed" /* DataDistributed */;
|
|
851
1129
|
const messages = [];
|
|
852
1130
|
for (const participantDid of state.cohort.participants) {
|
|
1131
|
+
const payload = strategy.getDistributePayload(state.cohort, participantDid);
|
|
853
1132
|
messages.push(createDistributeAggregatedDataMessage({
|
|
854
1133
|
from: this.did,
|
|
855
1134
|
to: participantDid,
|
|
856
1135
|
cohortId,
|
|
857
1136
|
beaconType: state.config.beaconType,
|
|
858
1137
|
signalBytesHex,
|
|
859
|
-
casAnnouncement:
|
|
860
|
-
smtProof:
|
|
1138
|
+
casAnnouncement: payload.casAnnouncement,
|
|
1139
|
+
smtProof: payload.smtProof
|
|
861
1140
|
}));
|
|
862
1141
|
}
|
|
863
1142
|
return messages;
|
|
@@ -919,6 +1198,14 @@ var AggregationService = class {
|
|
|
919
1198
|
});
|
|
920
1199
|
state.signingSession = session;
|
|
921
1200
|
state.phase = "SigningStarted" /* SigningStarted */;
|
|
1201
|
+
const prevOutScript = txData.prevOutScripts[0];
|
|
1202
|
+
if (!prevOutScript) {
|
|
1203
|
+
throw new AggregationServiceError(
|
|
1204
|
+
`Cannot start signing for cohort ${cohortId}: txData.prevOutScripts[0] is missing.`,
|
|
1205
|
+
"MISSING_PREV_OUT_SCRIPT",
|
|
1206
|
+
{ cohortId }
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
922
1209
|
const messages = [];
|
|
923
1210
|
for (const participantDid of state.cohort.participants) {
|
|
924
1211
|
messages.push(createAuthorizationRequestMessage({
|
|
@@ -926,7 +1213,8 @@ var AggregationService = class {
|
|
|
926
1213
|
to: participantDid,
|
|
927
1214
|
cohortId,
|
|
928
1215
|
sessionId: session.id,
|
|
929
|
-
pendingTx: txData.tx.
|
|
1216
|
+
pendingTx: txData.tx.hex,
|
|
1217
|
+
prevOutScriptHex: (0, import_utils2.bytesToHex)(prevOutScript),
|
|
930
1218
|
prevOutValue: txData.prevOutValues[0]?.toString() ?? "0"
|
|
931
1219
|
}));
|
|
932
1220
|
}
|
|
@@ -990,7 +1278,7 @@ var AggregationService = class {
|
|
|
990
1278
|
state.signingSession.addPartialSignature(message.from, partialSignature);
|
|
991
1279
|
if (state.signingSession.partialSignatures.size === state.cohort.participants.length) {
|
|
992
1280
|
const signature = state.signingSession.generateFinalSignature();
|
|
993
|
-
state.signingSession.pendingTx.
|
|
1281
|
+
state.signingSession.pendingTx.updateInput(0, { finalScriptWitness: [signature] });
|
|
994
1282
|
state.result = {
|
|
995
1283
|
cohortId,
|
|
996
1284
|
signature,
|
|
@@ -1019,13 +1307,19 @@ var AggregationService = class {
|
|
|
1019
1307
|
get cohorts() {
|
|
1020
1308
|
return [...this.#cohortStates.values()].map((s) => s.cohort);
|
|
1021
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Remove a cohort from the state map. Used by runners to GC state on cohort
|
|
1312
|
+
* completion, failure, or expiry. No-op if the cohort doesn't exist.
|
|
1313
|
+
*/
|
|
1314
|
+
removeCohort(cohortId) {
|
|
1315
|
+
this.#cohortStates.delete(cohortId);
|
|
1316
|
+
}
|
|
1022
1317
|
};
|
|
1023
1318
|
|
|
1024
1319
|
// src/core/aggregation/participant.ts
|
|
1025
|
-
var
|
|
1026
|
-
var import_smt2 = require("@did-btcr2/smt");
|
|
1320
|
+
var import_common5 = require("@did-btcr2/common");
|
|
1027
1321
|
var import_utils3 = require("@noble/hashes/utils");
|
|
1028
|
-
var
|
|
1322
|
+
var import_btc_signer3 = require("@scure/btc-signer");
|
|
1029
1323
|
var AggregationParticipant = class {
|
|
1030
1324
|
did;
|
|
1031
1325
|
keys;
|
|
@@ -1040,6 +1334,9 @@ var AggregationParticipant = class {
|
|
|
1040
1334
|
* outgoing messages — those come exclusively from action methods.
|
|
1041
1335
|
*/
|
|
1042
1336
|
receive(message) {
|
|
1337
|
+
if (message.version === void 0 || message.version !== AGGREGATION_WIRE_VERSION) {
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1043
1340
|
const type = message.type;
|
|
1044
1341
|
switch (type) {
|
|
1045
1342
|
case COHORT_ADVERT:
|
|
@@ -1196,42 +1493,26 @@ var AggregationParticipant = class {
|
|
|
1196
1493
|
if (!state || state.phase !== "UpdateSubmitted" /* UpdateSubmitted */) return;
|
|
1197
1494
|
if (!state.submittedUpdate) return;
|
|
1198
1495
|
const beaconType = message.body?.beaconType;
|
|
1496
|
+
if (!beaconType) return;
|
|
1497
|
+
const strategy = getBeaconStrategy(beaconType);
|
|
1498
|
+
if (!strategy) return;
|
|
1199
1499
|
const signalBytesHex = message.body?.signalBytesHex ?? "";
|
|
1200
|
-
const expectedHash = (0,
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
const smtProof = message.body?.smtProof;
|
|
1217
|
-
if (smtProof?.updateId && smtProof?.nonce) {
|
|
1218
|
-
const canonicalBytes = new TextEncoder().encode((0, import_common3.canonicalize)(state.submittedUpdate));
|
|
1219
|
-
const expectedUpdateId = (0, import_smt2.hashToHex)((0, import_smt2.blockHash)(canonicalBytes));
|
|
1220
|
-
if (smtProof.updateId === expectedUpdateId) {
|
|
1221
|
-
const index = (0, import_smt2.didToIndex)(this.did);
|
|
1222
|
-
const candidateHash = (0, import_smt2.blockHash)((0, import_smt2.blockHash)((0, import_smt2.hexToHash)(smtProof.nonce)), (0, import_smt2.hexToHash)(smtProof.updateId));
|
|
1223
|
-
matches = (0, import_smt2.verifySerializedProof)(smtProof, index, candidateHash);
|
|
1224
|
-
}
|
|
1225
|
-
state.validation = {
|
|
1226
|
-
cohortId,
|
|
1227
|
-
beaconType,
|
|
1228
|
-
signalBytesHex,
|
|
1229
|
-
smtProof,
|
|
1230
|
-
expectedHash,
|
|
1231
|
-
matches
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1500
|
+
const expectedHash = (0, import_common5.canonicalHash)(state.submittedUpdate);
|
|
1501
|
+
const result = strategy.validateParticipantView({
|
|
1502
|
+
participantDid: this.did,
|
|
1503
|
+
submittedUpdate: state.submittedUpdate,
|
|
1504
|
+
expectedHash,
|
|
1505
|
+
body: message.body
|
|
1506
|
+
});
|
|
1507
|
+
state.validation = {
|
|
1508
|
+
cohortId,
|
|
1509
|
+
beaconType,
|
|
1510
|
+
signalBytesHex,
|
|
1511
|
+
expectedHash,
|
|
1512
|
+
matches: result.matches,
|
|
1513
|
+
casAnnouncement: result.casAnnouncement,
|
|
1514
|
+
smtProof: result.smtProof
|
|
1515
|
+
};
|
|
1235
1516
|
state.phase = "AwaitingValidation" /* AwaitingValidation */;
|
|
1236
1517
|
}
|
|
1237
1518
|
/**
|
|
@@ -1292,12 +1573,14 @@ var AggregationParticipant = class {
|
|
|
1292
1573
|
if (state.phase !== "ValidationSent" /* ValidationSent */) return;
|
|
1293
1574
|
const sessionId = message.body?.sessionId;
|
|
1294
1575
|
const pendingTxHex = message.body?.pendingTx;
|
|
1576
|
+
const prevOutScriptHex = message.body?.prevOutScriptHex;
|
|
1295
1577
|
const prevOutValue = message.body?.prevOutValue;
|
|
1296
|
-
if (!sessionId || !pendingTxHex || !prevOutValue) return;
|
|
1578
|
+
if (!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue) return;
|
|
1297
1579
|
state.signingRequest = {
|
|
1298
1580
|
cohortId,
|
|
1299
1581
|
sessionId,
|
|
1300
1582
|
pendingTxHex,
|
|
1583
|
+
prevOutScriptHex,
|
|
1301
1584
|
prevOutValue
|
|
1302
1585
|
};
|
|
1303
1586
|
state.phase = "AwaitingSigning" /* AwaitingSigning */;
|
|
@@ -1321,8 +1604,8 @@ var AggregationParticipant = class {
|
|
|
1321
1604
|
{ cohortId }
|
|
1322
1605
|
);
|
|
1323
1606
|
}
|
|
1324
|
-
const tx =
|
|
1325
|
-
const prevOutScripts =
|
|
1607
|
+
const tx = import_btc_signer3.Transaction.fromRaw((0, import_utils3.hexToBytes)(state.signingRequest.pendingTxHex));
|
|
1608
|
+
const prevOutScripts = [(0, import_utils3.hexToBytes)(state.signingRequest.prevOutScriptHex)];
|
|
1326
1609
|
const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
|
|
1327
1610
|
const session = new BeaconSigningSession({
|
|
1328
1611
|
id: state.signingRequest.sessionId,
|
|
@@ -1392,6 +1675,67 @@ var AggregationParticipant = class {
|
|
|
1392
1675
|
}
|
|
1393
1676
|
};
|
|
1394
1677
|
|
|
1678
|
+
// src/core/aggregation/logger.ts
|
|
1679
|
+
var CONSOLE_LOGGER = {
|
|
1680
|
+
debug: (msg, ...args) => console.debug(msg, ...args),
|
|
1681
|
+
info: (msg, ...args) => console.info(msg, ...args),
|
|
1682
|
+
warn: (msg, ...args) => console.warn(msg, ...args),
|
|
1683
|
+
error: (msg, ...args) => console.error(msg, ...args)
|
|
1684
|
+
};
|
|
1685
|
+
var SILENT_LOGGER = {
|
|
1686
|
+
debug: () => {
|
|
1687
|
+
},
|
|
1688
|
+
info: () => {
|
|
1689
|
+
},
|
|
1690
|
+
warn: () => {
|
|
1691
|
+
},
|
|
1692
|
+
error: () => {
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
// src/core/aggregation/messages/bodies.ts
|
|
1697
|
+
var hasStr = (b, k) => !!b && typeof b[k] === "string";
|
|
1698
|
+
var hasNum = (b, k) => !!b && typeof b[k] === "number";
|
|
1699
|
+
var hasBool = (b, k) => !!b && typeof b[k] === "boolean";
|
|
1700
|
+
var hasBytes = (b, k) => !!b && b[k] instanceof Uint8Array;
|
|
1701
|
+
var hasBytesArray = (b, k) => {
|
|
1702
|
+
const v = b ? b[k] : void 0;
|
|
1703
|
+
return Array.isArray(v) && v.every((x) => x instanceof Uint8Array);
|
|
1704
|
+
};
|
|
1705
|
+
function isCohortAdvertMessage(m) {
|
|
1706
|
+
return m.type === COHORT_ADVERT && hasStr(m.body, "cohortId") && hasNum(m.body, "cohortSize") && hasStr(m.body, "beaconType") && hasStr(m.body, "network") && hasBytes(m.body, "communicationPk");
|
|
1707
|
+
}
|
|
1708
|
+
function isCohortOptInMessage(m) {
|
|
1709
|
+
return m.type === COHORT_OPT_IN && hasStr(m.body, "cohortId") && hasBytes(m.body, "participantPk") && hasBytes(m.body, "communicationPk");
|
|
1710
|
+
}
|
|
1711
|
+
function isCohortOptInAcceptMessage(m) {
|
|
1712
|
+
return m.type === COHORT_OPT_IN_ACCEPT && hasStr(m.body, "cohortId");
|
|
1713
|
+
}
|
|
1714
|
+
function isCohortReadyMessage(m) {
|
|
1715
|
+
return m.type === COHORT_READY && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconAddress") && hasBytesArray(m.body, "cohortKeys");
|
|
1716
|
+
}
|
|
1717
|
+
function isSubmitUpdateMessage(m) {
|
|
1718
|
+
return m.type === SUBMIT_UPDATE && hasStr(m.body, "cohortId") && !!m.body && typeof m.body.signedUpdate === "object";
|
|
1719
|
+
}
|
|
1720
|
+
function isDistributeAggregatedDataMessage(m) {
|
|
1721
|
+
return m.type === DISTRIBUTE_AGGREGATED_DATA && hasStr(m.body, "cohortId") && hasStr(m.body, "beaconType") && hasStr(m.body, "signalBytesHex");
|
|
1722
|
+
}
|
|
1723
|
+
function isValidationAckMessage(m) {
|
|
1724
|
+
return m.type === VALIDATION_ACK && hasStr(m.body, "cohortId") && hasBool(m.body, "approved");
|
|
1725
|
+
}
|
|
1726
|
+
function isAuthorizationRequestMessage(m) {
|
|
1727
|
+
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");
|
|
1728
|
+
}
|
|
1729
|
+
function isNonceContributionMessage(m) {
|
|
1730
|
+
return m.type === NONCE_CONTRIBUTION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "nonceContribution");
|
|
1731
|
+
}
|
|
1732
|
+
function isAggregatedNonceMessage(m) {
|
|
1733
|
+
return m.type === AGGREGATED_NONCE && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "aggregatedNonce");
|
|
1734
|
+
}
|
|
1735
|
+
function isSignatureAuthorizationMessage(m) {
|
|
1736
|
+
return m.type === SIGNATURE_AUTHORIZATION && hasStr(m.body, "cohortId") && hasStr(m.body, "sessionId") && hasBytes(m.body, "partialSignature");
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1395
1739
|
// src/core/aggregation/messages/guards.ts
|
|
1396
1740
|
var KEYGEN_VALUES = /* @__PURE__ */ new Set([
|
|
1397
1741
|
COHORT_ADVERT,
|
|
@@ -1424,132 +1768,1610 @@ function isSignMessageType(type) {
|
|
|
1424
1768
|
}
|
|
1425
1769
|
|
|
1426
1770
|
// src/core/aggregation/transport/error.ts
|
|
1427
|
-
var
|
|
1428
|
-
var TransportError = class extends
|
|
1771
|
+
var import_common6 = require("@did-btcr2/common");
|
|
1772
|
+
var TransportError = class extends import_common6.MethodError {
|
|
1429
1773
|
constructor(message, type = "TransportError", data) {
|
|
1430
1774
|
super(message, type, data);
|
|
1431
1775
|
}
|
|
1432
1776
|
};
|
|
1433
|
-
var TransportAdapterError = class extends
|
|
1777
|
+
var TransportAdapterError = class extends import_common6.MethodError {
|
|
1434
1778
|
constructor(message, type = "TransportAdapterError", data) {
|
|
1435
1779
|
super(message, type, data);
|
|
1436
1780
|
}
|
|
1437
1781
|
};
|
|
1438
1782
|
|
|
1439
1783
|
// src/core/aggregation/transport/factory.ts
|
|
1440
|
-
var
|
|
1784
|
+
var import_common10 = require("@did-btcr2/common");
|
|
1441
1785
|
|
|
1442
|
-
// src/core/aggregation/transport/
|
|
1786
|
+
// src/core/aggregation/transport/http/client.ts
|
|
1787
|
+
var import_keypair2 = require("@did-btcr2/keypair");
|
|
1788
|
+
|
|
1789
|
+
// src/core/identifier.ts
|
|
1790
|
+
var import_common7 = require("@did-btcr2/common");
|
|
1443
1791
|
var import_keypair = require("@did-btcr2/keypair");
|
|
1444
|
-
var
|
|
1445
|
-
var
|
|
1446
|
-
var import_pool = require("nostr-tools/pool");
|
|
1447
|
-
var DEFAULT_NOSTR_RELAYS = [
|
|
1448
|
-
"wss://relay.damus.io",
|
|
1449
|
-
"wss://nos.lol",
|
|
1450
|
-
"wss://relay.snort.social",
|
|
1451
|
-
"wss://nostr-pub.wellorder.net"
|
|
1452
|
-
];
|
|
1453
|
-
var NostrTransport = class _NostrTransport {
|
|
1454
|
-
name = "nostr";
|
|
1455
|
-
pool;
|
|
1456
|
-
#relays;
|
|
1457
|
-
#actors = /* @__PURE__ */ new Map();
|
|
1458
|
-
#peerRegistry = /* @__PURE__ */ new Map();
|
|
1459
|
-
#started = false;
|
|
1460
|
-
constructor(config) {
|
|
1461
|
-
this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
|
|
1462
|
-
}
|
|
1792
|
+
var import_base4 = require("@scure/base");
|
|
1793
|
+
var Identifier = class _Identifier {
|
|
1463
1794
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1795
|
+
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
|
|
1796
|
+
*
|
|
1797
|
+
* A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
|
|
1798
|
+
* - the specification version;
|
|
1799
|
+
* - the Bitcoin network identifier; and
|
|
1800
|
+
* - either:
|
|
1801
|
+
* - a key-value representing a secp256k1 public key; or
|
|
1802
|
+
* - a hash-value representing the hash of an initiating external DID document.
|
|
1803
|
+
*
|
|
1804
|
+
* @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
|
|
1805
|
+
* @param {DidCreateOptions} options The DID creation options.
|
|
1806
|
+
* @returns {string} The new did:btcr2 identifier.
|
|
1474
1807
|
*/
|
|
1475
|
-
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
this.#subscribeDirected(did, entry);
|
|
1808
|
+
static encode(genesisBytes, options) {
|
|
1809
|
+
const { idType, version = 1, network } = options;
|
|
1810
|
+
if (!(idType in import_common7.IdentifierTypes)) {
|
|
1811
|
+
throw new import_common7.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common7.INVALID_DID, { idType });
|
|
1480
1812
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
return this.#actors.get(did)?.keys.publicKey.compressed;
|
|
1484
|
-
}
|
|
1485
|
-
registerPeer(did, communicationPk) {
|
|
1486
|
-
try {
|
|
1487
|
-
new import_keypair.CompressedSecp256k1PublicKey(communicationPk);
|
|
1488
|
-
} catch {
|
|
1489
|
-
throw new TransportAdapterError(
|
|
1490
|
-
`Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
|
|
1491
|
-
"INVALID_PEER_KEY",
|
|
1492
|
-
{ adapter: this.name, did, keyLength: communicationPk.length }
|
|
1493
|
-
);
|
|
1813
|
+
if (isNaN(version) || version > 1) {
|
|
1814
|
+
throw new import_common7.IdentifierError('Expected "version" to be 1', import_common7.INVALID_DID, { version });
|
|
1494
1815
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
getPeerPk(did) {
|
|
1498
|
-
return this.#peerRegistry.get(did);
|
|
1499
|
-
}
|
|
1500
|
-
registerMessageHandler(actorDid, messageType, handler) {
|
|
1501
|
-
const actor = this.#actors.get(actorDid);
|
|
1502
|
-
if (!actor) {
|
|
1503
|
-
throw new TransportAdapterError(
|
|
1504
|
-
`Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
|
|
1505
|
-
"UNKNOWN_ACTOR_ERROR",
|
|
1506
|
-
{ adapter: this.name, did: actorDid }
|
|
1507
|
-
);
|
|
1816
|
+
if (typeof network === "string" && !(network in import_common7.BitcoinNetworkNames)) {
|
|
1817
|
+
throw new import_common7.IdentifierError('Invalid "network" name', import_common7.INVALID_DID, { network });
|
|
1508
1818
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
start() {
|
|
1512
|
-
if (this.#started) return this;
|
|
1513
|
-
this.#started = true;
|
|
1514
|
-
this.pool = new import_pool.SimplePool();
|
|
1515
|
-
const since = Math.floor(Date.now() / 1e3);
|
|
1516
|
-
this.pool.subscribeMany(this.#relays, { kinds: [1], "#t": [COHORT_ADVERT], since }, {
|
|
1517
|
-
onclose: (reasons) => console.debug("Nostr broadcast subscription closed", reasons),
|
|
1518
|
-
onevent: this.#handleBroadcastEvent.bind(this)
|
|
1519
|
-
});
|
|
1520
|
-
for (const [did, entry] of this.#actors) {
|
|
1521
|
-
this.#subscribeDirected(did, entry);
|
|
1819
|
+
if (typeof network === "number" && (network < 0 || network > 8)) {
|
|
1820
|
+
throw new import_common7.IdentifierError('Invalid "network" number', import_common7.INVALID_DID, { network });
|
|
1522
1821
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
);
|
|
1822
|
+
if (idType === "KEY") {
|
|
1823
|
+
try {
|
|
1824
|
+
new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
|
|
1825
|
+
} catch {
|
|
1826
|
+
throw new import_common7.IdentifierError(
|
|
1827
|
+
'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
|
|
1828
|
+
import_common7.INVALID_DID,
|
|
1829
|
+
{ genesisBytes }
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1534
1832
|
}
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1833
|
+
const hrp = idType === "KEY" ? "k" : "x";
|
|
1834
|
+
const nibbles = [];
|
|
1835
|
+
const fCount = Math.floor((version - 1) / 15);
|
|
1836
|
+
for (let i = 0; i < fCount; i++) {
|
|
1837
|
+
nibbles.push(15);
|
|
1838
|
+
}
|
|
1839
|
+
nibbles.push((version - 1) % 15);
|
|
1840
|
+
if (typeof network === "string") {
|
|
1841
|
+
nibbles.push(import_common7.BitcoinNetworkNames[network]);
|
|
1842
|
+
} else if (typeof network === "number") {
|
|
1843
|
+
nibbles.push(network + 11);
|
|
1844
|
+
}
|
|
1845
|
+
if (nibbles.length % 2 !== 0) {
|
|
1846
|
+
nibbles.push(0);
|
|
1847
|
+
}
|
|
1848
|
+
if (fCount !== 0) {
|
|
1849
|
+
for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
|
|
1850
|
+
throw new import_common7.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
|
|
1854
|
+
return `did:btcr2:${import_base4.bech32m.encodeFromBytes(hrp, dataBytes)}`;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
|
|
1858
|
+
* @param {string} identifier The BTCR2 DID to be parsed
|
|
1859
|
+
* @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
|
|
1860
|
+
* @throws {DidError} if an error occurs while parsing the identifier
|
|
1861
|
+
* @throws {DidErrorCode.InvalidDid} if identifier is invalid
|
|
1862
|
+
* @throws {DidErrorCode.MethodNotSupported} if the method is not supported
|
|
1863
|
+
*/
|
|
1864
|
+
static decode(identifier) {
|
|
1865
|
+
const components = identifier.split(":");
|
|
1866
|
+
if (components.length !== 3) {
|
|
1867
|
+
throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
|
|
1868
|
+
}
|
|
1869
|
+
const [scheme, method, encoded] = components;
|
|
1870
|
+
if (scheme !== "did") {
|
|
1871
|
+
throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
|
|
1872
|
+
}
|
|
1873
|
+
if (method !== "btcr2") {
|
|
1874
|
+
throw new import_common7.IdentifierError(`Invalid did method: ${method}`, import_common7.METHOD_NOT_SUPPORTED, { identifier });
|
|
1875
|
+
}
|
|
1876
|
+
if (!encoded) {
|
|
1877
|
+
throw new import_common7.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common7.INVALID_DID, { identifier });
|
|
1878
|
+
}
|
|
1879
|
+
const { prefix: hrp, bytes: dataBytes } = import_base4.bech32m.decodeToBytes(encoded);
|
|
1880
|
+
if (!["x", "k"].includes(hrp)) {
|
|
1881
|
+
throw new import_common7.IdentifierError(`Invalid hrp: ${hrp}`, import_common7.INVALID_DID, { identifier });
|
|
1882
|
+
}
|
|
1883
|
+
if (!dataBytes) {
|
|
1884
|
+
throw new import_common7.IdentifierError(`Failed to decode id: ${encoded}`, import_common7.INVALID_DID, { identifier });
|
|
1885
|
+
}
|
|
1886
|
+
const idType = hrp === "k" ? "KEY" : "EXTERNAL";
|
|
1887
|
+
let version = 1;
|
|
1888
|
+
let byteIndex = 0;
|
|
1889
|
+
let nibblesConsumed = 0;
|
|
1890
|
+
let currentByte = dataBytes[byteIndex];
|
|
1891
|
+
let versionNibble = currentByte >>> 4;
|
|
1892
|
+
while (versionNibble === 15) {
|
|
1893
|
+
version += 15;
|
|
1894
|
+
if (nibblesConsumed % 2 === 0) {
|
|
1895
|
+
versionNibble = currentByte & 15;
|
|
1896
|
+
} else {
|
|
1897
|
+
currentByte = dataBytes[++byteIndex];
|
|
1898
|
+
versionNibble = currentByte >>> 4;
|
|
1899
|
+
}
|
|
1900
|
+
nibblesConsumed += 1;
|
|
1901
|
+
if (version > 1) {
|
|
1902
|
+
throw new import_common7.IdentifierError(`Invalid version: ${version}`, import_common7.INVALID_DID, { identifier });
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
version += versionNibble;
|
|
1906
|
+
nibblesConsumed += 1;
|
|
1907
|
+
let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
|
|
1908
|
+
nibblesConsumed += 1;
|
|
1909
|
+
let network = import_common7.BitcoinNetworkNames[networkValue];
|
|
1910
|
+
if (!network) {
|
|
1911
|
+
if (networkValue >= 8 && networkValue <= 15) {
|
|
1912
|
+
network = networkValue - 11;
|
|
1913
|
+
} else {
|
|
1914
|
+
throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (nibblesConsumed % 2 === 1) {
|
|
1918
|
+
const fillerNibble = currentByte & 15;
|
|
1919
|
+
if (fillerNibble !== 0) {
|
|
1920
|
+
throw new import_common7.IdentifierError(`Invalid did: ${identifier}`, import_common7.INVALID_DID, { identifier });
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const genesisBytes = dataBytes.slice(byteIndex + 1);
|
|
1924
|
+
if (idType === "KEY") {
|
|
1925
|
+
try {
|
|
1926
|
+
new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
|
|
1927
|
+
} catch {
|
|
1928
|
+
throw new import_common7.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common7.INVALID_DID, { identifier });
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return { idType, hrp, version, network, genesisBytes };
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Generates a new did:btcr2 identifier based on a newly generated key pair.
|
|
1935
|
+
* @returns {string} The new did:btcr2 identifier.
|
|
1936
|
+
*/
|
|
1937
|
+
static generate() {
|
|
1938
|
+
const keyPair = import_keypair.SchnorrKeyPair.generate();
|
|
1939
|
+
const did = this.encode(
|
|
1940
|
+
keyPair.publicKey.compressed,
|
|
1941
|
+
{
|
|
1942
|
+
idType: "KEY",
|
|
1943
|
+
version: 1,
|
|
1944
|
+
network: "regtest"
|
|
1945
|
+
}
|
|
1946
|
+
);
|
|
1947
|
+
return { keyPair: keyPair.exportJSON(), did };
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
|
|
1951
|
+
* @param {string} did The did:btcr2 identifier to extract the public key from.
|
|
1952
|
+
* @returns {CompressedSecp256k1PublicKey} The compressed public key.
|
|
1953
|
+
* @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
|
|
1954
|
+
*/
|
|
1955
|
+
static getPublicKey(did) {
|
|
1956
|
+
const { idType, genesisBytes } = _Identifier.decode(did);
|
|
1957
|
+
if (idType !== "KEY") {
|
|
1958
|
+
throw new import_common7.IdentifierError(
|
|
1959
|
+
`Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
|
|
1960
|
+
import_common7.INVALID_DID,
|
|
1961
|
+
{ did, idType }
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
return new import_keypair.CompressedSecp256k1PublicKey(genesisBytes);
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Validates a did:btcr2 identifier.
|
|
1968
|
+
* @param {string} identifier The did:btcr2 identifier to validate.
|
|
1969
|
+
* @returns {boolean} True if the identifier is valid, false otherwise.
|
|
1970
|
+
*/
|
|
1971
|
+
static isValid(identifier) {
|
|
1972
|
+
try {
|
|
1973
|
+
this.decode(identifier);
|
|
1974
|
+
return true;
|
|
1975
|
+
} catch {
|
|
1976
|
+
return false;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1981
|
+
// src/core/aggregation/transport/http/envelope.ts
|
|
1982
|
+
var import_common8 = require("@did-btcr2/common");
|
|
1983
|
+
var import_utils4 = require("@noble/hashes/utils");
|
|
1984
|
+
|
|
1985
|
+
// src/core/aggregation/transport/http/errors.ts
|
|
1986
|
+
var HttpTransportError = class extends TransportAdapterError {
|
|
1987
|
+
constructor(message, type = "HttpTransportError", data) {
|
|
1988
|
+
super(message, type, { adapter: "http", ...data ?? {} });
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
// src/core/aggregation/transport/http/protocol.ts
|
|
1993
|
+
var HTTP_ENVELOPE_VERSION = 1;
|
|
1994
|
+
var HTTP_ROUTE = {
|
|
1995
|
+
ADVERTS: "/v1/adverts",
|
|
1996
|
+
MESSAGES: "/v1/messages",
|
|
1997
|
+
ACTOR_INBOX: "/v1/actors/{did}/inbox",
|
|
1998
|
+
WELL_KNOWN: "/v1/.well-known/aggregation"
|
|
1999
|
+
};
|
|
2000
|
+
var SSE_EVENT = {
|
|
2001
|
+
ADVERT: "advert",
|
|
2002
|
+
MESSAGE: "message",
|
|
2003
|
+
HEARTBEAT: "heartbeat"
|
|
2004
|
+
};
|
|
2005
|
+
var DEFAULT_CLOCK_SKEW_SEC = 60;
|
|
2006
|
+
var DEFAULT_NONCE_LEN_BYTES = 16;
|
|
2007
|
+
|
|
2008
|
+
// src/core/aggregation/transport/http/envelope.ts
|
|
2009
|
+
function signEnvelope(message, sender, opts = {}) {
|
|
2010
|
+
const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2011
|
+
const nonce = opts.nonce ?? (0, import_utils4.bytesToHex)((0, import_utils4.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2012
|
+
const messageJson = normalizeForWire(normalizeMessage(message));
|
|
2013
|
+
const unsigned = {
|
|
2014
|
+
v: HTTP_ENVELOPE_VERSION,
|
|
2015
|
+
from: sender.did,
|
|
2016
|
+
...opts.to !== void 0 ? { to: opts.to } : {},
|
|
2017
|
+
timestamp,
|
|
2018
|
+
nonce,
|
|
2019
|
+
message: messageJson
|
|
2020
|
+
};
|
|
2021
|
+
const digest = (0, import_common8.canonicalHashBytes)(unsigned);
|
|
2022
|
+
const sig = sender.keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2023
|
+
return { ...unsigned, sig: (0, import_utils4.bytesToHex)(sig) };
|
|
2024
|
+
}
|
|
2025
|
+
function verifyEnvelope(envelope, senderPk, opts = {}) {
|
|
2026
|
+
if (envelope.v !== HTTP_ENVELOPE_VERSION) {
|
|
2027
|
+
throw new HttpTransportError(
|
|
2028
|
+
`Unsupported envelope version: ${envelope.v}`,
|
|
2029
|
+
"ENVELOPE_VERSION_MISMATCH",
|
|
2030
|
+
{ version: envelope.v, expected: HTTP_ENVELOPE_VERSION }
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
if (opts.expectedFrom !== void 0 && envelope.from !== opts.expectedFrom) {
|
|
2034
|
+
throw new HttpTransportError(
|
|
2035
|
+
`Envelope from mismatch: expected ${opts.expectedFrom}, got ${envelope.from}`,
|
|
2036
|
+
"ENVELOPE_FROM_MISMATCH",
|
|
2037
|
+
{ expected: opts.expectedFrom, got: envelope.from }
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
if ("expectedTo" in opts && envelope.to !== opts.expectedTo) {
|
|
2041
|
+
throw new HttpTransportError(
|
|
2042
|
+
`Envelope to mismatch: expected ${opts.expectedTo ?? "<broadcast>"}, got ${envelope.to ?? "<broadcast>"}`,
|
|
2043
|
+
"ENVELOPE_TO_MISMATCH",
|
|
2044
|
+
{ expected: opts.expectedTo, got: envelope.to }
|
|
2045
|
+
);
|
|
2046
|
+
}
|
|
2047
|
+
const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2048
|
+
const nowMs = opts.now ? opts.now() : Date.now();
|
|
2049
|
+
const nowSec = Math.floor(nowMs / 1e3);
|
|
2050
|
+
const diff = Math.abs(nowSec - envelope.timestamp);
|
|
2051
|
+
if (diff > skewSec) {
|
|
2052
|
+
throw new HttpTransportError(
|
|
2053
|
+
`Envelope timestamp out of skew: ${diff}s > ${skewSec}s`,
|
|
2054
|
+
"ENVELOPE_TIMESTAMP_SKEW",
|
|
2055
|
+
{ diff, skewSec, timestamp: envelope.timestamp, now: nowSec }
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
let sigBytes;
|
|
2059
|
+
try {
|
|
2060
|
+
sigBytes = (0, import_utils4.hexToBytes)(envelope.sig);
|
|
2061
|
+
} catch {
|
|
2062
|
+
throw new HttpTransportError(
|
|
2063
|
+
"Envelope signature is not valid hex",
|
|
2064
|
+
"ENVELOPE_SIG_HEX"
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
if (sigBytes.length !== 64) {
|
|
2068
|
+
throw new HttpTransportError(
|
|
2069
|
+
`Invalid signature length: ${sigBytes.length} (expected 64)`,
|
|
2070
|
+
"ENVELOPE_SIG_LENGTH",
|
|
2071
|
+
{ length: sigBytes.length }
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const { sig: _sig, ...unsigned } = envelope;
|
|
2075
|
+
const digest = (0, import_common8.canonicalHashBytes)(unsigned);
|
|
2076
|
+
const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
|
|
2077
|
+
if (!ok) {
|
|
2078
|
+
throw new HttpTransportError(
|
|
2079
|
+
"Envelope signature verification failed",
|
|
2080
|
+
"ENVELOPE_SIG_INVALID"
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function normalizeMessage(message) {
|
|
2085
|
+
const maybeToJSON = message.toJSON;
|
|
2086
|
+
if (typeof maybeToJSON === "function") {
|
|
2087
|
+
return maybeToJSON.call(message);
|
|
2088
|
+
}
|
|
2089
|
+
return message;
|
|
2090
|
+
}
|
|
2091
|
+
function normalizeForWire(value) {
|
|
2092
|
+
if (value instanceof Uint8Array) {
|
|
2093
|
+
return { __bytes: (0, import_utils4.bytesToHex)(value) };
|
|
2094
|
+
}
|
|
2095
|
+
if (Array.isArray(value)) {
|
|
2096
|
+
return value.map((v) => normalizeForWire(v));
|
|
2097
|
+
}
|
|
2098
|
+
if (value && typeof value === "object") {
|
|
2099
|
+
const out = {};
|
|
2100
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2101
|
+
out[k] = normalizeForWire(v);
|
|
2102
|
+
}
|
|
2103
|
+
return out;
|
|
2104
|
+
}
|
|
2105
|
+
return value;
|
|
2106
|
+
}
|
|
2107
|
+
function reviveFromWire(value) {
|
|
2108
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2109
|
+
const rec = value;
|
|
2110
|
+
const keys = Object.keys(rec);
|
|
2111
|
+
if (keys.length === 1 && keys[0] === "__bytes" && typeof rec.__bytes === "string") {
|
|
2112
|
+
return (0, import_utils4.hexToBytes)(rec.__bytes);
|
|
2113
|
+
}
|
|
2114
|
+
const out = {};
|
|
2115
|
+
for (const [k, v] of Object.entries(rec)) out[k] = reviveFromWire(v);
|
|
2116
|
+
return out;
|
|
2117
|
+
}
|
|
2118
|
+
if (Array.isArray(value)) {
|
|
2119
|
+
return value.map((v) => reviveFromWire(v));
|
|
2120
|
+
}
|
|
2121
|
+
return value;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/core/aggregation/transport/http/request-auth.ts
|
|
2125
|
+
var import_common9 = require("@did-btcr2/common");
|
|
2126
|
+
var import_utils5 = require("@noble/hashes/utils");
|
|
2127
|
+
var REQUEST_AUTH_SCHEME = "BTCR2-Sig";
|
|
2128
|
+
function buildRequestAuth(did, keys, path, opts = {}) {
|
|
2129
|
+
const ts = opts.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
2130
|
+
const nonce = opts.nonce ?? (0, import_utils5.bytesToHex)((0, import_utils5.randomBytes)(DEFAULT_NONCE_LEN_BYTES));
|
|
2131
|
+
const digest = (0, import_common9.canonicalHashBytes)({
|
|
2132
|
+
v: HTTP_ENVELOPE_VERSION,
|
|
2133
|
+
did,
|
|
2134
|
+
ts,
|
|
2135
|
+
nonce,
|
|
2136
|
+
path
|
|
2137
|
+
});
|
|
2138
|
+
const sig = keys.secretKey.sign(digest, { scheme: "schnorr" });
|
|
2139
|
+
return `${REQUEST_AUTH_SCHEME} v=${HTTP_ENVELOPE_VERSION},did=${did},ts=${ts},nonce=${nonce},sig=${(0, import_utils5.bytesToHex)(sig)}`;
|
|
2140
|
+
}
|
|
2141
|
+
function parseRequestAuth(headerValue) {
|
|
2142
|
+
const prefix = `${REQUEST_AUTH_SCHEME} `;
|
|
2143
|
+
if (!headerValue.startsWith(prefix)) {
|
|
2144
|
+
throw new HttpTransportError(
|
|
2145
|
+
`Unexpected auth scheme (want ${REQUEST_AUTH_SCHEME})`,
|
|
2146
|
+
"REQUEST_AUTH_SCHEME"
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
const params = {};
|
|
2150
|
+
for (const piece of headerValue.slice(prefix.length).split(",")) {
|
|
2151
|
+
const eq = piece.indexOf("=");
|
|
2152
|
+
if (eq === -1) continue;
|
|
2153
|
+
const key = piece.slice(0, eq).trim();
|
|
2154
|
+
const val = piece.slice(eq + 1).trim();
|
|
2155
|
+
if (key.length > 0) params[key] = val;
|
|
2156
|
+
}
|
|
2157
|
+
const v = Number(params.v);
|
|
2158
|
+
const ts = Number(params.ts);
|
|
2159
|
+
if (!Number.isInteger(v) || !Number.isInteger(ts) || !params.did || !params.nonce || !params.sig) {
|
|
2160
|
+
throw new HttpTransportError(
|
|
2161
|
+
"Malformed auth header (missing or invalid field)",
|
|
2162
|
+
"REQUEST_AUTH_MALFORMED",
|
|
2163
|
+
{ received: Object.keys(params) }
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
return { v, did: params.did, ts, nonce: params.nonce, sig: params.sig };
|
|
2167
|
+
}
|
|
2168
|
+
function verifyRequestAuth(headerValue, expectedPath, senderPk, opts = {}) {
|
|
2169
|
+
const parsed = parseRequestAuth(headerValue);
|
|
2170
|
+
if (parsed.v !== HTTP_ENVELOPE_VERSION) {
|
|
2171
|
+
throw new HttpTransportError(
|
|
2172
|
+
`Unsupported auth version: ${parsed.v}`,
|
|
2173
|
+
"REQUEST_AUTH_VERSION_MISMATCH",
|
|
2174
|
+
{ version: parsed.v, expected: HTTP_ENVELOPE_VERSION }
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
const skewSec = opts.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2178
|
+
const nowMs = opts.now ? opts.now() : Date.now();
|
|
2179
|
+
const nowSec = Math.floor(nowMs / 1e3);
|
|
2180
|
+
const diff = Math.abs(nowSec - parsed.ts);
|
|
2181
|
+
if (diff > skewSec) {
|
|
2182
|
+
throw new HttpTransportError(
|
|
2183
|
+
`Auth timestamp out of skew: ${diff}s > ${skewSec}s`,
|
|
2184
|
+
"REQUEST_AUTH_TIMESTAMP_SKEW",
|
|
2185
|
+
{ diff, skewSec }
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
let sigBytes;
|
|
2189
|
+
try {
|
|
2190
|
+
sigBytes = (0, import_utils5.hexToBytes)(parsed.sig);
|
|
2191
|
+
} catch {
|
|
2192
|
+
throw new HttpTransportError("Auth signature is not valid hex", "REQUEST_AUTH_SIG_HEX");
|
|
2193
|
+
}
|
|
2194
|
+
if (sigBytes.length !== 64) {
|
|
2195
|
+
throw new HttpTransportError(
|
|
2196
|
+
`Invalid auth signature length: ${sigBytes.length}`,
|
|
2197
|
+
"REQUEST_AUTH_SIG_LENGTH",
|
|
2198
|
+
{ length: sigBytes.length }
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
const digest = (0, import_common9.canonicalHashBytes)({
|
|
2202
|
+
v: parsed.v,
|
|
2203
|
+
did: parsed.did,
|
|
2204
|
+
ts: parsed.ts,
|
|
2205
|
+
nonce: parsed.nonce,
|
|
2206
|
+
path: expectedPath
|
|
2207
|
+
});
|
|
2208
|
+
const ok = senderPk.verify(sigBytes, digest, { scheme: "schnorr" });
|
|
2209
|
+
if (!ok) {
|
|
2210
|
+
throw new HttpTransportError("Auth signature verification failed", "REQUEST_AUTH_SIG_INVALID");
|
|
2211
|
+
}
|
|
2212
|
+
return parsed;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// src/core/aggregation/transport/http/sse-stream.ts
|
|
2216
|
+
async function* parseSseStream(readable) {
|
|
2217
|
+
const decoder = new TextDecoder("utf-8");
|
|
2218
|
+
const reader = readable.getReader();
|
|
2219
|
+
let buffer = "";
|
|
2220
|
+
let pending = {};
|
|
2221
|
+
const dispatchPending = () => {
|
|
2222
|
+
if (pending.data === void 0) {
|
|
2223
|
+
pending = {};
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2226
|
+
const ev = { data: pending.data };
|
|
2227
|
+
if (pending.event !== void 0) ev.event = pending.event;
|
|
2228
|
+
if (pending.id !== void 0) ev.id = pending.id;
|
|
2229
|
+
if (pending.retry !== void 0) ev.retry = pending.retry;
|
|
2230
|
+
pending = {};
|
|
2231
|
+
return ev;
|
|
2232
|
+
};
|
|
2233
|
+
const processLine = (line) => {
|
|
2234
|
+
if (line.startsWith(":")) return;
|
|
2235
|
+
const colon = line.indexOf(":");
|
|
2236
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
2237
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
2238
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
2239
|
+
switch (field) {
|
|
2240
|
+
case "data":
|
|
2241
|
+
pending.data = pending.data === void 0 ? value : `${pending.data}
|
|
2242
|
+
${value}`;
|
|
2243
|
+
break;
|
|
2244
|
+
case "event":
|
|
2245
|
+
pending.event = value;
|
|
2246
|
+
break;
|
|
2247
|
+
case "id":
|
|
2248
|
+
if (!value.includes("\0")) pending.id = value;
|
|
2249
|
+
break;
|
|
2250
|
+
case "retry": {
|
|
2251
|
+
const n = Number(value);
|
|
2252
|
+
if (Number.isInteger(n) && n >= 0) pending.retry = n;
|
|
2253
|
+
break;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
try {
|
|
2258
|
+
for (; ; ) {
|
|
2259
|
+
const { value, done } = await reader.read();
|
|
2260
|
+
if (done) {
|
|
2261
|
+
buffer += decoder.decode();
|
|
2262
|
+
if (buffer.length > 0) {
|
|
2263
|
+
const line = buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer;
|
|
2264
|
+
if (line.length > 0) processLine(line);
|
|
2265
|
+
buffer = "";
|
|
2266
|
+
}
|
|
2267
|
+
const tail = dispatchPending();
|
|
2268
|
+
if (tail) yield tail;
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2272
|
+
let lineEnd = buffer.indexOf("\n");
|
|
2273
|
+
while (lineEnd !== -1) {
|
|
2274
|
+
let line = buffer.slice(0, lineEnd);
|
|
2275
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
2276
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
2277
|
+
if (line.length === 0) {
|
|
2278
|
+
const ev = dispatchPending();
|
|
2279
|
+
if (ev) yield ev;
|
|
2280
|
+
} else {
|
|
2281
|
+
processLine(line);
|
|
2282
|
+
}
|
|
2283
|
+
lineEnd = buffer.indexOf("\n");
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
} finally {
|
|
2287
|
+
try {
|
|
2288
|
+
reader.releaseLock();
|
|
2289
|
+
} catch {
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// src/core/aggregation/transport/http/client.ts
|
|
2295
|
+
function defaultReconnectBackoff(attempt) {
|
|
2296
|
+
const base2 = Math.min(1e3 * 2 ** attempt, 3e4);
|
|
2297
|
+
const jitter = base2 * 0.2 * Math.random();
|
|
2298
|
+
return Math.floor(base2 + jitter);
|
|
2299
|
+
}
|
|
2300
|
+
var HttpClientTransport = class {
|
|
2301
|
+
name = "http";
|
|
2302
|
+
#baseUrl;
|
|
2303
|
+
#fetch;
|
|
2304
|
+
#logger;
|
|
2305
|
+
#backoff;
|
|
2306
|
+
#clockSkewSec;
|
|
2307
|
+
#actors = /* @__PURE__ */ new Map();
|
|
2308
|
+
#peers = /* @__PURE__ */ new Map();
|
|
2309
|
+
#started = false;
|
|
2310
|
+
#broadcastAbort;
|
|
2311
|
+
constructor(config) {
|
|
2312
|
+
const base2 = typeof config.baseUrl === "string" ? new URL(config.baseUrl) : new URL(config.baseUrl.href);
|
|
2313
|
+
if (!base2.pathname.endsWith("/")) base2.pathname += "/";
|
|
2314
|
+
this.#baseUrl = base2;
|
|
2315
|
+
this.#logger = config.logger ?? CONSOLE_LOGGER;
|
|
2316
|
+
this.#backoff = config.reconnectBackoff ?? defaultReconnectBackoff;
|
|
2317
|
+
this.#clockSkewSec = config.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2318
|
+
const fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
2319
|
+
if (typeof fetchImpl !== "function") {
|
|
2320
|
+
throw new HttpTransportError(
|
|
2321
|
+
"No fetch implementation available. Pass config.fetchImpl explicitly.",
|
|
2322
|
+
"NO_FETCH_IMPL"
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
this.#fetch = fetchImpl;
|
|
2326
|
+
}
|
|
2327
|
+
start() {
|
|
2328
|
+
if (this.#started) return;
|
|
2329
|
+
this.#started = true;
|
|
2330
|
+
this.#broadcastAbort = new AbortController();
|
|
2331
|
+
this.#runBroadcastLoop(this.#broadcastAbort.signal);
|
|
2332
|
+
for (const [did, entry] of this.#actors) {
|
|
2333
|
+
this.#openInbox(did, entry);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Tear down all SSE subscriptions and stop reconnect loops. Not part of the
|
|
2338
|
+
* {@link Transport} interface, but needed in tests and whenever a client
|
|
2339
|
+
* wants to cleanly disconnect without unregistering every actor.
|
|
2340
|
+
*
|
|
2341
|
+
* Idempotent. Actors remain registered (re-call {@link start} to resume).
|
|
2342
|
+
*/
|
|
2343
|
+
stop() {
|
|
2344
|
+
this.#broadcastAbort?.abort();
|
|
2345
|
+
this.#broadcastAbort = void 0;
|
|
2346
|
+
for (const entry of this.#actors.values()) {
|
|
2347
|
+
entry.inboxAbort?.abort();
|
|
2348
|
+
entry.inboxAbort = void 0;
|
|
2349
|
+
}
|
|
2350
|
+
this.#started = false;
|
|
2351
|
+
}
|
|
2352
|
+
registerActor(did, keys) {
|
|
2353
|
+
const existing = this.#actors.get(did);
|
|
2354
|
+
if (existing?.inboxAbort) existing.inboxAbort.abort();
|
|
2355
|
+
const entry = { keys, handlers: /* @__PURE__ */ new Map() };
|
|
2356
|
+
this.#actors.set(did, entry);
|
|
2357
|
+
if (this.#started) this.#openInbox(did, entry);
|
|
2358
|
+
}
|
|
2359
|
+
unregisterActor(did) {
|
|
2360
|
+
const entry = this.#actors.get(did);
|
|
2361
|
+
if (!entry) return;
|
|
2362
|
+
entry.inboxAbort?.abort();
|
|
2363
|
+
this.#actors.delete(did);
|
|
2364
|
+
this.#peers.delete(did);
|
|
2365
|
+
}
|
|
2366
|
+
getActorPk(did) {
|
|
2367
|
+
return this.#actors.get(did)?.keys.publicKey.compressed;
|
|
2368
|
+
}
|
|
2369
|
+
registerPeer(did, communicationPk) {
|
|
2370
|
+
try {
|
|
2371
|
+
new import_keypair2.CompressedSecp256k1PublicKey(communicationPk);
|
|
2372
|
+
} catch {
|
|
2373
|
+
throw new HttpTransportError(
|
|
2374
|
+
`Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
|
|
2375
|
+
"INVALID_PEER_KEY",
|
|
2376
|
+
{ did, keyLength: communicationPk.length }
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
this.#peers.set(did, communicationPk);
|
|
2380
|
+
}
|
|
2381
|
+
getPeerPk(did) {
|
|
2382
|
+
return this.#peers.get(did);
|
|
2383
|
+
}
|
|
2384
|
+
registerMessageHandler(actorDid, messageType, handler) {
|
|
2385
|
+
const actor = this.#actors.get(actorDid);
|
|
2386
|
+
if (!actor) {
|
|
2387
|
+
throw new HttpTransportError(
|
|
2388
|
+
`Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
|
|
2389
|
+
"UNKNOWN_ACTOR",
|
|
2390
|
+
{ did: actorDid }
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
actor.handlers.set(messageType, handler);
|
|
2394
|
+
}
|
|
2395
|
+
unregisterMessageHandler(actorDid, messageType) {
|
|
2396
|
+
this.#actors.get(actorDid)?.handlers.delete(messageType);
|
|
2397
|
+
}
|
|
2398
|
+
async sendMessage(message, sender, recipient) {
|
|
2399
|
+
const actor = this.#actors.get(sender);
|
|
2400
|
+
if (!actor) {
|
|
2401
|
+
throw new HttpTransportError(
|
|
2402
|
+
`Unknown sender: ${sender}. Call registerActor() before sending messages.`,
|
|
2403
|
+
"UNKNOWN_SENDER",
|
|
2404
|
+
{ did: sender }
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
const envelope = signEnvelope(
|
|
2408
|
+
message,
|
|
2409
|
+
{ did: sender, keys: actor.keys },
|
|
2410
|
+
{ to: recipient }
|
|
2411
|
+
);
|
|
2412
|
+
const url = this.#route(HTTP_ROUTE.MESSAGES);
|
|
2413
|
+
const res = await this.#fetch(url, {
|
|
2414
|
+
method: "POST",
|
|
2415
|
+
headers: { "content-type": "application/json" },
|
|
2416
|
+
body: JSON.stringify(envelope)
|
|
2417
|
+
});
|
|
2418
|
+
if (!res.ok) {
|
|
2419
|
+
const body = await safeText(res);
|
|
2420
|
+
throw new HttpTransportError(
|
|
2421
|
+
`sendMessage failed: HTTP ${res.status}`,
|
|
2422
|
+
"SEND_MESSAGE_HTTP",
|
|
2423
|
+
{ status: res.status, body: body.slice(0, 256), messageType: message.type }
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
publishRepeating(message, sender, intervalMs, recipient) {
|
|
2428
|
+
let stopped = false;
|
|
2429
|
+
const attempt = () => {
|
|
2430
|
+
if (stopped) return;
|
|
2431
|
+
this.sendMessage(message, sender, recipient).catch((err) => {
|
|
2432
|
+
this.#logger.debug("publishRepeating send failed:", err);
|
|
2433
|
+
});
|
|
2434
|
+
};
|
|
2435
|
+
attempt();
|
|
2436
|
+
const timer = setInterval(attempt, intervalMs);
|
|
2437
|
+
return () => {
|
|
2438
|
+
if (stopped) return;
|
|
2439
|
+
stopped = true;
|
|
2440
|
+
clearInterval(timer);
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
#route(template) {
|
|
2444
|
+
return new URL(template.replace(/^\//, ""), this.#baseUrl);
|
|
2445
|
+
}
|
|
2446
|
+
#openInbox(did, entry) {
|
|
2447
|
+
const abort = new AbortController();
|
|
2448
|
+
entry.inboxAbort = abort;
|
|
2449
|
+
this.#runInboxLoop(did, entry, abort.signal);
|
|
2450
|
+
}
|
|
2451
|
+
async #runBroadcastLoop(signal) {
|
|
2452
|
+
const url = this.#route(HTTP_ROUTE.ADVERTS);
|
|
2453
|
+
let attempt = 0;
|
|
2454
|
+
while (!signal.aborted) {
|
|
2455
|
+
try {
|
|
2456
|
+
const res = await this.#fetch(url, {
|
|
2457
|
+
method: "GET",
|
|
2458
|
+
headers: { accept: "text/event-stream" },
|
|
2459
|
+
signal
|
|
2460
|
+
});
|
|
2461
|
+
if (!res.ok || !res.body) {
|
|
2462
|
+
this.#logger.warn(`Broadcast subscribe failed: HTTP ${res.status}`);
|
|
2463
|
+
await sleep(this.#backoff(attempt++), signal);
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
2466
|
+
attempt = 0;
|
|
2467
|
+
for await (const ev of parseSseStream(res.body)) {
|
|
2468
|
+
if (signal.aborted) return;
|
|
2469
|
+
if (ev.event !== SSE_EVENT.ADVERT) continue;
|
|
2470
|
+
this.#dispatchBroadcast(ev.data);
|
|
2471
|
+
}
|
|
2472
|
+
} catch (err) {
|
|
2473
|
+
if (signal.aborted) return;
|
|
2474
|
+
this.#logger.debug("Broadcast loop error:", err);
|
|
2475
|
+
try {
|
|
2476
|
+
await sleep(this.#backoff(attempt++), signal);
|
|
2477
|
+
} catch {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
async #runInboxLoop(did, entry, signal) {
|
|
2484
|
+
const url = this.#route(HTTP_ROUTE.ACTOR_INBOX.replace("{did}", encodeURIComponent(did)));
|
|
2485
|
+
let attempt = 0;
|
|
2486
|
+
while (!signal.aborted) {
|
|
2487
|
+
try {
|
|
2488
|
+
const auth = buildRequestAuth(did, entry.keys, url.pathname);
|
|
2489
|
+
const res = await this.#fetch(url, {
|
|
2490
|
+
method: "GET",
|
|
2491
|
+
headers: { accept: "text/event-stream", authorization: auth },
|
|
2492
|
+
signal
|
|
2493
|
+
});
|
|
2494
|
+
if (!res.ok || !res.body) {
|
|
2495
|
+
this.#logger.warn(`Inbox subscribe failed for ${did}: HTTP ${res.status}`);
|
|
2496
|
+
await sleep(this.#backoff(attempt++), signal);
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
attempt = 0;
|
|
2500
|
+
for await (const ev of parseSseStream(res.body)) {
|
|
2501
|
+
if (signal.aborted) return;
|
|
2502
|
+
if (ev.event !== SSE_EVENT.MESSAGE) continue;
|
|
2503
|
+
await this.#dispatchInbox(ev.data, did, entry);
|
|
2504
|
+
}
|
|
2505
|
+
} catch (err) {
|
|
2506
|
+
if (signal.aborted) return;
|
|
2507
|
+
this.#logger.debug(`Inbox loop error for ${did}:`, err);
|
|
2508
|
+
try {
|
|
2509
|
+
await sleep(this.#backoff(attempt++), signal);
|
|
2510
|
+
} catch {
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
#dispatchBroadcast(dataJson) {
|
|
2517
|
+
const envelope = parseEnvelope(dataJson, this.#logger);
|
|
2518
|
+
if (!envelope) return;
|
|
2519
|
+
const senderPk = this.#resolveSenderPk(envelope.from);
|
|
2520
|
+
if (!senderPk) {
|
|
2521
|
+
this.#logger.debug(`Broadcast from unresolvable DID: ${envelope.from}`);
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
try {
|
|
2525
|
+
verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
|
|
2526
|
+
} catch (err) {
|
|
2527
|
+
this.#logger.debug("Broadcast envelope verification failed:", err);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
const revived = reviveFromWire(envelope.message);
|
|
2531
|
+
const flat = flattenMessage(revived);
|
|
2532
|
+
const messageType = typeof flat.type === "string" ? flat.type : void 0;
|
|
2533
|
+
if (!messageType) return;
|
|
2534
|
+
for (const actor of this.#actors.values()) {
|
|
2535
|
+
const handler = actor.handlers.get(messageType);
|
|
2536
|
+
if (handler) void Promise.resolve(handler(flat));
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
async #dispatchInbox(dataJson, actorDid, entry) {
|
|
2540
|
+
const envelope = parseEnvelope(dataJson, this.#logger);
|
|
2541
|
+
if (!envelope) return;
|
|
2542
|
+
const senderPk = this.#resolveSenderPk(envelope.from);
|
|
2543
|
+
if (!senderPk) {
|
|
2544
|
+
this.#logger.debug(`Inbox message from unresolvable DID: ${envelope.from}`);
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
try {
|
|
2548
|
+
verifyEnvelope(envelope, senderPk, {
|
|
2549
|
+
clockSkewSec: this.#clockSkewSec,
|
|
2550
|
+
expectedTo: actorDid
|
|
2551
|
+
});
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
this.#logger.debug(`Inbox envelope verification failed for ${actorDid}:`, err);
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
const revived = reviveFromWire(envelope.message);
|
|
2557
|
+
const flat = flattenMessage(revived);
|
|
2558
|
+
const messageType = typeof flat.type === "string" ? flat.type : void 0;
|
|
2559
|
+
if (!messageType) return;
|
|
2560
|
+
const handler = entry.handlers.get(messageType);
|
|
2561
|
+
if (handler) await handler(flat);
|
|
2562
|
+
}
|
|
2563
|
+
#resolveSenderPk(did) {
|
|
2564
|
+
const peerBytes = this.#peers.get(did);
|
|
2565
|
+
if (peerBytes) {
|
|
2566
|
+
try {
|
|
2567
|
+
return new import_keypair2.CompressedSecp256k1PublicKey(peerBytes);
|
|
2568
|
+
} catch {
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
try {
|
|
2572
|
+
const components = Identifier.decode(did);
|
|
2573
|
+
if (components.idType === "KEY") {
|
|
2574
|
+
return new import_keypair2.CompressedSecp256k1PublicKey(components.genesisBytes);
|
|
2575
|
+
}
|
|
2576
|
+
} catch {
|
|
2577
|
+
}
|
|
2578
|
+
return void 0;
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
function sleep(ms, signal) {
|
|
2582
|
+
if (ms <= 0) return Promise.resolve();
|
|
2583
|
+
return new Promise((resolve, reject) => {
|
|
2584
|
+
const onAbort = () => {
|
|
2585
|
+
clearTimeout(timer);
|
|
2586
|
+
reject(new Error("aborted"));
|
|
2587
|
+
};
|
|
2588
|
+
const timer = setTimeout(() => {
|
|
2589
|
+
signal.removeEventListener("abort", onAbort);
|
|
2590
|
+
resolve();
|
|
2591
|
+
}, ms);
|
|
2592
|
+
if (signal.aborted) onAbort();
|
|
2593
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
async function safeText(res) {
|
|
2597
|
+
try {
|
|
2598
|
+
return await res.text();
|
|
2599
|
+
} catch {
|
|
2600
|
+
return "";
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function parseEnvelope(dataJson, logger) {
|
|
2604
|
+
try {
|
|
2605
|
+
return JSON.parse(dataJson);
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
logger.debug("SSE event: failed to parse envelope JSON:", err);
|
|
2608
|
+
return void 0;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
function flattenMessage(msg) {
|
|
2612
|
+
if (msg.body && typeof msg.body === "object") {
|
|
2613
|
+
return { ...msg, ...msg.body };
|
|
2614
|
+
}
|
|
2615
|
+
return msg;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/core/aggregation/transport/http/server.ts
|
|
2619
|
+
var import_keypair3 = require("@did-btcr2/keypair");
|
|
2620
|
+
|
|
2621
|
+
// src/core/aggregation/transport/http/inbox-buffer.ts
|
|
2622
|
+
var InboxBuffer = class {
|
|
2623
|
+
#capacity;
|
|
2624
|
+
#entries = [];
|
|
2625
|
+
#nextId = 1;
|
|
2626
|
+
constructor(capacity = 100) {
|
|
2627
|
+
if (capacity < 1) throw new Error(`InboxBuffer capacity must be >= 1; got ${capacity}`);
|
|
2628
|
+
this.#capacity = capacity;
|
|
2629
|
+
}
|
|
2630
|
+
/** Append an event. Returns the stored record (including its assigned id). */
|
|
2631
|
+
append(event, data) {
|
|
2632
|
+
const stored = { id: String(this.#nextId++), event, data };
|
|
2633
|
+
this.#entries.push(stored);
|
|
2634
|
+
if (this.#entries.length > this.#capacity) this.#entries.shift();
|
|
2635
|
+
return stored;
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* Return stored events with id strictly greater than `lastEventId`. If
|
|
2639
|
+
* `lastEventId` is unset or unparseable, returns everything currently
|
|
2640
|
+
* retained.
|
|
2641
|
+
*/
|
|
2642
|
+
since(lastEventId) {
|
|
2643
|
+
if (!lastEventId) return this.#entries.slice();
|
|
2644
|
+
const boundary = Number(lastEventId);
|
|
2645
|
+
if (!Number.isFinite(boundary)) return this.#entries.slice();
|
|
2646
|
+
return this.#entries.filter((e) => Number(e.id) > boundary);
|
|
2647
|
+
}
|
|
2648
|
+
/** Currently retained event count. */
|
|
2649
|
+
size() {
|
|
2650
|
+
return this.#entries.length;
|
|
2651
|
+
}
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
// src/core/aggregation/transport/http/nonce-cache.ts
|
|
2655
|
+
var NonceCache = class {
|
|
2656
|
+
#maxEntries;
|
|
2657
|
+
#entries = /* @__PURE__ */ new Map();
|
|
2658
|
+
constructor(config = {}) {
|
|
2659
|
+
this.#maxEntries = config.maxEntries ?? 1e4;
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Record a nonce. Returns `true` if it was novel (caller should accept the
|
|
2663
|
+
* request) or `false` if it was a replay (caller should reject).
|
|
2664
|
+
*/
|
|
2665
|
+
store(did, nonce, timestampSec) {
|
|
2666
|
+
const key = `${did}:${nonce}`;
|
|
2667
|
+
if (this.#entries.has(key)) return false;
|
|
2668
|
+
this.#entries.set(key, timestampSec);
|
|
2669
|
+
if (this.#entries.size > this.#maxEntries) {
|
|
2670
|
+
const oldest = this.#entries.keys().next();
|
|
2671
|
+
if (!oldest.done) this.#entries.delete(oldest.value);
|
|
2672
|
+
}
|
|
2673
|
+
return true;
|
|
2674
|
+
}
|
|
2675
|
+
/** Current cache size. Exposed for observability and tests. */
|
|
2676
|
+
size() {
|
|
2677
|
+
return this.#entries.size;
|
|
2678
|
+
}
|
|
2679
|
+
};
|
|
2680
|
+
|
|
2681
|
+
// src/core/aggregation/transport/http/rate-limiter.ts
|
|
2682
|
+
var InMemoryRateLimitStore = class {
|
|
2683
|
+
#buckets = /* @__PURE__ */ new Map();
|
|
2684
|
+
get(key) {
|
|
2685
|
+
return this.#buckets.get(key);
|
|
2686
|
+
}
|
|
2687
|
+
set(key, state) {
|
|
2688
|
+
this.#buckets.set(key, state);
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
var RateLimiter = class {
|
|
2692
|
+
#rps;
|
|
2693
|
+
#burst;
|
|
2694
|
+
#store;
|
|
2695
|
+
constructor(config = {}) {
|
|
2696
|
+
this.#rps = config.rps ?? 10;
|
|
2697
|
+
this.#burst = config.burst ?? 30;
|
|
2698
|
+
this.#store = config.store ?? new InMemoryRateLimitStore();
|
|
2699
|
+
}
|
|
2700
|
+
/** Consume one token for `key`. Returns `true` if accepted, `false` if throttled. */
|
|
2701
|
+
consume(key, nowMs) {
|
|
2702
|
+
const existing = this.#store.get(key);
|
|
2703
|
+
const state = existing ?? { tokens: this.#burst, lastRefillMs: nowMs };
|
|
2704
|
+
if (existing) {
|
|
2705
|
+
const elapsedSec = Math.max(0, (nowMs - existing.lastRefillMs) / 1e3);
|
|
2706
|
+
state.tokens = Math.min(this.#burst, existing.tokens + elapsedSec * this.#rps);
|
|
2707
|
+
state.lastRefillMs = nowMs;
|
|
2708
|
+
}
|
|
2709
|
+
if (state.tokens < 1) {
|
|
2710
|
+
this.#store.set(key, state);
|
|
2711
|
+
return false;
|
|
2712
|
+
}
|
|
2713
|
+
state.tokens -= 1;
|
|
2714
|
+
this.#store.set(key, state);
|
|
2715
|
+
return true;
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
|
|
2719
|
+
// src/core/aggregation/transport/http/server.ts
|
|
2720
|
+
var INBOX_PATH_PREFIX = "/v1/actors/";
|
|
2721
|
+
var INBOX_PATH_SUFFIX = "/inbox";
|
|
2722
|
+
var DEFAULT_ADVERT_TTL_MS = 5 * 60 * 1e3;
|
|
2723
|
+
var DEFAULT_HEARTBEAT_MS = 2e4;
|
|
2724
|
+
var HttpServerTransport = class {
|
|
2725
|
+
name = "http";
|
|
2726
|
+
#logger;
|
|
2727
|
+
#cors;
|
|
2728
|
+
#clockSkewSec;
|
|
2729
|
+
#inboxBufferSize;
|
|
2730
|
+
#advertTtlMs;
|
|
2731
|
+
#heartbeatMs;
|
|
2732
|
+
#rateLimiter;
|
|
2733
|
+
#nonceCache;
|
|
2734
|
+
#now;
|
|
2735
|
+
#actors = /* @__PURE__ */ new Map();
|
|
2736
|
+
#peers = /* @__PURE__ */ new Map();
|
|
2737
|
+
#inboxes = /* @__PURE__ */ new Map();
|
|
2738
|
+
#broadcastSubscribers = /* @__PURE__ */ new Set();
|
|
2739
|
+
#currentAdvert;
|
|
2740
|
+
#advertSeq = 0;
|
|
2741
|
+
constructor(config = {}) {
|
|
2742
|
+
this.#logger = config.logger ?? CONSOLE_LOGGER;
|
|
2743
|
+
this.#cors = config.cors ?? { mode: "permissive" };
|
|
2744
|
+
this.#clockSkewSec = config.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
|
|
2745
|
+
this.#inboxBufferSize = config.inboxBufferSize ?? 100;
|
|
2746
|
+
this.#advertTtlMs = config.advertTtlMs ?? DEFAULT_ADVERT_TTL_MS;
|
|
2747
|
+
this.#heartbeatMs = config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
2748
|
+
this.#rateLimiter = config.rateLimiter ?? new RateLimiter();
|
|
2749
|
+
this.#nonceCache = config.nonceCache ?? new NonceCache();
|
|
2750
|
+
this.#now = config.now ?? (() => Date.now());
|
|
2751
|
+
}
|
|
2752
|
+
// ----------------------------------------------------------------
|
|
2753
|
+
// Transport interface
|
|
2754
|
+
// ----------------------------------------------------------------
|
|
2755
|
+
start() {
|
|
2756
|
+
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Detach the transport: close every open SSE subscription, clear the advert
|
|
2759
|
+
* cache, and drop all actor / peer / inbox state. Intended for shutdown and
|
|
2760
|
+
* for test teardown.
|
|
2761
|
+
*/
|
|
2762
|
+
stop() {
|
|
2763
|
+
for (const sub of this.#broadcastSubscribers) this.#closeBroadcastSubscriber(sub);
|
|
2764
|
+
this.#broadcastSubscribers.clear();
|
|
2765
|
+
for (const inbox of this.#inboxes.values()) {
|
|
2766
|
+
for (const sub of inbox.subscribers) this.#closeInboxSubscriber(sub);
|
|
2767
|
+
inbox.subscribers.clear();
|
|
2768
|
+
}
|
|
2769
|
+
this.#inboxes.clear();
|
|
2770
|
+
this.#currentAdvert = void 0;
|
|
2771
|
+
}
|
|
2772
|
+
registerActor(did, keys) {
|
|
2773
|
+
this.#actors.set(did, { keys, handlers: /* @__PURE__ */ new Map() });
|
|
2774
|
+
}
|
|
2775
|
+
unregisterActor(did) {
|
|
2776
|
+
this.#actors.delete(did);
|
|
2777
|
+
this.#peers.delete(did);
|
|
2778
|
+
}
|
|
2779
|
+
getActorPk(did) {
|
|
2780
|
+
return this.#actors.get(did)?.keys.publicKey.compressed;
|
|
2781
|
+
}
|
|
2782
|
+
registerPeer(did, communicationPk) {
|
|
2783
|
+
try {
|
|
2784
|
+
new import_keypair3.CompressedSecp256k1PublicKey(communicationPk);
|
|
2785
|
+
} catch {
|
|
2786
|
+
throw new HttpTransportError(
|
|
2787
|
+
`Invalid peer public key for ${did}`,
|
|
2788
|
+
"INVALID_PEER_KEY",
|
|
2789
|
+
{ did, keyLength: communicationPk.length }
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
this.#peers.set(did, communicationPk);
|
|
2793
|
+
}
|
|
2794
|
+
getPeerPk(did) {
|
|
2795
|
+
return this.#peers.get(did);
|
|
2796
|
+
}
|
|
2797
|
+
registerMessageHandler(actorDid, messageType, handler) {
|
|
2798
|
+
const actor = this.#actors.get(actorDid);
|
|
2799
|
+
if (!actor) {
|
|
2800
|
+
throw new HttpTransportError(
|
|
2801
|
+
`Cannot register handler: actor ${actorDid} not registered`,
|
|
2802
|
+
"UNKNOWN_ACTOR",
|
|
2803
|
+
{ did: actorDid }
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
actor.handlers.set(messageType, handler);
|
|
2807
|
+
}
|
|
2808
|
+
unregisterMessageHandler(actorDid, messageType) {
|
|
2809
|
+
this.#actors.get(actorDid)?.handlers.delete(messageType);
|
|
2810
|
+
}
|
|
2811
|
+
async sendMessage(message, sender, recipient) {
|
|
2812
|
+
if (!recipient) {
|
|
2813
|
+
throw new HttpTransportError(
|
|
2814
|
+
"HttpServerTransport.sendMessage requires a recipient. Use publishRepeating for broadcasts.",
|
|
2815
|
+
"MISSING_RECIPIENT",
|
|
2816
|
+
{ messageType: message.type }
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
const actor = this.#actors.get(sender);
|
|
2820
|
+
if (!actor) {
|
|
2821
|
+
throw new HttpTransportError(
|
|
2822
|
+
`Unknown sender: ${sender}`,
|
|
2823
|
+
"UNKNOWN_SENDER",
|
|
2824
|
+
{ did: sender }
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
const envelope = signEnvelope(message, { did: sender, keys: actor.keys }, { to: recipient });
|
|
2828
|
+
const dataJson = JSON.stringify(envelope);
|
|
2829
|
+
const inbox = this.#getOrCreateInbox(recipient);
|
|
2830
|
+
const stored = inbox.buffer.append(SSE_EVENT.MESSAGE, dataJson);
|
|
2831
|
+
for (const sub of inbox.subscribers) {
|
|
2832
|
+
this.#safeWrite(sub.stream, stored.event, stored.data, stored.id);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
publishRepeating(message, sender, _intervalMs, _recipient) {
|
|
2836
|
+
const actor = this.#actors.get(sender);
|
|
2837
|
+
if (!actor) {
|
|
2838
|
+
throw new HttpTransportError(`Unknown sender: ${sender}`, "UNKNOWN_SENDER", { did: sender });
|
|
2839
|
+
}
|
|
2840
|
+
const envelope = signEnvelope(message, { did: sender, keys: actor.keys });
|
|
2841
|
+
const dataJson = JSON.stringify(envelope);
|
|
2842
|
+
const id = String(++this.#advertSeq);
|
|
2843
|
+
const expiresAtMs = this.#now() + this.#advertTtlMs;
|
|
2844
|
+
this.#currentAdvert = { dataJson, id, expiresAtMs };
|
|
2845
|
+
for (const sub of this.#broadcastSubscribers) {
|
|
2846
|
+
this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, dataJson, id);
|
|
2847
|
+
}
|
|
2848
|
+
return () => {
|
|
2849
|
+
if (this.#currentAdvert?.id === id) this.#currentAdvert = void 0;
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
// ----------------------------------------------------------------
|
|
2853
|
+
// Sans-I/O HTTP surface
|
|
2854
|
+
// ----------------------------------------------------------------
|
|
2855
|
+
/**
|
|
2856
|
+
* Handle a POST / GET request (non-SSE). The caller dispatches SSE paths to
|
|
2857
|
+
* {@link handleSse} instead. Returns a fully formed response; the caller's
|
|
2858
|
+
* adapter turns it into an HTTP write.
|
|
2859
|
+
*/
|
|
2860
|
+
async handleRequest(req) {
|
|
2861
|
+
const method = req.method.toUpperCase();
|
|
2862
|
+
if (method === "OPTIONS") return this.#respond(204, "", req);
|
|
2863
|
+
const path = extractPath(req.url);
|
|
2864
|
+
if (method === "GET" && path === HTTP_ROUTE.WELL_KNOWN) {
|
|
2865
|
+
return this.#respondJson(200, this.#wellKnownMetadata(), req);
|
|
2866
|
+
}
|
|
2867
|
+
if (method === "POST" && path === HTTP_ROUTE.MESSAGES) {
|
|
2868
|
+
return await this.#handleMessagesPost(req);
|
|
2869
|
+
}
|
|
2870
|
+
if (method === "POST" && path === HTTP_ROUTE.ADVERTS) {
|
|
2871
|
+
return await this.#handleAdvertsPost(req);
|
|
2872
|
+
}
|
|
2873
|
+
return this.#respondJson(404, { error: "not_found" }, req);
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Open an SSE stream for a GET request. The caller is responsible for
|
|
2877
|
+
* flushing writes and propagating the `onClose` callback when the HTTP
|
|
2878
|
+
* connection ends.
|
|
2879
|
+
*/
|
|
2880
|
+
handleSse(req, stream) {
|
|
2881
|
+
if (req.method.toUpperCase() !== "GET") {
|
|
2882
|
+
stream.close();
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const path = extractPath(req.url);
|
|
2886
|
+
if (path === HTTP_ROUTE.ADVERTS) {
|
|
2887
|
+
this.#openBroadcastSubscription(stream);
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
const inboxMatch = matchInboxPath(path);
|
|
2891
|
+
if (inboxMatch) {
|
|
2892
|
+
this.#openInboxSubscription(req, stream, inboxMatch.did, path);
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
stream.close();
|
|
2896
|
+
}
|
|
2897
|
+
// ----------------------------------------------------------------
|
|
2898
|
+
// Request handlers
|
|
2899
|
+
// ----------------------------------------------------------------
|
|
2900
|
+
async #handleMessagesPost(req) {
|
|
2901
|
+
const envelope = parseJsonBody(req.body);
|
|
2902
|
+
if (!envelope) return this.#respondJson(400, { error: "invalid_json" }, req);
|
|
2903
|
+
const senderPk = this.#resolveSenderPk(envelope.from);
|
|
2904
|
+
if (!senderPk) {
|
|
2905
|
+
return this.#respondJson(401, { error: "unknown_sender" }, req);
|
|
2906
|
+
}
|
|
2907
|
+
try {
|
|
2908
|
+
verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
this.#logger.debug("POST /v1/messages: envelope verification failed:", err);
|
|
2911
|
+
return this.#respondJson(401, { error: "invalid_envelope" }, req);
|
|
2912
|
+
}
|
|
2913
|
+
if (!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
|
|
2914
|
+
return this.#respondJson(409, { error: "replay" }, req);
|
|
2915
|
+
}
|
|
2916
|
+
if (!this.#rateLimiter.consume(envelope.from, this.#now())) {
|
|
2917
|
+
return this.#respondJson(429, { error: "rate_limited" }, req);
|
|
2918
|
+
}
|
|
2919
|
+
if (!envelope.to) {
|
|
2920
|
+
return this.#respondJson(400, { error: "missing_recipient" }, req);
|
|
2921
|
+
}
|
|
2922
|
+
const actor = this.#actors.get(envelope.to);
|
|
2923
|
+
if (!actor) {
|
|
2924
|
+
return this.#respondJson(404, { error: "unknown_recipient" }, req);
|
|
2925
|
+
}
|
|
2926
|
+
const revived = reviveFromWire(envelope.message);
|
|
2927
|
+
const flat = flattenMessage2(revived);
|
|
2928
|
+
const messageType = typeof flat.type === "string" ? flat.type : void 0;
|
|
2929
|
+
if (!messageType) return this.#respondJson(400, { error: "missing_message_type" }, req);
|
|
2930
|
+
const handler = actor.handlers.get(messageType);
|
|
2931
|
+
if (handler) {
|
|
2932
|
+
try {
|
|
2933
|
+
await handler(flat);
|
|
2934
|
+
} catch (err) {
|
|
2935
|
+
this.#logger.debug(`Handler threw for ${messageType}:`, err);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
return this.#respondJson(202, { ok: true }, req);
|
|
2939
|
+
}
|
|
2940
|
+
async #handleAdvertsPost(req) {
|
|
2941
|
+
const envelope = parseJsonBody(req.body);
|
|
2942
|
+
if (!envelope) return this.#respondJson(400, { error: "invalid_json" }, req);
|
|
2943
|
+
const senderPk = this.#resolveSenderPk(envelope.from);
|
|
2944
|
+
if (!senderPk) return this.#respondJson(401, { error: "unknown_sender" }, req);
|
|
2945
|
+
try {
|
|
2946
|
+
verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
|
|
2947
|
+
} catch {
|
|
2948
|
+
return this.#respondJson(401, { error: "invalid_envelope" }, req);
|
|
2949
|
+
}
|
|
2950
|
+
if (!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
|
|
2951
|
+
return this.#respondJson(409, { error: "replay" }, req);
|
|
2952
|
+
}
|
|
2953
|
+
if (!this.#rateLimiter.consume(envelope.from, this.#now())) {
|
|
2954
|
+
return this.#respondJson(429, { error: "rate_limited" }, req);
|
|
2955
|
+
}
|
|
2956
|
+
if (!this.#actors.has(envelope.from)) {
|
|
2957
|
+
return this.#respondJson(403, { error: "not_an_actor" }, req);
|
|
2958
|
+
}
|
|
2959
|
+
const id = String(++this.#advertSeq);
|
|
2960
|
+
this.#currentAdvert = {
|
|
2961
|
+
dataJson: JSON.stringify(envelope),
|
|
2962
|
+
id,
|
|
2963
|
+
expiresAtMs: this.#now() + this.#advertTtlMs
|
|
2964
|
+
};
|
|
2965
|
+
for (const sub of this.#broadcastSubscribers) {
|
|
2966
|
+
this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, id);
|
|
2967
|
+
}
|
|
2968
|
+
return this.#respondJson(202, { ok: true }, req);
|
|
2969
|
+
}
|
|
2970
|
+
#openBroadcastSubscription(stream) {
|
|
2971
|
+
const sub = { stream };
|
|
2972
|
+
this.#broadcastSubscribers.add(sub);
|
|
2973
|
+
stream.onClose(() => {
|
|
2974
|
+
this.#closeBroadcastSubscriber(sub);
|
|
2975
|
+
this.#broadcastSubscribers.delete(sub);
|
|
2976
|
+
});
|
|
2977
|
+
if (this.#currentAdvert && this.#currentAdvert.expiresAtMs > this.#now()) {
|
|
2978
|
+
this.#safeWrite(stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, this.#currentAdvert.id);
|
|
2979
|
+
}
|
|
2980
|
+
if (this.#heartbeatMs > 0) {
|
|
2981
|
+
sub.heartbeatTimer = setInterval(() => {
|
|
2982
|
+
try {
|
|
2983
|
+
stream.writeComment("hb");
|
|
2984
|
+
} catch {
|
|
2985
|
+
}
|
|
2986
|
+
}, this.#heartbeatMs);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
#openInboxSubscription(req, stream, did, path) {
|
|
2990
|
+
const auth = req.headers.authorization;
|
|
2991
|
+
if (!auth) {
|
|
2992
|
+
this.#logger.debug(`Inbox subscribe: missing authorization header for ${did}`);
|
|
2993
|
+
stream.close();
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
const senderPk = this.#resolveSenderPk(did);
|
|
2997
|
+
if (!senderPk) {
|
|
2998
|
+
stream.close();
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
let parsedTs = 0;
|
|
3002
|
+
let parsedNonce = "";
|
|
3003
|
+
try {
|
|
3004
|
+
const parsed = verifyRequestAuth(auth, path, senderPk, {
|
|
3005
|
+
clockSkewSec: this.#clockSkewSec,
|
|
3006
|
+
now: () => this.#now()
|
|
3007
|
+
});
|
|
3008
|
+
if (parsed.did !== did) {
|
|
3009
|
+
stream.close();
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
parsedTs = parsed.ts;
|
|
3013
|
+
parsedNonce = parsed.nonce;
|
|
3014
|
+
} catch (err) {
|
|
3015
|
+
this.#logger.debug(`Inbox subscribe: auth verification failed for ${did}:`, err);
|
|
3016
|
+
stream.close();
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
if (!this.#nonceCache.store(did, parsedNonce, parsedTs)) {
|
|
3020
|
+
stream.close();
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
if (!this.#rateLimiter.consume(did, this.#now())) {
|
|
3024
|
+
stream.close();
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const inbox = this.#getOrCreateInbox(did);
|
|
3028
|
+
const sub = { stream };
|
|
3029
|
+
inbox.subscribers.add(sub);
|
|
3030
|
+
stream.onClose(() => {
|
|
3031
|
+
this.#closeInboxSubscriber(sub);
|
|
3032
|
+
inbox.subscribers.delete(sub);
|
|
3033
|
+
});
|
|
3034
|
+
const lastEventId = req.headers["last-event-id"];
|
|
3035
|
+
for (const stored of inbox.buffer.since(lastEventId)) {
|
|
3036
|
+
this.#safeWrite(stream, stored.event, stored.data, stored.id);
|
|
3037
|
+
}
|
|
3038
|
+
if (this.#heartbeatMs > 0) {
|
|
3039
|
+
sub.heartbeatTimer = setInterval(() => {
|
|
3040
|
+
try {
|
|
3041
|
+
stream.writeComment("hb");
|
|
3042
|
+
} catch {
|
|
3043
|
+
}
|
|
3044
|
+
}, this.#heartbeatMs);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
// ----------------------------------------------------------------
|
|
3048
|
+
// Internal helpers
|
|
3049
|
+
// ----------------------------------------------------------------
|
|
3050
|
+
#getOrCreateInbox(did) {
|
|
3051
|
+
let inbox = this.#inboxes.get(did);
|
|
3052
|
+
if (!inbox) {
|
|
3053
|
+
inbox = { buffer: new InboxBuffer(this.#inboxBufferSize), subscribers: /* @__PURE__ */ new Set() };
|
|
3054
|
+
this.#inboxes.set(did, inbox);
|
|
3055
|
+
}
|
|
3056
|
+
return inbox;
|
|
3057
|
+
}
|
|
3058
|
+
#resolveSenderPk(did) {
|
|
3059
|
+
const peerBytes = this.#peers.get(did);
|
|
3060
|
+
if (peerBytes) {
|
|
3061
|
+
try {
|
|
3062
|
+
return new import_keypair3.CompressedSecp256k1PublicKey(peerBytes);
|
|
3063
|
+
} catch {
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
try {
|
|
3067
|
+
const components = Identifier.decode(did);
|
|
3068
|
+
if (components.idType === "KEY") {
|
|
3069
|
+
return new import_keypair3.CompressedSecp256k1PublicKey(components.genesisBytes);
|
|
3070
|
+
}
|
|
3071
|
+
} catch {
|
|
3072
|
+
}
|
|
3073
|
+
return void 0;
|
|
3074
|
+
}
|
|
3075
|
+
#safeWrite(stream, event, data, id) {
|
|
3076
|
+
try {
|
|
3077
|
+
stream.writeEvent(event, data, id);
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
this.#logger.debug("SSE writeEvent failed:", err);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
#closeBroadcastSubscriber(sub) {
|
|
3083
|
+
if (sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
|
|
3084
|
+
try {
|
|
3085
|
+
sub.stream.close();
|
|
3086
|
+
} catch {
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
#closeInboxSubscriber(sub) {
|
|
3090
|
+
if (sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
|
|
3091
|
+
try {
|
|
3092
|
+
sub.stream.close();
|
|
3093
|
+
} catch {
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
#respondJson(status, body, req) {
|
|
3097
|
+
return {
|
|
3098
|
+
status,
|
|
3099
|
+
headers: { "content-type": "application/json", ...this.#corsHeaders(req) },
|
|
3100
|
+
body: JSON.stringify(body)
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
#respond(status, body, req) {
|
|
3104
|
+
return { status, headers: this.#corsHeaders(req), body };
|
|
3105
|
+
}
|
|
3106
|
+
#corsHeaders(req) {
|
|
3107
|
+
const origin = req.headers.origin;
|
|
3108
|
+
if (!origin) return {};
|
|
3109
|
+
const common = {
|
|
3110
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
3111
|
+
"access-control-allow-headers": "authorization, content-type, last-event-id",
|
|
3112
|
+
"access-control-max-age": "86400"
|
|
3113
|
+
};
|
|
3114
|
+
switch (this.#cors.mode) {
|
|
3115
|
+
case "permissive":
|
|
3116
|
+
return { "access-control-allow-origin": "*", ...common };
|
|
3117
|
+
case "allowlist":
|
|
3118
|
+
if (this.#cors.origins.includes(origin)) {
|
|
3119
|
+
return { "access-control-allow-origin": origin, vary: "origin", ...common };
|
|
3120
|
+
}
|
|
3121
|
+
return {};
|
|
3122
|
+
case "same-origin":
|
|
3123
|
+
return {};
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
#wellKnownMetadata() {
|
|
3127
|
+
return {
|
|
3128
|
+
envelopeVersion: HTTP_ENVELOPE_VERSION,
|
|
3129
|
+
heartbeatIntervalMs: this.#heartbeatMs,
|
|
3130
|
+
inboxBufferSize: this.#inboxBufferSize,
|
|
3131
|
+
advertTtlMs: this.#advertTtlMs
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
};
|
|
3135
|
+
function extractPath(reqUrl) {
|
|
3136
|
+
if (reqUrl.startsWith("http://") || reqUrl.startsWith("https://")) {
|
|
3137
|
+
return new URL(reqUrl).pathname;
|
|
3138
|
+
}
|
|
3139
|
+
const q = reqUrl.indexOf("?");
|
|
3140
|
+
return q === -1 ? reqUrl : reqUrl.slice(0, q);
|
|
3141
|
+
}
|
|
3142
|
+
function matchInboxPath(path) {
|
|
3143
|
+
if (!path.startsWith(INBOX_PATH_PREFIX) || !path.endsWith(INBOX_PATH_SUFFIX)) return void 0;
|
|
3144
|
+
const encodedDid = path.slice(INBOX_PATH_PREFIX.length, path.length - INBOX_PATH_SUFFIX.length);
|
|
3145
|
+
if (!encodedDid) return void 0;
|
|
3146
|
+
try {
|
|
3147
|
+
return { did: decodeURIComponent(encodedDid) };
|
|
3148
|
+
} catch {
|
|
3149
|
+
return void 0;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function parseJsonBody(body) {
|
|
3153
|
+
if (body === void 0 || body === "") return void 0;
|
|
3154
|
+
try {
|
|
3155
|
+
return JSON.parse(body);
|
|
3156
|
+
} catch {
|
|
3157
|
+
return void 0;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function flattenMessage2(msg) {
|
|
3161
|
+
if (msg.body && typeof msg.body === "object") {
|
|
3162
|
+
return { ...msg, ...msg.body };
|
|
3163
|
+
}
|
|
3164
|
+
return msg;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
// src/core/aggregation/transport/nostr.ts
|
|
3168
|
+
var import_keypair4 = require("@did-btcr2/keypair");
|
|
3169
|
+
var import_utils6 = require("@noble/hashes/utils");
|
|
3170
|
+
var import_nostr_tools = require("nostr-tools");
|
|
3171
|
+
var import_pool = require("nostr-tools/pool");
|
|
3172
|
+
var DEFAULT_NOSTR_RELAYS = [
|
|
3173
|
+
"wss://relay.damus.io",
|
|
3174
|
+
"wss://nos.lol",
|
|
3175
|
+
"wss://relay.snort.social",
|
|
3176
|
+
"wss://nostr-pub.wellorder.net"
|
|
3177
|
+
];
|
|
3178
|
+
var DEFAULT_BROADCAST_LOOKBACK_MS = 5 * 60 * 1e3;
|
|
3179
|
+
var NostrTransport = class _NostrTransport {
|
|
3180
|
+
name = "nostr";
|
|
3181
|
+
pool;
|
|
3182
|
+
#relays;
|
|
3183
|
+
#actors = /* @__PURE__ */ new Map();
|
|
3184
|
+
#peerRegistry = /* @__PURE__ */ new Map();
|
|
3185
|
+
#started = false;
|
|
3186
|
+
#logger;
|
|
3187
|
+
#broadcastLookbackMs;
|
|
3188
|
+
constructor(config) {
|
|
3189
|
+
this.#relays = config?.relays ?? DEFAULT_NOSTR_RELAYS;
|
|
3190
|
+
this.#logger = config?.logger ?? CONSOLE_LOGGER;
|
|
3191
|
+
this.#broadcastLookbackMs = config?.broadcastLookbackMs ?? DEFAULT_BROADCAST_LOOKBACK_MS;
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Registers an actor (DID + keys) to send/receive messages with.
|
|
3195
|
+
* Must be called before start() to ensure subscriptions are created for the actor.
|
|
3196
|
+
* @param {string} did - The DID of the actor.
|
|
3197
|
+
* @param {SchnorrKeyPair} keys - The Schnorr key pair for the actor.
|
|
3198
|
+
* @throws {TransportAdapterError} If the actor is already registered or if the transport has already started.
|
|
3199
|
+
* @example
|
|
3200
|
+
* const transport = new NostrTransport();
|
|
3201
|
+
* const keys = SchnorrKeyPair.generate();
|
|
3202
|
+
* const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
|
|
3203
|
+
* transport.registerActor(did, keys);
|
|
3204
|
+
* transport.start();
|
|
3205
|
+
*/
|
|
3206
|
+
registerActor(did, keys) {
|
|
3207
|
+
const entry = { keys, handlers: /* @__PURE__ */ new Map(), subscriptions: [] };
|
|
3208
|
+
this.#actors.set(did, entry);
|
|
3209
|
+
if (this.#started && this.pool) {
|
|
3210
|
+
this.#subscribeDirected(did, entry);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Detach an actor: drop its handlers, close its relay subscriptions, and remove its keys + peer
|
|
3215
|
+
* mapping. Idempotent.
|
|
3216
|
+
* @param {string} did - The DID of the actor to unregister.
|
|
3217
|
+
* @returns {void}
|
|
3218
|
+
* @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
|
|
3219
|
+
* @example
|
|
3220
|
+
* transport.unregisterActor(did);
|
|
3221
|
+
*/
|
|
3222
|
+
unregisterActor(did) {
|
|
3223
|
+
const entry = this.#actors.get(did);
|
|
3224
|
+
if (!entry) return;
|
|
3225
|
+
for (const sub of entry.subscriptions) {
|
|
3226
|
+
try {
|
|
3227
|
+
sub.close();
|
|
3228
|
+
} catch (err) {
|
|
3229
|
+
this.#logger.debug(`Error closing subscription for ${did}:`, err);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
entry.subscriptions.length = 0;
|
|
3233
|
+
entry.handlers.clear();
|
|
3234
|
+
this.#actors.delete(did);
|
|
3235
|
+
this.#peerRegistry.delete(did);
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* Remove a single (actor, messageType) handler. Idempotent.
|
|
3239
|
+
* @param {string} actorDid - The DID of the actor.
|
|
3240
|
+
* @param {string} messageType - The type of message to unregister the handler for.
|
|
3241
|
+
* @returns {void}
|
|
3242
|
+
* @example
|
|
3243
|
+
* transport.unregisterMessageHandler(actorDid, messageType);
|
|
3244
|
+
*/
|
|
3245
|
+
unregisterMessageHandler(actorDid, messageType) {
|
|
3246
|
+
const actor = this.#actors.get(actorDid);
|
|
3247
|
+
if (!actor) return;
|
|
3248
|
+
actor.handlers.delete(messageType);
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Gets the public key for a registered actor by their DID.
|
|
3252
|
+
* @param {string} did - The DID of the registered actor to get the public key for.
|
|
3253
|
+
* @returns {Uint8Array | undefined} The compressed public key bytes for the actor's DID, or
|
|
3254
|
+
* undefined if the DID is not registered.
|
|
3255
|
+
*/
|
|
3256
|
+
getActorPk(did) {
|
|
3257
|
+
return this.#actors.get(did)?.keys.publicKey.compressed;
|
|
3258
|
+
}
|
|
3259
|
+
/**
|
|
3260
|
+
* Registers a peer's communication public key for encrypted messages.
|
|
3261
|
+
* @param {string} did - The DID of the peer to register.
|
|
3262
|
+
* @param {Uint8Array} communicationPk - The compressed secp256k1 public key bytes for the peer.
|
|
3263
|
+
*/
|
|
3264
|
+
registerPeer(did, communicationPk) {
|
|
3265
|
+
try {
|
|
3266
|
+
new import_keypair4.CompressedSecp256k1PublicKey(communicationPk);
|
|
3267
|
+
} catch {
|
|
3268
|
+
throw new TransportAdapterError(
|
|
3269
|
+
`Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
|
|
3270
|
+
"INVALID_PEER_KEY",
|
|
3271
|
+
{ adapter: this.name, did, keyLength: communicationPk.length }
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
this.#peerRegistry.set(did, communicationPk);
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Gets the registered communication public key for a peer by their DID.
|
|
3278
|
+
* @param {string} did - The DID of the peer to get the communication public key for.
|
|
3279
|
+
* @returns {Uint8Array | undefined} The compressed secp256k1 public key bytes for the peer, or
|
|
3280
|
+
* undefined if the peer is not registered.
|
|
3281
|
+
*/
|
|
3282
|
+
getPeerPk(did) {
|
|
3283
|
+
return this.#peerRegistry.get(did);
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Registers a message handler function for a specific actor and message type. The handler will be called
|
|
3287
|
+
* when a message of the specified type is received for the actor's DID. The transport must have been
|
|
3288
|
+
* started for handlers to be invoked. If the transport is already started, the handler will be registered
|
|
3289
|
+
* immediately; otherwise, it will be registered when the transport starts and the actor's subscription is created.
|
|
3290
|
+
* @param {string} actorDid - The DID of the actor to register the message handler for.
|
|
3291
|
+
* @param {string} messageType - The type of message to handle.
|
|
3292
|
+
* @param {MessageHandler} handler - The function to handle incoming messages of the specified type.
|
|
3293
|
+
* @throws {TransportAdapterError} If the actor DID is not registered or if the handler is invalid.
|
|
3294
|
+
*/
|
|
3295
|
+
registerMessageHandler(actorDid, messageType, handler) {
|
|
3296
|
+
const actor = this.#actors.get(actorDid);
|
|
3297
|
+
if (!actor) {
|
|
3298
|
+
throw new TransportAdapterError(
|
|
3299
|
+
`Cannot register handler: actor ${actorDid} not registered. Call registerActor() first.`,
|
|
3300
|
+
"UNKNOWN_ACTOR_ERROR",
|
|
3301
|
+
{ adapter: this.name, did: actorDid }
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
actor.handlers.set(messageType, handler);
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Starts the transport by connecting to the configured Nostr relays and setting up subscriptions
|
|
3308
|
+
* for all registered actors. This method must be called after registering actors via registerActor()
|
|
3309
|
+
* and before sending or receiving messages. The transport will subscribe to broadcast events (kind 1)
|
|
3310
|
+
* for cohort adverts and directed events (kinds 1 and 1059) for each registered actor based on their
|
|
3311
|
+
* public keys. Incoming events are processed and dispatched to the appropriate handlers based on
|
|
3312
|
+
* message type. If the transport is already started, this method has no effect.
|
|
3313
|
+
* @returns {NostrTransport}
|
|
3314
|
+
*/
|
|
3315
|
+
start() {
|
|
3316
|
+
if (this.#started) return this;
|
|
3317
|
+
this.#started = true;
|
|
3318
|
+
this.pool = new import_pool.SimplePool();
|
|
3319
|
+
const broadcastFilter = {
|
|
3320
|
+
kinds: [1],
|
|
3321
|
+
"#t": [COHORT_ADVERT]
|
|
3322
|
+
};
|
|
3323
|
+
if (this.#broadcastLookbackMs > 0) {
|
|
3324
|
+
broadcastFilter.since = Math.floor((Date.now() - this.#broadcastLookbackMs) / 1e3);
|
|
3325
|
+
}
|
|
3326
|
+
this.pool.subscribeMany(this.#relays, broadcastFilter, {
|
|
3327
|
+
onclose: (reasons) => this.#logger.debug("Nostr broadcast subscription closed", reasons),
|
|
3328
|
+
onevent: this.#handleBroadcastEvent.bind(this)
|
|
3329
|
+
});
|
|
3330
|
+
for (const [did, entry] of this.#actors) {
|
|
3331
|
+
this.#subscribeDirected(did, entry);
|
|
3332
|
+
}
|
|
3333
|
+
this.#logger.info(`NostrTransport started, listening on ${this.#relays.length} relay(s)`);
|
|
3334
|
+
return this;
|
|
3335
|
+
}
|
|
3336
|
+
/**
|
|
3337
|
+
* Sends a message by publishing a Nostr event to the configured relays. The message is serialized
|
|
3338
|
+
* as JSON and included in the event content.
|
|
3339
|
+
* @param {BaseMessage} message - The aggregation message to send. Must include a valid `type` property.
|
|
3340
|
+
* @param {Did} sender - The DID of the registered actor sending the message. Must have been
|
|
3341
|
+
* registered via registerActor().
|
|
3342
|
+
* @param {Did} [to] - Optional recipient DID for directed messages. Required for encrypted message
|
|
3343
|
+
* types. If provided, must have been registered via registerPeer().
|
|
3344
|
+
* @returns {Promise<void>} Resolves when the message has been published to the relays. Note that
|
|
3345
|
+
* publication is best-effort: the method will resolve as long as at least one relay accepts the
|
|
3346
|
+
* event, even if others reject it.
|
|
3347
|
+
*/
|
|
3348
|
+
async sendMessage(message, sender, to) {
|
|
3349
|
+
const type = message.type;
|
|
3350
|
+
if (!type) {
|
|
3351
|
+
throw new TransportAdapterError(
|
|
3352
|
+
"Message type is undefined",
|
|
3353
|
+
"SEND_MESSAGE_ERROR",
|
|
3354
|
+
{ adapter: this.name, type }
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
const actor = this.#actors.get(sender);
|
|
3358
|
+
if (!actor) {
|
|
3359
|
+
throw new TransportAdapterError(
|
|
3360
|
+
`Unknown sender: ${sender}. Call registerActor() before sending messages.`,
|
|
1539
3361
|
"UNKNOWN_ACTOR_ERROR",
|
|
1540
3362
|
{ adapter: this.name, did: sender }
|
|
1541
3363
|
);
|
|
1542
3364
|
}
|
|
1543
3365
|
const senderKeys = actor.keys;
|
|
1544
3366
|
const tags = [
|
|
1545
|
-
["p", (0,
|
|
3367
|
+
["p", (0, import_utils6.bytesToHex)(senderKeys.publicKey.x)],
|
|
1546
3368
|
["t", type]
|
|
1547
3369
|
];
|
|
1548
3370
|
if (to) {
|
|
1549
3371
|
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
1550
3372
|
if (recipientPkBytes) {
|
|
1551
|
-
const recipientPk = new
|
|
1552
|
-
tags.push(["p", (0,
|
|
3373
|
+
const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3374
|
+
tags.push(["p", (0, import_utils6.bytesToHex)(recipientPk.x)]);
|
|
1553
3375
|
}
|
|
1554
3376
|
}
|
|
1555
3377
|
if (isKeygenMessageType(type)) {
|
|
@@ -1559,7 +3381,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
1559
3381
|
tags,
|
|
1560
3382
|
content: JSON.stringify(message, _NostrTransport.#jsonReplacer)
|
|
1561
3383
|
}, senderKeys.secretKey.bytes);
|
|
1562
|
-
|
|
3384
|
+
this.#logger.debug(`Publishing kind 1 [${type}]`);
|
|
1563
3385
|
await this.#publishToRelays(event);
|
|
1564
3386
|
return;
|
|
1565
3387
|
}
|
|
@@ -1579,10 +3401,10 @@ var NostrTransport = class _NostrTransport {
|
|
|
1579
3401
|
{ adapter: this.name, did: to }
|
|
1580
3402
|
);
|
|
1581
3403
|
}
|
|
1582
|
-
const recipientPk = new
|
|
3404
|
+
const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
1583
3405
|
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
1584
3406
|
senderKeys.secretKey.bytes,
|
|
1585
|
-
(0,
|
|
3407
|
+
(0, import_utils6.bytesToHex)(recipientPk.x)
|
|
1586
3408
|
);
|
|
1587
3409
|
const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
|
|
1588
3410
|
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
@@ -1591,25 +3413,70 @@ var NostrTransport = class _NostrTransport {
|
|
|
1591
3413
|
tags,
|
|
1592
3414
|
content
|
|
1593
3415
|
}, senderKeys.secretKey.bytes);
|
|
1594
|
-
|
|
3416
|
+
this.#logger.debug(`Publishing kind 1059 [${type}]`);
|
|
1595
3417
|
await this.#publishToRelays(event);
|
|
1596
3418
|
return;
|
|
1597
3419
|
}
|
|
1598
|
-
|
|
3420
|
+
this.#logger.warn(`Unsupported message type: ${type}`);
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Publish the message once immediately and then re-publish on an interval
|
|
3424
|
+
* until the returned stop function is invoked.
|
|
3425
|
+
*
|
|
3426
|
+
* Useful for broadcast messages (COHORT_ADVERT) on relays that don't
|
|
3427
|
+
* backfill historical events to late subscribers: republishing gives late
|
|
3428
|
+
* joiners a window to discover the message without requiring protocol
|
|
3429
|
+
* changes. Relay rate-limit / publish failures inside the interval are
|
|
3430
|
+
* caught and logged rather than propagated — the caller should stop the
|
|
3431
|
+
* repeater once the protocol condition is satisfied.
|
|
3432
|
+
*/
|
|
3433
|
+
publishRepeating(message, sender, intervalMs, recipient) {
|
|
3434
|
+
let stopped = false;
|
|
3435
|
+
void this.sendMessage(message, sender, recipient).catch((err) => {
|
|
3436
|
+
this.#logger.debug("publishRepeating first send failed:", err);
|
|
3437
|
+
});
|
|
3438
|
+
const timer = setInterval(() => {
|
|
3439
|
+
if (stopped) return;
|
|
3440
|
+
void this.sendMessage(message, sender, recipient).catch((err) => {
|
|
3441
|
+
this.#logger.debug("publishRepeating retry failed:", err);
|
|
3442
|
+
});
|
|
3443
|
+
}, intervalMs);
|
|
3444
|
+
return () => {
|
|
3445
|
+
if (stopped) return;
|
|
3446
|
+
stopped = true;
|
|
3447
|
+
clearInterval(timer);
|
|
3448
|
+
};
|
|
1599
3449
|
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Creates a directed subscription for the given actor, filtering for messages that match the
|
|
3452
|
+
* actor's public key. Messages received on this subscription are dispatched to the actor's
|
|
3453
|
+
* registered handlers based on message type.
|
|
3454
|
+
* @param {string} did - The DID of the actor to create the subscription for.
|
|
3455
|
+
* @param {ActorEntry} entry - The actor's registration entry containing keys and handlers.
|
|
3456
|
+
* @returns {void}
|
|
3457
|
+
* @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
|
|
3458
|
+
*/
|
|
1600
3459
|
#subscribeDirected(did, entry) {
|
|
1601
3460
|
if (!this.pool) return;
|
|
1602
|
-
const pkHex = (0,
|
|
1603
|
-
const
|
|
1604
|
-
|
|
1605
|
-
onclose: (reasons) => console.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
3461
|
+
const pkHex = (0, import_utils6.bytesToHex)(entry.keys.publicKey.x);
|
|
3462
|
+
const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
|
|
3463
|
+
onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
1606
3464
|
onevent: this.#makeActorEventHandler(did)
|
|
1607
3465
|
});
|
|
3466
|
+
entry.subscriptions.push(sub);
|
|
1608
3467
|
}
|
|
3468
|
+
/**
|
|
3469
|
+
* Creates an event handler function for a specific actor that processes incoming events, decrypts
|
|
3470
|
+
* if necessary, and dispatches messages to the actor's registered handlers based on message type.
|
|
3471
|
+
* @param {string} actorDid - The DID of the actor to create the event handler for.
|
|
3472
|
+
* @returns {(event: Event) => Promise<void>} An asynchronous event handler function that
|
|
3473
|
+
* processes incoming events for the specified actor.
|
|
3474
|
+
*/
|
|
1609
3475
|
#makeActorEventHandler(actorDid) {
|
|
1610
3476
|
return async (event) => {
|
|
1611
3477
|
const actor = this.#actors.get(actorDid);
|
|
1612
3478
|
if (!actor) return;
|
|
3479
|
+
if (event.pubkey === (0, import_utils6.bytesToHex)(actor.keys.publicKey.x)) return;
|
|
1613
3480
|
let message;
|
|
1614
3481
|
try {
|
|
1615
3482
|
if (event.kind === 1) {
|
|
@@ -1625,19 +3492,29 @@ var NostrTransport = class _NostrTransport {
|
|
|
1625
3492
|
return;
|
|
1626
3493
|
}
|
|
1627
3494
|
} catch (err) {
|
|
1628
|
-
|
|
3495
|
+
this.#logger.debug(`Failed to parse event ${event.id} for ${actorDid}:`, err);
|
|
1629
3496
|
return;
|
|
1630
3497
|
}
|
|
1631
3498
|
this.#dispatchMessage(message, actor);
|
|
1632
3499
|
};
|
|
1633
3500
|
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Handles incoming broadcast events (kind 1) by parsing the event content, validating it as an
|
|
3503
|
+
* aggregation message, and dispatching it to all registered actors that have handlers for the
|
|
3504
|
+
* message type. This is used for COHORT_ADVERT messages that need to be received by all actors
|
|
3505
|
+
* regardless of DID.
|
|
3506
|
+
* @param {Event} event - The Nostr event to handle, expected to be a kind 1 broadcast containing
|
|
3507
|
+
* a COHORT_ADVERT message. The event content is parsed and dispatched to all registered actors
|
|
3508
|
+
* that have handlers for the
|
|
3509
|
+
* @returns
|
|
3510
|
+
*/
|
|
1634
3511
|
async #handleBroadcastEvent(event) {
|
|
1635
3512
|
if (event.kind !== 1) return;
|
|
1636
3513
|
let message;
|
|
1637
3514
|
try {
|
|
1638
3515
|
message = JSON.parse(event.content, _NostrTransport.#jsonReviver);
|
|
1639
3516
|
} catch (err) {
|
|
1640
|
-
|
|
3517
|
+
this.#logger.debug(`Failed to parse broadcast event ${event.id}:`, err);
|
|
1641
3518
|
return;
|
|
1642
3519
|
}
|
|
1643
3520
|
if (message.body && typeof message.body === "object") {
|
|
@@ -1650,6 +3527,18 @@ var NostrTransport = class _NostrTransport {
|
|
|
1650
3527
|
if (handler) await handler(message);
|
|
1651
3528
|
}
|
|
1652
3529
|
}
|
|
3530
|
+
/**
|
|
3531
|
+
* Dispatches a parsed message to the appropriate handler of a registered actor based on message type.
|
|
3532
|
+
* The message is expected to have already been parsed from the Nostr event content and validated as
|
|
3533
|
+
* an aggregation message. If the message has a body, its properties are merged into the top-level
|
|
3534
|
+
* message object for easier handler access. The message is then dispatched to the handler registered
|
|
3535
|
+
* for its type, if one exists.
|
|
3536
|
+
* @param {Record<string, unknown>} message - The message object parsed from a Nostr event, expected to
|
|
3537
|
+
* @param {ActorEntry} actor - The registered actor entry containing keys and handlers to dispatch the message to.
|
|
3538
|
+
* @returns {void}
|
|
3539
|
+
* @throws {TransportAdapterError} If the message type is unsupported or if no handler is registered
|
|
3540
|
+
* for the message type.
|
|
3541
|
+
*/
|
|
1653
3542
|
#dispatchMessage(message, actor) {
|
|
1654
3543
|
if (message.body && typeof message.body === "object") {
|
|
1655
3544
|
message = { ...message, ...message.body };
|
|
@@ -1659,6 +3548,16 @@ var NostrTransport = class _NostrTransport {
|
|
|
1659
3548
|
const handler = actor.handlers.get(messageType);
|
|
1660
3549
|
if (handler) handler(message);
|
|
1661
3550
|
}
|
|
3551
|
+
/**
|
|
3552
|
+
* Publishes a Nostr event to the configured relays and handles the results. The method waits for all
|
|
3553
|
+
* relay promises to settle and checks how many accepted or rejected the event. If all relays reject the event,
|
|
3554
|
+
* an error is thrown. Otherwise, the method completes successfully even if some relays rejected the event,
|
|
3555
|
+
* as long as at least one relay accepted it. Relay rejections are logged for debugging purposes.
|
|
3556
|
+
* @param {Event} event - The Nostr event to publish to the configured relays. The event should already
|
|
3557
|
+
* @returns {Promise<void>} A promise that resolves if the event was accepted by at least one relay, or rejects
|
|
3558
|
+
* with a TransportAdapterError if all relays rejected the event.
|
|
3559
|
+
* @throws {TransportAdapterError} If the pool is not initialized or if all relays reject the event.
|
|
3560
|
+
*/
|
|
1662
3561
|
async #publishToRelays(event) {
|
|
1663
3562
|
const relayPromises = this.pool?.publish(this.#relays, event);
|
|
1664
3563
|
if (!relayPromises?.length) return;
|
|
@@ -1666,7 +3565,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
1666
3565
|
const accepted = results.filter((r) => r.status === "fulfilled").length;
|
|
1667
3566
|
const rejected = results.filter((r) => r.status === "rejected");
|
|
1668
3567
|
for (const r of rejected) {
|
|
1669
|
-
|
|
3568
|
+
this.#logger.debug(`Relay rejected event ${event.id}: ${r.reason}`);
|
|
1670
3569
|
}
|
|
1671
3570
|
if (accepted === 0) {
|
|
1672
3571
|
throw new TransportAdapterError(
|
|
@@ -1676,15 +3575,39 @@ var NostrTransport = class _NostrTransport {
|
|
|
1676
3575
|
);
|
|
1677
3576
|
}
|
|
1678
3577
|
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Custom JSON replacer to handle serialization of Uint8Array values as hex strings in message
|
|
3580
|
+
* content. This allows messages containing binary data (e.g. public keys, signatures) to be correctly
|
|
3581
|
+
* serialized to JSON for Nostr event content. The replacer checks if a value is a Uint8Array and, if so,
|
|
3582
|
+
* converts it to a hex string wrapped in an object with a __bytes property. The corresponding reviver
|
|
3583
|
+
* can then convert this back to a Uint8Array when parsing the message content from the event.
|
|
3584
|
+
* @param {string} _key - The key of the property being processed.
|
|
3585
|
+
* @param {unknown} value - The value to check if the message type is valid.
|
|
3586
|
+
* @returns {unknown} The transformed value for JSON serialization. If the value is a Uint8Array,
|
|
3587
|
+
* it returns an object with a __bytes property containing the hex string. Otherwise, it returns
|
|
3588
|
+
* the value unchanged.
|
|
3589
|
+
*/
|
|
1679
3590
|
static #jsonReplacer(_key, value) {
|
|
1680
3591
|
if (value instanceof Uint8Array) {
|
|
1681
|
-
return { __bytes: (0,
|
|
3592
|
+
return { __bytes: (0, import_utils6.bytesToHex)(value) };
|
|
1682
3593
|
}
|
|
1683
3594
|
return value;
|
|
1684
3595
|
}
|
|
3596
|
+
/**
|
|
3597
|
+
* Custom JSON reviver to handle deserialization of hex strings back into Uint8Array values in message
|
|
3598
|
+
* content. This complements the custom replacer used during serialization, allowing messages that contain
|
|
3599
|
+
* binary data (e.g. public keys, signatures) to be correctly reconstructed when parsing JSON from
|
|
3600
|
+
* Nostr event content. The reviver checks if a value is an object with a __bytes property and,
|
|
3601
|
+
* if so, converts the hex string back into a Uint8Array. Otherwise, it returns the value unchanged.
|
|
3602
|
+
* @param {string} _key - The key of the property being processed.
|
|
3603
|
+
* @param {unknown} value - The value to check if it is an object containing a __bytes property for
|
|
3604
|
+
* hex string conversion.
|
|
3605
|
+
* @returns {unknown} The transformed value for JSON deserialization. If the value is an object
|
|
3606
|
+
* with a __bytes property, it returns a Uint8Array. Otherwise, it returns the value unchanged.
|
|
3607
|
+
*/
|
|
1685
3608
|
static #jsonReviver(_key, value) {
|
|
1686
3609
|
if (value && typeof value === "object" && "__bytes" in value) {
|
|
1687
|
-
return (0,
|
|
3610
|
+
return (0, import_utils6.hexToBytes)(value.__bytes);
|
|
1688
3611
|
}
|
|
1689
3612
|
return value;
|
|
1690
3613
|
}
|
|
@@ -1695,9 +3618,19 @@ var TransportFactory = class {
|
|
|
1695
3618
|
static establish(config) {
|
|
1696
3619
|
switch (config.type) {
|
|
1697
3620
|
case "nostr":
|
|
1698
|
-
return new NostrTransport({
|
|
3621
|
+
return new NostrTransport({
|
|
3622
|
+
relays: config.relays,
|
|
3623
|
+
logger: config.logger,
|
|
3624
|
+
broadcastLookbackMs: config.broadcastLookbackMs
|
|
3625
|
+
});
|
|
1699
3626
|
case "didcomm":
|
|
1700
|
-
throw new
|
|
3627
|
+
throw new import_common10.NotImplementedError("DIDComm transport not implemented yet.");
|
|
3628
|
+
case "http":
|
|
3629
|
+
if (config.role === "client") return new HttpClientTransport(config);
|
|
3630
|
+
if (config.role === "server") return new HttpServerTransport(config);
|
|
3631
|
+
throw new import_common10.NotImplementedError(
|
|
3632
|
+
`HTTP transport role not implemented: ${config.role}`
|
|
3633
|
+
);
|
|
1701
3634
|
default:
|
|
1702
3635
|
throw new TransportError(
|
|
1703
3636
|
`Invalid transport type: ${config.type}`,
|
|
@@ -1709,32 +3642,58 @@ var TransportFactory = class {
|
|
|
1709
3642
|
};
|
|
1710
3643
|
|
|
1711
3644
|
// src/core/aggregation/transport/didcomm.ts
|
|
1712
|
-
var
|
|
3645
|
+
var import_common11 = require("@did-btcr2/common");
|
|
1713
3646
|
var DidCommTransport = class {
|
|
1714
3647
|
name = "didcomm";
|
|
1715
3648
|
start() {
|
|
1716
|
-
throw new
|
|
3649
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
|
|
1717
3650
|
}
|
|
1718
3651
|
registerActor(_did, _keys) {
|
|
1719
|
-
throw new
|
|
3652
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1720
3653
|
}
|
|
1721
3654
|
getActorPk(_did) {
|
|
1722
|
-
throw new
|
|
3655
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1723
3656
|
}
|
|
1724
3657
|
registerPeer(_did, _communicationPk) {
|
|
1725
|
-
throw new
|
|
3658
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1726
3659
|
}
|
|
1727
3660
|
getPeerPk(_did) {
|
|
1728
|
-
throw new
|
|
3661
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1729
3662
|
}
|
|
1730
3663
|
registerMessageHandler(_actorDid, _messageType, _handler) {
|
|
1731
|
-
throw new
|
|
3664
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
3665
|
+
}
|
|
3666
|
+
unregisterMessageHandler(_actorDid, _messageType) {
|
|
3667
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
3668
|
+
}
|
|
3669
|
+
unregisterActor(_did) {
|
|
3670
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1732
3671
|
}
|
|
1733
3672
|
async sendMessage(_message, _sender, _recipient) {
|
|
1734
|
-
throw new
|
|
3673
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
3674
|
+
}
|
|
3675
|
+
publishRepeating(_message, _sender, _intervalMs, _recipient) {
|
|
3676
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
1735
3677
|
}
|
|
1736
3678
|
};
|
|
1737
3679
|
|
|
3680
|
+
// src/core/aggregation/transport/http/sse-writer.ts
|
|
3681
|
+
function formatSseEvent(event, data, id) {
|
|
3682
|
+
const lines = [];
|
|
3683
|
+
if (id !== void 0) lines.push(`id: ${id}`);
|
|
3684
|
+
lines.push(`event: ${event}`);
|
|
3685
|
+
for (const part of data.split("\n")) lines.push(`data: ${part}`);
|
|
3686
|
+
lines.push("");
|
|
3687
|
+
lines.push("");
|
|
3688
|
+
return lines.join("\n");
|
|
3689
|
+
}
|
|
3690
|
+
function formatSseComment(comment) {
|
|
3691
|
+
const safe = comment.replace(/\n/g, " ");
|
|
3692
|
+
return `: ${safe}
|
|
3693
|
+
|
|
3694
|
+
`;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
1738
3697
|
// src/core/aggregation/runner/typed-emitter.ts
|
|
1739
3698
|
var TypedEventEmitter = class {
|
|
1740
3699
|
#listeners = /* @__PURE__ */ new Map();
|
|
@@ -1790,7 +3749,8 @@ var TypedEventEmitter = class {
|
|
|
1790
3749
|
};
|
|
1791
3750
|
|
|
1792
3751
|
// src/core/aggregation/runner/service-runner.ts
|
|
1793
|
-
var
|
|
3752
|
+
var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
|
|
3753
|
+
var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
|
|
1794
3754
|
/** Direct access to the underlying state machine for advanced use. */
|
|
1795
3755
|
session;
|
|
1796
3756
|
#transport;
|
|
@@ -1799,11 +3759,26 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1799
3759
|
#onOptInReceived;
|
|
1800
3760
|
#onReadyToFinalize;
|
|
1801
3761
|
#onProvideTxData;
|
|
3762
|
+
#cohortTtlMs;
|
|
3763
|
+
#phaseTimeoutMs;
|
|
3764
|
+
#advertRepeatIntervalMs;
|
|
1802
3765
|
#cohortId;
|
|
1803
3766
|
#handlersRegistered = false;
|
|
1804
3767
|
#stopped = false;
|
|
3768
|
+
/**
|
|
3769
|
+
* Guard against the async race where two concurrent #handleOptIn invocations
|
|
3770
|
+
* both pass the `participants.length >= minParticipants` check before either
|
|
3771
|
+
* mutates the cohort phase. Set synchronously before any `await` so subsequent
|
|
3772
|
+
* handlers observe it on their next resumption.
|
|
3773
|
+
*/
|
|
3774
|
+
#finalizing = false;
|
|
1805
3775
|
#resolveRun;
|
|
1806
3776
|
#rejectRun;
|
|
3777
|
+
#cohortTtlTimer;
|
|
3778
|
+
#phaseTimer;
|
|
3779
|
+
#lastObservedPhase;
|
|
3780
|
+
/** Stop handle for the repeating COHORT_ADVERT publish loop. */
|
|
3781
|
+
#stopAdvertRepeat;
|
|
1807
3782
|
constructor(options) {
|
|
1808
3783
|
super();
|
|
1809
3784
|
this.#transport = options.transport;
|
|
@@ -1814,7 +3789,25 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1814
3789
|
finalize: acceptedCount >= minRequired
|
|
1815
3790
|
}));
|
|
1816
3791
|
this.#onProvideTxData = options.onProvideTxData;
|
|
1817
|
-
this
|
|
3792
|
+
this.#cohortTtlMs = options.cohortTtlMs;
|
|
3793
|
+
this.#phaseTimeoutMs = options.phaseTimeoutMs;
|
|
3794
|
+
this.#advertRepeatIntervalMs = options.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
|
|
3795
|
+
this.session = new AggregationService({
|
|
3796
|
+
did: options.did,
|
|
3797
|
+
keys: options.keys,
|
|
3798
|
+
maxUpdateSizeBytes: options.maxUpdateSizeBytes
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Drain any silent rejections the state machine recorded during the most
|
|
3803
|
+
* recent receive() and surface them as `message-rejected` events. Safe to
|
|
3804
|
+
* call even before a cohortId is assigned.
|
|
3805
|
+
*/
|
|
3806
|
+
#drainRejections() {
|
|
3807
|
+
if (!this.#cohortId) return;
|
|
3808
|
+
for (const r of this.session.drainRejections(this.#cohortId)) {
|
|
3809
|
+
this.emit("message-rejected", { cohortId: this.#cohortId, ...r });
|
|
3810
|
+
}
|
|
1818
3811
|
}
|
|
1819
3812
|
/**
|
|
1820
3813
|
* Run the protocol to completion. Resolves with the final aggregation result
|
|
@@ -1829,22 +3822,103 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1829
3822
|
try {
|
|
1830
3823
|
this.#registerHandlers();
|
|
1831
3824
|
this.#cohortId = this.session.createCohort(this.#config);
|
|
3825
|
+
this.#startTimers();
|
|
1832
3826
|
const advertMsgs = this.session.advertise(this.#cohortId);
|
|
3827
|
+
this.#onPhaseMaybeChanged();
|
|
1833
3828
|
this.emit("cohort-advertised", { cohortId: this.#cohortId });
|
|
1834
|
-
this.#
|
|
3829
|
+
if (this.#advertRepeatIntervalMs > 0) {
|
|
3830
|
+
this.#startAdvertRepeat(advertMsgs);
|
|
3831
|
+
} else {
|
|
3832
|
+
this.#sendAll(advertMsgs).catch((err) => this.#fail(err));
|
|
3833
|
+
}
|
|
1835
3834
|
} catch (err) {
|
|
1836
3835
|
this.#fail(err);
|
|
1837
3836
|
}
|
|
1838
3837
|
});
|
|
1839
3838
|
}
|
|
1840
3839
|
/**
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
1843
|
-
*
|
|
3840
|
+
* Begin publishing the cohort advert immediately and on a repeating interval
|
|
3841
|
+
* until {@link #stopAdvertRepeating} is called. Each advert is broadcast
|
|
3842
|
+
* (no recipient) via the transport's `publishRepeating` primitive.
|
|
3843
|
+
*/
|
|
3844
|
+
#startAdvertRepeat(advertMsgs) {
|
|
3845
|
+
const stops = [];
|
|
3846
|
+
for (const msg of advertMsgs) {
|
|
3847
|
+
stops.push(this.#transport.publishRepeating(msg, this.#did, this.#advertRepeatIntervalMs));
|
|
3848
|
+
}
|
|
3849
|
+
this.#stopAdvertRepeat = () => {
|
|
3850
|
+
for (const stop of stops) {
|
|
3851
|
+
try {
|
|
3852
|
+
stop();
|
|
3853
|
+
} catch {
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
/** Stop the advert republish loop. Idempotent. */
|
|
3859
|
+
#stopAdvertRepeating() {
|
|
3860
|
+
if (!this.#stopAdvertRepeat) return;
|
|
3861
|
+
const stop = this.#stopAdvertRepeat;
|
|
3862
|
+
this.#stopAdvertRepeat = void 0;
|
|
3863
|
+
stop();
|
|
3864
|
+
}
|
|
3865
|
+
/** Schedule cohort TTL + phase timeout at the start of a run. */
|
|
3866
|
+
#startTimers() {
|
|
3867
|
+
if (this.#cohortTtlMs !== void 0) {
|
|
3868
|
+
this.#cohortTtlTimer = setTimeout(() => {
|
|
3869
|
+
const reason = `Cohort ${this.#cohortId ?? ""} exceeded TTL of ${this.#cohortTtlMs}ms`;
|
|
3870
|
+
this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
|
|
3871
|
+
this.#fail(new Error(reason));
|
|
3872
|
+
}, this.#cohortTtlMs);
|
|
3873
|
+
}
|
|
3874
|
+
this.#resetPhaseTimer();
|
|
3875
|
+
}
|
|
3876
|
+
/** Reset the per-phase stall timer. Called when a phase transition is observed. */
|
|
3877
|
+
#resetPhaseTimer() {
|
|
3878
|
+
if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
|
|
3879
|
+
this.#phaseTimer = void 0;
|
|
3880
|
+
if (this.#phaseTimeoutMs === void 0) return;
|
|
3881
|
+
this.#phaseTimer = setTimeout(() => {
|
|
3882
|
+
const reason = `Cohort ${this.#cohortId ?? ""} stalled in phase ${this.#lastObservedPhase ?? "?"} for ${this.#phaseTimeoutMs}ms`;
|
|
3883
|
+
this.emit("cohort-failed", { cohortId: this.#cohortId ?? "", reason });
|
|
3884
|
+
this.#fail(new Error(reason));
|
|
3885
|
+
}, this.#phaseTimeoutMs);
|
|
3886
|
+
}
|
|
3887
|
+
/** Detect a phase change since the last observation and reset the phase timer. */
|
|
3888
|
+
#onPhaseMaybeChanged() {
|
|
3889
|
+
if (!this.#cohortId) return;
|
|
3890
|
+
const phase = this.session.getCohortPhase(this.#cohortId);
|
|
3891
|
+
if (phase !== this.#lastObservedPhase) {
|
|
3892
|
+
this.#lastObservedPhase = phase;
|
|
3893
|
+
this.#resetPhaseTimer();
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
/** Clear both timers. Called on successful completion, stop(), and #fail. */
|
|
3897
|
+
#clearTimers() {
|
|
3898
|
+
if (this.#cohortTtlTimer) clearTimeout(this.#cohortTtlTimer);
|
|
3899
|
+
if (this.#phaseTimer) clearTimeout(this.#phaseTimer);
|
|
3900
|
+
this.#cohortTtlTimer = void 0;
|
|
3901
|
+
this.#phaseTimer = void 0;
|
|
3902
|
+
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Stop the runner early. Marks the runner stopped and detaches transport
|
|
3905
|
+
* handlers so a restart or a new runner doesn't inherit stale dispatch.
|
|
1844
3906
|
*/
|
|
1845
3907
|
stop() {
|
|
1846
3908
|
this.#stopped = true;
|
|
1847
|
-
|
|
3909
|
+
this.#stopAdvertRepeating();
|
|
3910
|
+
this.#clearTimers();
|
|
3911
|
+
this.#unregisterHandlers();
|
|
3912
|
+
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
3913
|
+
}
|
|
3914
|
+
/** Message types this runner listens for on the transport. */
|
|
3915
|
+
static #HANDLED_MESSAGE_TYPES = [
|
|
3916
|
+
COHORT_OPT_IN,
|
|
3917
|
+
SUBMIT_UPDATE,
|
|
3918
|
+
VALIDATION_ACK,
|
|
3919
|
+
NONCE_CONTRIBUTION,
|
|
3920
|
+
SIGNATURE_AUTHORIZATION
|
|
3921
|
+
];
|
|
1848
3922
|
/**
|
|
1849
3923
|
* Internal: handler registration with the transport. Idempotent.
|
|
1850
3924
|
*/
|
|
@@ -1857,6 +3931,14 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1857
3931
|
this.#transport.registerMessageHandler(this.#did, NONCE_CONTRIBUTION, this.#handleNonceContribution.bind(this));
|
|
1858
3932
|
this.#transport.registerMessageHandler(this.#did, SIGNATURE_AUTHORIZATION, this.#handleSignatureAuthorization.bind(this));
|
|
1859
3933
|
}
|
|
3934
|
+
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
3935
|
+
#unregisterHandlers() {
|
|
3936
|
+
if (!this.#handlersRegistered) return;
|
|
3937
|
+
this.#handlersRegistered = false;
|
|
3938
|
+
for (const type of _AggregationServiceRunner.#HANDLED_MESSAGE_TYPES) {
|
|
3939
|
+
this.#transport.unregisterMessageHandler(this.#did, type);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
1860
3942
|
/**
|
|
1861
3943
|
* Internal: message handlers for each protocol step. Each handler:
|
|
1862
3944
|
* 1) feeds the message into the state machine via session.receive()
|
|
@@ -1873,6 +3955,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1873
3955
|
if (this.#stopped) return;
|
|
1874
3956
|
try {
|
|
1875
3957
|
this.session.receive(msg);
|
|
3958
|
+
this.#drainRejections();
|
|
3959
|
+
this.#onPhaseMaybeChanged();
|
|
1876
3960
|
const optIn = this.session.pendingOptIns(this.#cohortId).get(msg.from);
|
|
1877
3961
|
if (!optIn) return;
|
|
1878
3962
|
this.emit("opt-in-received", optIn);
|
|
@@ -1884,19 +3968,23 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1884
3968
|
await this.#sendAll(this.session.acceptParticipant(this.#cohortId, msg.from));
|
|
1885
3969
|
this.emit("participant-accepted", { participantDid: msg.from });
|
|
1886
3970
|
const cohort = this.session.getCohort(this.#cohortId);
|
|
1887
|
-
if (cohort.participants.length >= this.#config.minParticipants) {
|
|
3971
|
+
if (cohort.participants.length >= this.#config.minParticipants && !this.#finalizing) {
|
|
3972
|
+
this.#finalizing = true;
|
|
1888
3973
|
const finalizeDecision = await this.#onReadyToFinalize({
|
|
1889
3974
|
acceptedCount: cohort.participants.length,
|
|
1890
3975
|
minRequired: this.#config.minParticipants
|
|
1891
3976
|
});
|
|
1892
|
-
if (finalizeDecision.finalize) {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
cohortId: this.#cohortId,
|
|
1896
|
-
beaconAddress: cohort.beaconAddress
|
|
1897
|
-
});
|
|
1898
|
-
await this.#sendAll(readyMsgs);
|
|
3977
|
+
if (!finalizeDecision.finalize) {
|
|
3978
|
+
this.#finalizing = false;
|
|
3979
|
+
return;
|
|
1899
3980
|
}
|
|
3981
|
+
const readyMsgs = this.session.finalizeKeygen(this.#cohortId);
|
|
3982
|
+
this.#stopAdvertRepeating();
|
|
3983
|
+
this.emit("keygen-complete", {
|
|
3984
|
+
cohortId: this.#cohortId,
|
|
3985
|
+
beaconAddress: cohort.beaconAddress
|
|
3986
|
+
});
|
|
3987
|
+
await this.#sendAll(readyMsgs);
|
|
1900
3988
|
}
|
|
1901
3989
|
} catch (err) {
|
|
1902
3990
|
this.#fail(err);
|
|
@@ -1914,6 +4002,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1914
4002
|
if (this.#stopped) return;
|
|
1915
4003
|
try {
|
|
1916
4004
|
this.session.receive(msg);
|
|
4005
|
+
this.#drainRejections();
|
|
4006
|
+
this.#onPhaseMaybeChanged();
|
|
1917
4007
|
this.emit("update-received", { participantDid: msg.from });
|
|
1918
4008
|
if (this.session.getCohortPhase(this.#cohortId) === "UpdatesCollected" /* UpdatesCollected */) {
|
|
1919
4009
|
const distributeMsgs = this.session.buildAndDistribute(this.#cohortId);
|
|
@@ -1936,9 +4026,18 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1936
4026
|
if (this.#stopped) return;
|
|
1937
4027
|
try {
|
|
1938
4028
|
this.session.receive(msg);
|
|
4029
|
+
this.#drainRejections();
|
|
4030
|
+
this.#onPhaseMaybeChanged();
|
|
1939
4031
|
const approved = !!msg.body?.approved;
|
|
1940
4032
|
this.emit("validation-received", { participantDid: msg.from, approved });
|
|
1941
|
-
|
|
4033
|
+
const phase = this.session.getCohortPhase(this.#cohortId);
|
|
4034
|
+
if (phase === "Failed" /* Failed */) {
|
|
4035
|
+
const reason = `Validation rejected by participant ${msg.from}`;
|
|
4036
|
+
this.emit("cohort-failed", { cohortId: this.#cohortId, reason });
|
|
4037
|
+
this.#fail(new Error(reason));
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
if (phase === "Validated" /* Validated */) {
|
|
1942
4041
|
const cohort = this.session.getCohort(this.#cohortId);
|
|
1943
4042
|
const txData = await this.#onProvideTxData({
|
|
1944
4043
|
cohortId: this.#cohortId,
|
|
@@ -1966,6 +4065,8 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1966
4065
|
if (this.#stopped) return;
|
|
1967
4066
|
try {
|
|
1968
4067
|
this.session.receive(msg);
|
|
4068
|
+
this.#drainRejections();
|
|
4069
|
+
this.#onPhaseMaybeChanged();
|
|
1969
4070
|
this.emit("nonce-received", { participantDid: msg.from });
|
|
1970
4071
|
if (this.session.getCohortPhase(this.#cohortId) === "NoncesCollected" /* NoncesCollected */) {
|
|
1971
4072
|
await this.#sendAll(this.session.sendAggregatedNonce(this.#cohortId));
|
|
@@ -1986,8 +4087,12 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
1986
4087
|
if (this.#stopped) return;
|
|
1987
4088
|
try {
|
|
1988
4089
|
this.session.receive(msg);
|
|
4090
|
+
this.#drainRejections();
|
|
4091
|
+
this.#onPhaseMaybeChanged();
|
|
1989
4092
|
const result = this.session.getResult(this.#cohortId);
|
|
1990
4093
|
if (result) {
|
|
4094
|
+
this.#clearTimers();
|
|
4095
|
+
this.#unregisterHandlers();
|
|
1991
4096
|
this.emit("signing-complete", result);
|
|
1992
4097
|
this.#resolveRun?.(result);
|
|
1993
4098
|
}
|
|
@@ -2012,6 +4117,10 @@ var AggregationServiceRunner = class extends TypedEventEmitter {
|
|
|
2012
4117
|
* @param {Error} err - The error to handle.
|
|
2013
4118
|
*/
|
|
2014
4119
|
#fail(err) {
|
|
4120
|
+
this.#stopAdvertRepeating();
|
|
4121
|
+
this.#clearTimers();
|
|
4122
|
+
this.#unregisterHandlers();
|
|
4123
|
+
if (this.#cohortId) this.session.removeCohort(this.#cohortId);
|
|
2015
4124
|
this.emit("error", err);
|
|
2016
4125
|
this.#rejectRun?.(err);
|
|
2017
4126
|
}
|
|
@@ -2046,9 +4155,27 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
2046
4155
|
async start() {
|
|
2047
4156
|
this.#registerHandlers();
|
|
2048
4157
|
}
|
|
2049
|
-
/** Stop the runner
|
|
4158
|
+
/** Stop the runner and detach transport handlers. Safe to call repeatedly. */
|
|
2050
4159
|
stop() {
|
|
2051
4160
|
this.#stopped = true;
|
|
4161
|
+
this.#unregisterHandlers();
|
|
4162
|
+
}
|
|
4163
|
+
/** Message types this runner listens for on the transport. */
|
|
4164
|
+
static #HANDLED_MESSAGE_TYPES = [
|
|
4165
|
+
COHORT_ADVERT,
|
|
4166
|
+
COHORT_OPT_IN_ACCEPT,
|
|
4167
|
+
COHORT_READY,
|
|
4168
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
4169
|
+
AUTHORIZATION_REQUEST,
|
|
4170
|
+
AGGREGATED_NONCE
|
|
4171
|
+
];
|
|
4172
|
+
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
4173
|
+
#unregisterHandlers() {
|
|
4174
|
+
if (!this.#handlersRegistered) return;
|
|
4175
|
+
this.#handlersRegistered = false;
|
|
4176
|
+
for (const type of _AggregationParticipantRunner.#HANDLED_MESSAGE_TYPES) {
|
|
4177
|
+
this.#transport.unregisterMessageHandler(this.#did, type);
|
|
4178
|
+
}
|
|
2052
4179
|
}
|
|
2053
4180
|
/**
|
|
2054
4181
|
* Single-shot helper: start, join the first cohort that passes `shouldJoin`,
|
|
@@ -2213,7 +4340,14 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
2213
4340
|
if (this.session.getCohortPhase(cohortId) === "Complete" /* Complete */) {
|
|
2214
4341
|
const info = this.session.joinedCohorts.get(cohortId);
|
|
2215
4342
|
if (info) {
|
|
2216
|
-
this.
|
|
4343
|
+
const validation = this.session.pendingValidations.get(cohortId);
|
|
4344
|
+
this.emit("cohort-complete", {
|
|
4345
|
+
cohortId,
|
|
4346
|
+
beaconAddress: info.beaconAddress,
|
|
4347
|
+
beaconType: validation?.beaconType ?? "",
|
|
4348
|
+
casAnnouncement: validation?.casAnnouncement,
|
|
4349
|
+
smtProof: validation?.smtProof
|
|
4350
|
+
});
|
|
2217
4351
|
}
|
|
2218
4352
|
}
|
|
2219
4353
|
} catch (err) {
|
|
@@ -2235,33 +4369,32 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
2235
4369
|
};
|
|
2236
4370
|
|
|
2237
4371
|
// src/core/beacon/beacon.ts
|
|
2238
|
-
var
|
|
2239
|
-
var
|
|
2240
|
-
var import_bitcoinjs_lib4 = require("bitcoinjs-lib");
|
|
4372
|
+
var import_utils7 = require("@noble/hashes/utils.js");
|
|
4373
|
+
var import_btc_signer4 = require("@scure/btc-signer");
|
|
2241
4374
|
|
|
2242
4375
|
// src/core/beacon/error.ts
|
|
2243
|
-
var
|
|
2244
|
-
var BeaconError = class extends
|
|
4376
|
+
var import_common12 = require("@did-btcr2/common");
|
|
4377
|
+
var BeaconError = class extends import_common12.MethodError {
|
|
2245
4378
|
constructor(message, type = "BeaconError", data) {
|
|
2246
4379
|
super(message, type, data);
|
|
2247
4380
|
}
|
|
2248
4381
|
};
|
|
2249
|
-
var SingletonBeaconError = class extends
|
|
4382
|
+
var SingletonBeaconError = class extends import_common12.MethodError {
|
|
2250
4383
|
constructor(message, type = "SingletonBeaconError", data) {
|
|
2251
4384
|
super(message, type, data);
|
|
2252
4385
|
}
|
|
2253
4386
|
};
|
|
2254
|
-
var AggregateBeaconError = class extends
|
|
4387
|
+
var AggregateBeaconError = class extends import_common12.MethodError {
|
|
2255
4388
|
constructor(message, type = "AggregateBeaconError", data) {
|
|
2256
4389
|
super(message, type, data);
|
|
2257
4390
|
}
|
|
2258
4391
|
};
|
|
2259
|
-
var CASBeaconError = class extends
|
|
4392
|
+
var CASBeaconError = class extends import_common12.MethodError {
|
|
2260
4393
|
constructor(message, type = "CASBeaconError", data) {
|
|
2261
4394
|
super(message, type, data);
|
|
2262
4395
|
}
|
|
2263
4396
|
};
|
|
2264
|
-
var SMTBeaconError = class extends
|
|
4397
|
+
var SMTBeaconError = class extends import_common12.MethodError {
|
|
2265
4398
|
constructor(message, type = "SMTBeaconError", data) {
|
|
2266
4399
|
super(message, type, data);
|
|
2267
4400
|
}
|
|
@@ -2289,6 +4422,121 @@ var StaticFeeEstimator = class {
|
|
|
2289
4422
|
|
|
2290
4423
|
// src/core/beacon/beacon.ts
|
|
2291
4424
|
var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
|
|
4425
|
+
var P2TR_BEACON_TX_VSIZE = 160;
|
|
4426
|
+
var P2WPKH_BEACON_TX_VSIZE = 155;
|
|
4427
|
+
var P2PKH_BEACON_TX_VSIZE = 240;
|
|
4428
|
+
var SINGLETON_BEACON_TX_VSIZE = {
|
|
4429
|
+
p2pkh: P2PKH_BEACON_TX_VSIZE,
|
|
4430
|
+
p2wpkh: P2WPKH_BEACON_TX_VSIZE,
|
|
4431
|
+
p2tr: P2TR_BEACON_TX_VSIZE
|
|
4432
|
+
};
|
|
4433
|
+
function detectSingletonScriptKind(bitcoinAddress, network) {
|
|
4434
|
+
const decoded = (0, import_btc_signer4.Address)(network).decode(bitcoinAddress);
|
|
4435
|
+
if (decoded.type === "pkh") return "p2pkh";
|
|
4436
|
+
if (decoded.type === "wpkh") return "p2wpkh";
|
|
4437
|
+
if (decoded.type === "tr") return "p2tr";
|
|
4438
|
+
throw new BeaconError(
|
|
4439
|
+
`Unsupported singleton beacon address type "${decoded.type}". Expected P2PKH, P2WPKH, or P2TR (taproot key-path).`,
|
|
4440
|
+
"UNSUPPORTED_BEACON_ADDRESS_TYPE",
|
|
4441
|
+
{ address: bitcoinAddress, kind: decoded.type }
|
|
4442
|
+
);
|
|
4443
|
+
}
|
|
4444
|
+
function deriveSingletonAddress(kind, pubkey, network) {
|
|
4445
|
+
if (kind === "p2pkh") return (0, import_btc_signer4.p2pkh)(pubkey, network).address;
|
|
4446
|
+
if (kind === "p2wpkh") return (0, import_btc_signer4.p2wpkh)(pubkey, network).address;
|
|
4447
|
+
return (0, import_btc_signer4.p2tr)(pubkey.slice(1, 33), void 0, network).address;
|
|
4448
|
+
}
|
|
4449
|
+
function opReturnScript(signalBytes) {
|
|
4450
|
+
return import_btc_signer4.Script.encode(["RETURN", signalBytes]);
|
|
4451
|
+
}
|
|
4452
|
+
async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
4453
|
+
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
4454
|
+
if (!utxos.length) {
|
|
4455
|
+
throw new BeaconError(
|
|
4456
|
+
"No UTXOs found, please fund address!",
|
|
4457
|
+
"UNFUNDED_BEACON_ADDRESS",
|
|
4458
|
+
{ address: bitcoinAddress }
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
|
|
4462
|
+
if (!utxo) {
|
|
4463
|
+
throw new BeaconError(
|
|
4464
|
+
"Beacon bitcoin address unfunded or utxos unconfirmed.",
|
|
4465
|
+
"UNFUNDED_BEACON_ADDRESS",
|
|
4466
|
+
{ address: bitcoinAddress }
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
4470
|
+
return { utxo, prevTxBytes: (0, import_utils7.hexToBytes)(prevTxHex) };
|
|
4471
|
+
}
|
|
4472
|
+
async function buildAggregationBeaconTx(opts) {
|
|
4473
|
+
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
4474
|
+
const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
|
|
4475
|
+
const tapOut = (0, import_btc_signer4.p2tr)(opts.internalPubkey, void 0, opts.network);
|
|
4476
|
+
const witnessScript = tapOut.script;
|
|
4477
|
+
const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
|
|
4478
|
+
if (BigInt(utxo.value) <= feeSats) {
|
|
4479
|
+
throw new BeaconError(
|
|
4480
|
+
`UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
4481
|
+
"INSUFFICIENT_FUNDS",
|
|
4482
|
+
{ address: opts.beaconAddress, valueSats: utxo.value, feeSats }
|
|
4483
|
+
);
|
|
4484
|
+
}
|
|
4485
|
+
const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
|
|
4486
|
+
tx.addInput({
|
|
4487
|
+
txid: utxo.txid,
|
|
4488
|
+
index: utxo.vout,
|
|
4489
|
+
nonWitnessUtxo: prevTxBytes,
|
|
4490
|
+
witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
|
|
4491
|
+
tapInternalKey: opts.internalPubkey
|
|
4492
|
+
});
|
|
4493
|
+
tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
|
|
4494
|
+
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
4495
|
+
return {
|
|
4496
|
+
tx,
|
|
4497
|
+
prevOutScripts: [witnessScript],
|
|
4498
|
+
prevOutValues: [BigInt(utxo.value)],
|
|
4499
|
+
beaconAddress: opts.beaconAddress,
|
|
4500
|
+
utxo,
|
|
4501
|
+
feeSats,
|
|
4502
|
+
scriptKind: "p2tr"
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
async function signSingletonInput(tx, inputIdx, kind, signer, prevOutScript, amount) {
|
|
4506
|
+
const pubkey = signer.publicKey;
|
|
4507
|
+
if (kind === "p2pkh") {
|
|
4508
|
+
const sighashType = import_btc_signer4.SigHash.ALL;
|
|
4509
|
+
const sighash2 = tx.preimageLegacy(inputIdx, prevOutScript, sighashType);
|
|
4510
|
+
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
4511
|
+
const sigWithType = (0, import_utils7.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
4512
|
+
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
4513
|
+
tx.finalize();
|
|
4514
|
+
return tx.hex;
|
|
4515
|
+
}
|
|
4516
|
+
if (kind === "p2wpkh") {
|
|
4517
|
+
const decoded = import_btc_signer4.OutScript.decode(prevOutScript);
|
|
4518
|
+
if (decoded.type !== "wpkh") {
|
|
4519
|
+
throw new BeaconError(
|
|
4520
|
+
`Expected P2WPKH prev-output script, got "${decoded.type}".`,
|
|
4521
|
+
"PREVOUT_SCRIPT_MISMATCH",
|
|
4522
|
+
{ kind, observedScriptType: decoded.type }
|
|
4523
|
+
);
|
|
4524
|
+
}
|
|
4525
|
+
const sighashScript = import_btc_signer4.OutScript.encode({ type: "pkh", hash: decoded.hash });
|
|
4526
|
+
const sighashType = import_btc_signer4.SigHash.ALL;
|
|
4527
|
+
const sighash2 = tx.preimageWitnessV0(inputIdx, sighashScript, sighashType, amount);
|
|
4528
|
+
const sig2 = signer.sign(sighash2, "ecdsa");
|
|
4529
|
+
const sigWithType = (0, import_utils7.concatBytes)(sig2, new Uint8Array([sighashType]));
|
|
4530
|
+
tx.updateInput(inputIdx, { partialSig: [[pubkey, sigWithType]] }, true);
|
|
4531
|
+
tx.finalize();
|
|
4532
|
+
return tx.hex;
|
|
4533
|
+
}
|
|
4534
|
+
const sighash = tx.preimageWitnessV1(inputIdx, [prevOutScript], import_btc_signer4.SigHash.DEFAULT, [amount]);
|
|
4535
|
+
const sig = signer.sign(sighash, "bip341");
|
|
4536
|
+
tx.updateInput(inputIdx, { tapKeySig: sig });
|
|
4537
|
+
tx.finalize();
|
|
4538
|
+
return tx.hex;
|
|
4539
|
+
}
|
|
2292
4540
|
var Beacon = class {
|
|
2293
4541
|
/**
|
|
2294
4542
|
* The Beacon service configuration parsed from the DID Document.
|
|
@@ -2298,79 +4546,134 @@ var Beacon = class {
|
|
|
2298
4546
|
this.service = service;
|
|
2299
4547
|
}
|
|
2300
4548
|
/**
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2304
|
-
* 1. Parse the beacon's `serviceEndpoint` (stripping `bitcoin:` prefix) into a Bitcoin address.
|
|
2305
|
-
* 2. Query the address for unconfirmed/confirmed UTXOs.
|
|
2306
|
-
* 3. Select the most recent confirmed UTXO.
|
|
2307
|
-
* 4. Fetch the previous transaction hex for `nonWitnessUtxo`.
|
|
2308
|
-
* 5. Build a PSBT: input (UTXO) → change output + OP_RETURN(signalBytes).
|
|
2309
|
-
* 6. Compute the fee via the supplied (or default) {@link FeeEstimator} against the tx vsize.
|
|
2310
|
-
* 7. Sign input 0 with an ECDSA signer derived from `secretKey`.
|
|
2311
|
-
* 8. Finalize, extract, and broadcast via the REST transaction endpoint.
|
|
4549
|
+
* Build + sign + broadcast a singleton beacon signal transaction. The beacon
|
|
4550
|
+
* address's script kind (P2PKH / P2WPKH / P2TR) is detected automatically
|
|
4551
|
+
* and the input is constructed and signed accordingly.
|
|
2312
4552
|
*
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
4553
|
+
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
4554
|
+
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
4555
|
+
* in isolation. Aggregation beacons use {@link buildAggregationBeaconTx} instead —
|
|
4556
|
+
* the multi-party path can't share the signing phase, but the tx-construction
|
|
4557
|
+
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
2317
4558
|
*
|
|
2318
4559
|
* @param signalBytes 32-byte payload to embed in OP_RETURN.
|
|
2319
|
-
* @param
|
|
4560
|
+
* @param signer Signer used to sign the spending input.
|
|
2320
4561
|
* @param bitcoin Bitcoin network connection.
|
|
2321
4562
|
* @param options Broadcast options (fee estimator, etc.).
|
|
2322
4563
|
* @returns The txid of the broadcast transaction.
|
|
2323
|
-
* @throws {BeaconError} if the address is unfunded
|
|
4564
|
+
* @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
|
|
2324
4565
|
*/
|
|
2325
|
-
async buildSignAndBroadcast(signalBytes,
|
|
4566
|
+
async buildSignAndBroadcast(signalBytes, signer, bitcoin, options) {
|
|
2326
4567
|
const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
2327
|
-
const
|
|
2328
|
-
const
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
4568
|
+
const beaconAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
|
|
4569
|
+
const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
|
|
4570
|
+
const plan = await this.buildSinglePartyTx({
|
|
4571
|
+
signalBytes,
|
|
4572
|
+
beaconAddress,
|
|
4573
|
+
utxo,
|
|
4574
|
+
prevTxBytes,
|
|
4575
|
+
signer,
|
|
4576
|
+
bitcoin,
|
|
4577
|
+
feeEstimator
|
|
4578
|
+
});
|
|
4579
|
+
const signedHex = await this.signSinglePartyTx(plan, signer);
|
|
4580
|
+
return this.broadcastRawTx(bitcoin, signedHex);
|
|
4581
|
+
}
|
|
4582
|
+
/**
|
|
4583
|
+
* Build an unsigned singleton beacon tx ready for {@link signSinglePartyTx}.
|
|
4584
|
+
*
|
|
4585
|
+
* Detects the beacon address script kind (P2PKH / P2WPKH / P2TR) and configures
|
|
4586
|
+
* the input accordingly. Validates that the signer's pubkey produces the beacon
|
|
4587
|
+
* address under that script kind — without this check, a misconfigured caller
|
|
4588
|
+
* would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
|
|
4589
|
+
* the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
|
|
4590
|
+
* round-trip.
|
|
4591
|
+
*/
|
|
4592
|
+
async buildSinglePartyTx(opts) {
|
|
4593
|
+
const network = opts.bitcoin.data;
|
|
4594
|
+
const pubkey = opts.signer.publicKey;
|
|
4595
|
+
const kind = detectSingletonScriptKind(opts.beaconAddress, network);
|
|
4596
|
+
const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
|
|
4597
|
+
if (derivedAddress !== opts.beaconAddress) {
|
|
2340
4598
|
throw new BeaconError(
|
|
2341
|
-
|
|
2342
|
-
"
|
|
2343
|
-
{
|
|
4599
|
+
`Signer pubkey produces ${kind.toUpperCase()} address "${derivedAddress}", but beacon address is "${opts.beaconAddress}".`,
|
|
4600
|
+
"SIGNER_KEY_MISMATCH",
|
|
4601
|
+
{ kind, address: opts.beaconAddress, derivedAddress }
|
|
2344
4602
|
);
|
|
2345
4603
|
}
|
|
2346
|
-
const
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
publicKey: keyPair.publicKey.compressed,
|
|
2350
|
-
sign: (hash5) => keyPair.secretKey.sign(hash5, { scheme: "ecdsa" })
|
|
2351
|
-
};
|
|
2352
|
-
const build = (fee2) => new import_bitcoinjs_lib4.Psbt({ network: bitcoin.data }).addInput({
|
|
2353
|
-
hash: utxo.txid,
|
|
2354
|
-
index: utxo.vout,
|
|
2355
|
-
nonWitnessUtxo: (0, import_utils5.hexToBytes)(prevTx)
|
|
2356
|
-
}).addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - fee2 }).addOutput({ script: import_bitcoinjs_lib4.script.compile([import_bitcoinjs_lib4.opcodes.OP_RETURN, signalBytes]), value: 0n });
|
|
2357
|
-
const probeTx = build(0n).signInput(0, signer).finalizeAllInputs().extractTransaction();
|
|
2358
|
-
const vsize = probeTx.virtualSize();
|
|
2359
|
-
const fee = await feeEstimator.estimateFee(vsize);
|
|
2360
|
-
if (BigInt(utxo.value) <= fee) {
|
|
4604
|
+
const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
|
|
4605
|
+
const amount = BigInt(opts.utxo.value);
|
|
4606
|
+
if (amount <= feeSats) {
|
|
2361
4607
|
throw new BeaconError(
|
|
2362
|
-
`UTXO value (${utxo.value}) insufficient to cover fee (${
|
|
4608
|
+
`UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
2363
4609
|
"INSUFFICIENT_FUNDS",
|
|
2364
|
-
{
|
|
4610
|
+
{ address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
|
|
2365
4611
|
);
|
|
2366
4612
|
}
|
|
2367
|
-
const
|
|
2368
|
-
|
|
4613
|
+
const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
|
|
4614
|
+
let prevOutScript;
|
|
4615
|
+
if (kind === "p2pkh") {
|
|
4616
|
+
prevOutScript = (0, import_btc_signer4.p2pkh)(pubkey, network).script;
|
|
4617
|
+
tx.addInput({
|
|
4618
|
+
txid: opts.utxo.txid,
|
|
4619
|
+
index: opts.utxo.vout,
|
|
4620
|
+
nonWitnessUtxo: opts.prevTxBytes
|
|
4621
|
+
});
|
|
4622
|
+
} else if (kind === "p2wpkh") {
|
|
4623
|
+
prevOutScript = (0, import_btc_signer4.p2wpkh)(pubkey, network).script;
|
|
4624
|
+
tx.addInput({
|
|
4625
|
+
txid: opts.utxo.txid,
|
|
4626
|
+
index: opts.utxo.vout,
|
|
4627
|
+
nonWitnessUtxo: opts.prevTxBytes,
|
|
4628
|
+
witnessUtxo: { amount, script: prevOutScript }
|
|
4629
|
+
});
|
|
4630
|
+
} else {
|
|
4631
|
+
const internalKey = pubkey.slice(1, 33);
|
|
4632
|
+
prevOutScript = (0, import_btc_signer4.p2tr)(internalKey, void 0, network).script;
|
|
4633
|
+
tx.addInput({
|
|
4634
|
+
txid: opts.utxo.txid,
|
|
4635
|
+
index: opts.utxo.vout,
|
|
4636
|
+
nonWitnessUtxo: opts.prevTxBytes,
|
|
4637
|
+
witnessUtxo: { amount, script: prevOutScript },
|
|
4638
|
+
tapInternalKey: internalKey
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
|
|
4642
|
+
tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
|
|
4643
|
+
return {
|
|
4644
|
+
tx,
|
|
4645
|
+
prevOutScripts: [prevOutScript],
|
|
4646
|
+
prevOutValues: [amount],
|
|
4647
|
+
beaconAddress: opts.beaconAddress,
|
|
4648
|
+
utxo: opts.utxo,
|
|
4649
|
+
feeSats,
|
|
4650
|
+
scriptKind: kind
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Sign + finalize the unsigned single-party tx and return its raw hex.
|
|
4655
|
+
* Dispatches to the correct signing primitive based on `plan.scriptKind`.
|
|
4656
|
+
*/
|
|
4657
|
+
async signSinglePartyTx(plan, signer) {
|
|
4658
|
+
return signSingletonInput(
|
|
4659
|
+
plan.tx,
|
|
4660
|
+
0,
|
|
4661
|
+
plan.scriptKind,
|
|
4662
|
+
signer,
|
|
4663
|
+
plan.prevOutScripts[0],
|
|
4664
|
+
plan.prevOutValues[0]
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
|
|
4669
|
+
*/
|
|
4670
|
+
async broadcastRawTx(bitcoin, rawHex) {
|
|
4671
|
+
return bitcoin.rest.transaction.send(rawHex);
|
|
2369
4672
|
}
|
|
2370
4673
|
};
|
|
2371
4674
|
|
|
2372
4675
|
// src/core/beacon/cas-beacon.ts
|
|
2373
|
-
var
|
|
4676
|
+
var import_common13 = require("@did-btcr2/common");
|
|
2374
4677
|
var CASBeacon = class extends Beacon {
|
|
2375
4678
|
/**
|
|
2376
4679
|
* Creates an instance of CASBeacon.
|
|
@@ -2411,7 +4714,7 @@ var CASBeacon = class extends Beacon {
|
|
|
2411
4714
|
if (!updateHashEncoded) {
|
|
2412
4715
|
continue;
|
|
2413
4716
|
}
|
|
2414
|
-
const updateHash = (0,
|
|
4717
|
+
const updateHash = (0, import_common13.encode)((0, import_common13.decode)(updateHashEncoded, "base64urlnopad"), "hex");
|
|
2415
4718
|
const signedUpdate = sidecar.updateMap.get(updateHash);
|
|
2416
4719
|
if (!signedUpdate) {
|
|
2417
4720
|
needs.push({
|
|
@@ -2434,19 +4737,19 @@ var CASBeacon = class extends Beacon {
|
|
|
2434
4737
|
* and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
2435
4738
|
*
|
|
2436
4739
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
2437
|
-
* @param {
|
|
4740
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
2438
4741
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
2439
4742
|
* @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
|
|
2440
4743
|
* `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
|
|
2441
4744
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
2442
4745
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
2443
4746
|
*/
|
|
2444
|
-
async broadcastSignal(signedUpdate,
|
|
4747
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
2445
4748
|
const did = this.service.id.split("#")[0];
|
|
2446
|
-
const updateHash = (0,
|
|
4749
|
+
const updateHash = (0, import_common13.canonicalHash)(signedUpdate);
|
|
2447
4750
|
const casAnnouncement = { [did]: updateHash };
|
|
2448
|
-
const announcementHash = (0,
|
|
2449
|
-
await this.buildSignAndBroadcast(announcementHash,
|
|
4751
|
+
const announcementHash = (0, import_common13.hash)((0, import_common13.canonicalize)(casAnnouncement));
|
|
4752
|
+
await this.buildSignAndBroadcast(announcementHash, signer, bitcoin, options);
|
|
2450
4753
|
if (options?.casPublish) {
|
|
2451
4754
|
await options.casPublish(casAnnouncement);
|
|
2452
4755
|
}
|
|
@@ -2455,10 +4758,10 @@ var CASBeacon = class extends Beacon {
|
|
|
2455
4758
|
};
|
|
2456
4759
|
|
|
2457
4760
|
// src/core/beacon/factory.ts
|
|
2458
|
-
var
|
|
4761
|
+
var import_common16 = require("@did-btcr2/common");
|
|
2459
4762
|
|
|
2460
4763
|
// src/core/beacon/singleton-beacon.ts
|
|
2461
|
-
var
|
|
4764
|
+
var import_common14 = require("@did-btcr2/common");
|
|
2462
4765
|
var SingletonBeacon = class extends Beacon {
|
|
2463
4766
|
/**
|
|
2464
4767
|
* Creates an instance of SingletonBeacon.
|
|
@@ -2499,23 +4802,23 @@ var SingletonBeacon = class extends Beacon {
|
|
|
2499
4802
|
* {@link Beacon.buildSignAndBroadcast}.
|
|
2500
4803
|
*
|
|
2501
4804
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
2502
|
-
* @param {
|
|
4805
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
2503
4806
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
2504
4807
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
2505
4808
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
2506
4809
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
2507
4810
|
*/
|
|
2508
|
-
async broadcastSignal(signedUpdate,
|
|
2509
|
-
const signalBytes = (0,
|
|
2510
|
-
await this.buildSignAndBroadcast(signalBytes,
|
|
4811
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
4812
|
+
const signalBytes = (0, import_common14.hash)((0, import_common14.canonicalize)(signedUpdate));
|
|
4813
|
+
await this.buildSignAndBroadcast(signalBytes, signer, bitcoin, options);
|
|
2511
4814
|
return signedUpdate;
|
|
2512
4815
|
}
|
|
2513
4816
|
};
|
|
2514
4817
|
|
|
2515
4818
|
// src/core/beacon/smt-beacon.ts
|
|
2516
|
-
var
|
|
4819
|
+
var import_common15 = require("@did-btcr2/common");
|
|
2517
4820
|
var import_smt3 = require("@did-btcr2/smt");
|
|
2518
|
-
var
|
|
4821
|
+
var import_utils8 = require("@noble/hashes/utils");
|
|
2519
4822
|
var SMTBeacon = class extends Beacon {
|
|
2520
4823
|
/**
|
|
2521
4824
|
* Creates an instance of SMTBeacon.
|
|
@@ -2593,20 +4896,20 @@ var SMTBeacon = class extends Beacon {
|
|
|
2593
4896
|
* signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
2594
4897
|
*
|
|
2595
4898
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
2596
|
-
* @param {
|
|
4899
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
2597
4900
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
2598
4901
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
2599
4902
|
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
2600
4903
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
2601
4904
|
*/
|
|
2602
|
-
async broadcastSignal(signedUpdate,
|
|
4905
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
2603
4906
|
const did = this.service.id.split("#")[0];
|
|
2604
|
-
const canonicalBytes = new TextEncoder().encode((0,
|
|
2605
|
-
const nonce = (0,
|
|
4907
|
+
const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
|
|
4908
|
+
const nonce = (0, import_utils8.randomBytes)(32);
|
|
2606
4909
|
const tree = new import_smt3.BTCR2MerkleTree();
|
|
2607
4910
|
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
2608
4911
|
tree.finalize();
|
|
2609
|
-
await this.buildSignAndBroadcast(tree.rootHash,
|
|
4912
|
+
await this.buildSignAndBroadcast(tree.rootHash, signer, bitcoin, options);
|
|
2610
4913
|
return signedUpdate;
|
|
2611
4914
|
}
|
|
2612
4915
|
};
|
|
@@ -2627,19 +4930,19 @@ var BeaconFactory = class {
|
|
|
2627
4930
|
case "SMTBeacon":
|
|
2628
4931
|
return new SMTBeacon(service);
|
|
2629
4932
|
default:
|
|
2630
|
-
throw new
|
|
4933
|
+
throw new import_common16.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
|
|
2631
4934
|
}
|
|
2632
4935
|
}
|
|
2633
4936
|
};
|
|
2634
4937
|
|
|
2635
4938
|
// src/core/beacon/signal-discovery.ts
|
|
2636
4939
|
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
2637
|
-
var
|
|
4940
|
+
var import_common18 = require("@did-btcr2/common");
|
|
2638
4941
|
|
|
2639
4942
|
// src/core/beacon/utils.ts
|
|
2640
4943
|
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
2641
|
-
var
|
|
2642
|
-
var
|
|
4944
|
+
var import_common17 = require("@did-btcr2/common");
|
|
4945
|
+
var import_btc_signer5 = require("@scure/btc-signer");
|
|
2643
4946
|
|
|
2644
4947
|
// src/utils/appendix.ts
|
|
2645
4948
|
var import_dids = require("@web5/dids");
|
|
@@ -3679,198 +5982,6 @@ var Appendix = class _Appendix {
|
|
|
3679
5982
|
}
|
|
3680
5983
|
};
|
|
3681
5984
|
|
|
3682
|
-
// src/core/identifier.ts
|
|
3683
|
-
var import_common12 = require("@did-btcr2/common");
|
|
3684
|
-
var import_keypair3 = require("@did-btcr2/keypair");
|
|
3685
|
-
var import_base5 = require("@scure/base");
|
|
3686
|
-
var Identifier = class _Identifier {
|
|
3687
|
-
/**
|
|
3688
|
-
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
|
|
3689
|
-
*
|
|
3690
|
-
* A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
|
|
3691
|
-
* - the specification version;
|
|
3692
|
-
* - the Bitcoin network identifier; and
|
|
3693
|
-
* - either:
|
|
3694
|
-
* - a key-value representing a secp256k1 public key; or
|
|
3695
|
-
* - a hash-value representing the hash of an initiating external DID document.
|
|
3696
|
-
*
|
|
3697
|
-
* @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
|
|
3698
|
-
* @param {DidCreateOptions} options The DID creation options.
|
|
3699
|
-
* @returns {string} The new did:btcr2 identifier.
|
|
3700
|
-
*/
|
|
3701
|
-
static encode(genesisBytes, options) {
|
|
3702
|
-
const { idType, version = 1, network } = options;
|
|
3703
|
-
if (!(idType in import_common12.IdentifierTypes)) {
|
|
3704
|
-
throw new import_common12.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common12.INVALID_DID, { idType });
|
|
3705
|
-
}
|
|
3706
|
-
if (isNaN(version) || version > 1) {
|
|
3707
|
-
throw new import_common12.IdentifierError('Expected "version" to be 1', import_common12.INVALID_DID, { version });
|
|
3708
|
-
}
|
|
3709
|
-
if (typeof network === "string" && !(network in import_common12.BitcoinNetworkNames)) {
|
|
3710
|
-
throw new import_common12.IdentifierError('Invalid "network" name', import_common12.INVALID_DID, { network });
|
|
3711
|
-
}
|
|
3712
|
-
if (typeof network === "number" && (network < 0 || network > 8)) {
|
|
3713
|
-
throw new import_common12.IdentifierError('Invalid "network" number', import_common12.INVALID_DID, { network });
|
|
3714
|
-
}
|
|
3715
|
-
if (idType === "KEY") {
|
|
3716
|
-
try {
|
|
3717
|
-
new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
|
|
3718
|
-
} catch {
|
|
3719
|
-
throw new import_common12.IdentifierError(
|
|
3720
|
-
'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
|
|
3721
|
-
import_common12.INVALID_DID,
|
|
3722
|
-
{ genesisBytes }
|
|
3723
|
-
);
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
const hrp = idType === "KEY" ? "k" : "x";
|
|
3727
|
-
const nibbles = [];
|
|
3728
|
-
const fCount = Math.floor((version - 1) / 15);
|
|
3729
|
-
for (let i = 0; i < fCount; i++) {
|
|
3730
|
-
nibbles.push(15);
|
|
3731
|
-
}
|
|
3732
|
-
nibbles.push((version - 1) % 15);
|
|
3733
|
-
if (typeof network === "string") {
|
|
3734
|
-
nibbles.push(import_common12.BitcoinNetworkNames[network]);
|
|
3735
|
-
} else if (typeof network === "number") {
|
|
3736
|
-
nibbles.push(network + 11);
|
|
3737
|
-
}
|
|
3738
|
-
if (nibbles.length % 2 !== 0) {
|
|
3739
|
-
nibbles.push(0);
|
|
3740
|
-
}
|
|
3741
|
-
if (fCount !== 0) {
|
|
3742
|
-
for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
|
|
3743
|
-
throw new import_common12.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
|
-
const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
|
|
3747
|
-
return `did:btcr2:${import_base5.bech32m.encodeFromBytes(hrp, dataBytes)}`;
|
|
3748
|
-
}
|
|
3749
|
-
/**
|
|
3750
|
-
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
|
|
3751
|
-
* @param {string} identifier The BTCR2 DID to be parsed
|
|
3752
|
-
* @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
|
|
3753
|
-
* @throws {DidError} if an error occurs while parsing the identifier
|
|
3754
|
-
* @throws {DidErrorCode.InvalidDid} if identifier is invalid
|
|
3755
|
-
* @throws {DidErrorCode.MethodNotSupported} if the method is not supported
|
|
3756
|
-
*/
|
|
3757
|
-
static decode(identifier) {
|
|
3758
|
-
const components = identifier.split(":");
|
|
3759
|
-
if (components.length !== 3) {
|
|
3760
|
-
throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
|
|
3761
|
-
}
|
|
3762
|
-
const [scheme, method, encoded] = components;
|
|
3763
|
-
if (scheme !== "did") {
|
|
3764
|
-
throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
|
|
3765
|
-
}
|
|
3766
|
-
if (method !== "btcr2") {
|
|
3767
|
-
throw new import_common12.IdentifierError(`Invalid did method: ${method}`, import_common12.METHOD_NOT_SUPPORTED, { identifier });
|
|
3768
|
-
}
|
|
3769
|
-
if (!encoded) {
|
|
3770
|
-
throw new import_common12.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common12.INVALID_DID, { identifier });
|
|
3771
|
-
}
|
|
3772
|
-
const { prefix: hrp, bytes: dataBytes } = import_base5.bech32m.decodeToBytes(encoded);
|
|
3773
|
-
if (!["x", "k"].includes(hrp)) {
|
|
3774
|
-
throw new import_common12.IdentifierError(`Invalid hrp: ${hrp}`, import_common12.INVALID_DID, { identifier });
|
|
3775
|
-
}
|
|
3776
|
-
if (!dataBytes) {
|
|
3777
|
-
throw new import_common12.IdentifierError(`Failed to decode id: ${encoded}`, import_common12.INVALID_DID, { identifier });
|
|
3778
|
-
}
|
|
3779
|
-
const idType = hrp === "k" ? "KEY" : "EXTERNAL";
|
|
3780
|
-
let version = 1;
|
|
3781
|
-
let byteIndex = 0;
|
|
3782
|
-
let nibblesConsumed = 0;
|
|
3783
|
-
let currentByte = dataBytes[byteIndex];
|
|
3784
|
-
let versionNibble = currentByte >>> 4;
|
|
3785
|
-
while (versionNibble === 15) {
|
|
3786
|
-
version += 15;
|
|
3787
|
-
if (nibblesConsumed % 2 === 0) {
|
|
3788
|
-
versionNibble = currentByte & 15;
|
|
3789
|
-
} else {
|
|
3790
|
-
currentByte = dataBytes[++byteIndex];
|
|
3791
|
-
versionNibble = currentByte >>> 4;
|
|
3792
|
-
}
|
|
3793
|
-
nibblesConsumed += 1;
|
|
3794
|
-
if (version > 1) {
|
|
3795
|
-
throw new import_common12.IdentifierError(`Invalid version: ${version}`, import_common12.INVALID_DID, { identifier });
|
|
3796
|
-
}
|
|
3797
|
-
}
|
|
3798
|
-
version += versionNibble;
|
|
3799
|
-
nibblesConsumed += 1;
|
|
3800
|
-
let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
|
|
3801
|
-
nibblesConsumed += 1;
|
|
3802
|
-
let network = import_common12.BitcoinNetworkNames[networkValue];
|
|
3803
|
-
if (!network) {
|
|
3804
|
-
if (networkValue >= 8 && networkValue <= 15) {
|
|
3805
|
-
network = networkValue - 11;
|
|
3806
|
-
} else {
|
|
3807
|
-
throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
|
|
3808
|
-
}
|
|
3809
|
-
}
|
|
3810
|
-
if (nibblesConsumed % 2 === 1) {
|
|
3811
|
-
const fillerNibble = currentByte & 15;
|
|
3812
|
-
if (fillerNibble !== 0) {
|
|
3813
|
-
throw new import_common12.IdentifierError(`Invalid did: ${identifier}`, import_common12.INVALID_DID, { identifier });
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
const genesisBytes = dataBytes.slice(byteIndex + 1);
|
|
3817
|
-
if (idType === "KEY") {
|
|
3818
|
-
try {
|
|
3819
|
-
new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
|
|
3820
|
-
} catch {
|
|
3821
|
-
throw new import_common12.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common12.INVALID_DID, { identifier });
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
3824
|
-
return { idType, hrp, version, network, genesisBytes };
|
|
3825
|
-
}
|
|
3826
|
-
/**
|
|
3827
|
-
* Generates a new did:btcr2 identifier based on a newly generated key pair.
|
|
3828
|
-
* @returns {string} The new did:btcr2 identifier.
|
|
3829
|
-
*/
|
|
3830
|
-
static generate() {
|
|
3831
|
-
const keyPair = import_keypair3.SchnorrKeyPair.generate();
|
|
3832
|
-
const did = this.encode(
|
|
3833
|
-
keyPair.publicKey.compressed,
|
|
3834
|
-
{
|
|
3835
|
-
idType: "KEY",
|
|
3836
|
-
version: 1,
|
|
3837
|
-
network: "regtest"
|
|
3838
|
-
}
|
|
3839
|
-
);
|
|
3840
|
-
return { keyPair: keyPair.exportJSON(), did };
|
|
3841
|
-
}
|
|
3842
|
-
/**
|
|
3843
|
-
* Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
|
|
3844
|
-
* @param {string} did The did:btcr2 identifier to extract the public key from.
|
|
3845
|
-
* @returns {CompressedSecp256k1PublicKey} The compressed public key.
|
|
3846
|
-
* @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
|
|
3847
|
-
*/
|
|
3848
|
-
static getPublicKey(did) {
|
|
3849
|
-
const { idType, genesisBytes } = _Identifier.decode(did);
|
|
3850
|
-
if (idType !== "KEY") {
|
|
3851
|
-
throw new import_common12.IdentifierError(
|
|
3852
|
-
`Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
|
|
3853
|
-
import_common12.INVALID_DID,
|
|
3854
|
-
{ did, idType }
|
|
3855
|
-
);
|
|
3856
|
-
}
|
|
3857
|
-
return new import_keypair3.CompressedSecp256k1PublicKey(genesisBytes);
|
|
3858
|
-
}
|
|
3859
|
-
/**
|
|
3860
|
-
* Validates a did:btcr2 identifier.
|
|
3861
|
-
* @param {string} identifier The did:btcr2 identifier to validate.
|
|
3862
|
-
* @returns {boolean} True if the identifier is valid, false otherwise.
|
|
3863
|
-
*/
|
|
3864
|
-
static isValid(identifier) {
|
|
3865
|
-
try {
|
|
3866
|
-
this.decode(identifier);
|
|
3867
|
-
return true;
|
|
3868
|
-
} catch {
|
|
3869
|
-
return false;
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
};
|
|
3873
|
-
|
|
3874
5985
|
// src/core/beacon/utils.ts
|
|
3875
5986
|
var BeaconUtils = class {
|
|
3876
5987
|
/**
|
|
@@ -3881,7 +5992,7 @@ var BeaconUtils = class {
|
|
|
3881
5992
|
*/
|
|
3882
5993
|
static parseBitcoinAddress(uri) {
|
|
3883
5994
|
if (!uri.startsWith("bitcoin:")) {
|
|
3884
|
-
throw new
|
|
5995
|
+
throw new import_common17.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
|
|
3885
5996
|
}
|
|
3886
5997
|
return uri.replace("bitcoin:", "").split("?")[0];
|
|
3887
5998
|
}
|
|
@@ -3939,7 +6050,8 @@ var BeaconUtils = class {
|
|
|
3939
6050
|
const network = (0, import_bitcoin.getNetwork)(components.network);
|
|
3940
6051
|
const pubkey = components.genesisBytes;
|
|
3941
6052
|
const id = `${did}#initial${addressType.toUpperCase()}`;
|
|
3942
|
-
const
|
|
6053
|
+
const address = addressType === "p2tr" ? (0, import_btc_signer5.p2tr)(pubkey.slice(1, 33), void 0, network).address : addressType === "p2wpkh" ? (0, import_btc_signer5.p2wpkh)(pubkey, network).address : (0, import_btc_signer5.p2pkh)(pubkey, network).address;
|
|
6054
|
+
const serviceEndpoint = `bitcoin:${address}`;
|
|
3943
6055
|
return { id, type: beaconType, serviceEndpoint };
|
|
3944
6056
|
} catch (error) {
|
|
3945
6057
|
throw new BeaconError(
|
|
@@ -3957,27 +6069,27 @@ var BeaconUtils = class {
|
|
|
3957
6069
|
*/
|
|
3958
6070
|
static generateBeaconServices({ id, publicKey, network, beaconType }) {
|
|
3959
6071
|
try {
|
|
3960
|
-
const
|
|
3961
|
-
const
|
|
3962
|
-
const
|
|
3963
|
-
if (!
|
|
3964
|
-
throw new
|
|
6072
|
+
const p2pkhAddr = (0, import_btc_signer5.p2pkh)(publicKey, network).address;
|
|
6073
|
+
const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
|
|
6074
|
+
const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
|
|
6075
|
+
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
6076
|
+
throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
|
|
3965
6077
|
}
|
|
3966
6078
|
return [
|
|
3967
6079
|
{
|
|
3968
6080
|
id: `${id}#initialP2PKH`,
|
|
3969
6081
|
type: beaconType,
|
|
3970
|
-
serviceEndpoint: `bitcoin:${
|
|
6082
|
+
serviceEndpoint: `bitcoin:${p2pkhAddr}`
|
|
3971
6083
|
},
|
|
3972
6084
|
{
|
|
3973
6085
|
id: `${id}#initialP2WPKH`,
|
|
3974
6086
|
type: beaconType,
|
|
3975
|
-
serviceEndpoint: `bitcoin:${
|
|
6087
|
+
serviceEndpoint: `bitcoin:${p2wpkhAddr}`
|
|
3976
6088
|
},
|
|
3977
6089
|
{
|
|
3978
6090
|
id: `${id}#initialP2TR`,
|
|
3979
6091
|
type: beaconType,
|
|
3980
|
-
serviceEndpoint: `bitcoin:${
|
|
6092
|
+
serviceEndpoint: `bitcoin:${p2trAddr}`
|
|
3981
6093
|
}
|
|
3982
6094
|
];
|
|
3983
6095
|
} catch (error) {
|
|
@@ -4077,7 +6189,7 @@ var BeaconSignalDiscovery = class {
|
|
|
4077
6189
|
}
|
|
4078
6190
|
const rpc = bitcoin.rpc;
|
|
4079
6191
|
if (!rpc) {
|
|
4080
|
-
throw new
|
|
6192
|
+
throw new import_common18.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
|
|
4081
6193
|
}
|
|
4082
6194
|
const targetHeight = await rpc.getBlockCount();
|
|
4083
6195
|
const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
|
|
@@ -4148,26 +6260,24 @@ var BeaconSignalDiscovery = class {
|
|
|
4148
6260
|
|
|
4149
6261
|
// src/core/resolver.ts
|
|
4150
6262
|
var import_bitcoin4 = require("@did-btcr2/bitcoin");
|
|
4151
|
-
var
|
|
4152
|
-
var
|
|
4153
|
-
var
|
|
6263
|
+
var import_common22 = require("@did-btcr2/common");
|
|
6264
|
+
var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
|
|
6265
|
+
var import_keypair6 = require("@did-btcr2/keypair");
|
|
4154
6266
|
|
|
4155
6267
|
// src/did-btcr2.ts
|
|
4156
|
-
var
|
|
6268
|
+
var import_common21 = require("@did-btcr2/common");
|
|
4157
6269
|
var import_dids2 = require("@web5/dids");
|
|
4158
|
-
var ecc = __toESM(require("@bitcoinerlab/secp256k1"), 1);
|
|
4159
|
-
var import_bitcoinjs_lib7 = require("bitcoinjs-lib");
|
|
4160
6270
|
|
|
4161
6271
|
// src/core/updater.ts
|
|
4162
|
-
var
|
|
4163
|
-
var
|
|
6272
|
+
var import_common20 = require("@did-btcr2/common");
|
|
6273
|
+
var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
|
|
4164
6274
|
|
|
4165
6275
|
// src/utils/did-document.ts
|
|
4166
6276
|
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
4167
|
-
var
|
|
4168
|
-
var
|
|
4169
|
-
var
|
|
4170
|
-
var
|
|
6277
|
+
var import_common19 = require("@did-btcr2/common");
|
|
6278
|
+
var import_keypair5 = require("@did-btcr2/keypair");
|
|
6279
|
+
var import_utils10 = require("@web5/dids/utils");
|
|
6280
|
+
var import_btc_signer6 = require("@scure/btc-signer");
|
|
4171
6281
|
var BTCR2_DID_DOCUMENT_CONTEXT = [
|
|
4172
6282
|
"https://www.w3.org/ns/did/v1.1",
|
|
4173
6283
|
"https://btcr2.dev/context/v1"
|
|
@@ -4208,20 +6318,20 @@ var DidDocument = class _DidDocument {
|
|
|
4208
6318
|
deactivated;
|
|
4209
6319
|
constructor(document) {
|
|
4210
6320
|
if (!document.id) {
|
|
4211
|
-
throw new
|
|
6321
|
+
throw new import_common19.DidDocumentError("DID Document must have an id", import_common19.INVALID_DID_DOCUMENT, document);
|
|
4212
6322
|
}
|
|
4213
|
-
const idType = document.id.includes("k1") ?
|
|
6323
|
+
const idType = document.id.includes("k1") ? import_common19.IdentifierTypes.KEY : import_common19.IdentifierTypes.EXTERNAL;
|
|
4214
6324
|
const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
|
|
4215
6325
|
const { id, verificationMethod: vm, service } = document;
|
|
4216
6326
|
if (!isGenesis) {
|
|
4217
6327
|
if (!_DidDocument.isValidId(id)) {
|
|
4218
|
-
throw new
|
|
6328
|
+
throw new import_common19.DidDocumentError(`Invalid id: ${id}`, import_common19.INVALID_DID_DOCUMENT, document);
|
|
4219
6329
|
}
|
|
4220
6330
|
if (!_DidDocument.isValidVerificationMethods(vm)) {
|
|
4221
|
-
throw new
|
|
6331
|
+
throw new import_common19.DidDocumentError("Invalid verificationMethod: " + vm, import_common19.INVALID_DID_DOCUMENT, document);
|
|
4222
6332
|
}
|
|
4223
6333
|
if (!_DidDocument.isValidServices(service)) {
|
|
4224
|
-
throw new
|
|
6334
|
+
throw new import_common19.DidDocumentError("Invalid service: " + service, import_common19.INVALID_DID_DOCUMENT, document);
|
|
4225
6335
|
}
|
|
4226
6336
|
}
|
|
4227
6337
|
this.id = document.id;
|
|
@@ -4231,7 +6341,7 @@ var DidDocument = class _DidDocument {
|
|
|
4231
6341
|
"https://www.w3.org/ns/did/v1.1",
|
|
4232
6342
|
"https://btcr2.dev/context/v1"
|
|
4233
6343
|
];
|
|
4234
|
-
if (idType ===
|
|
6344
|
+
if (idType === import_common19.IdentifierTypes.KEY) {
|
|
4235
6345
|
const keyRef = `${this.id}#initialKey`;
|
|
4236
6346
|
this.authentication = document.authentication || [keyRef];
|
|
4237
6347
|
this.assertionMethod = document.assertionMethod || [keyRef];
|
|
@@ -4317,19 +6427,19 @@ var DidDocument = class _DidDocument {
|
|
|
4317
6427
|
*/
|
|
4318
6428
|
static isValid(didDocument) {
|
|
4319
6429
|
if (!this.isValidContext(didDocument?.["@context"])) {
|
|
4320
|
-
throw new
|
|
6430
|
+
throw new import_common19.DidDocumentError('Invalid "@context"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
4321
6431
|
}
|
|
4322
6432
|
if (!this.isValidId(didDocument?.id)) {
|
|
4323
|
-
throw new
|
|
6433
|
+
throw new import_common19.DidDocumentError('Invalid "id"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
4324
6434
|
}
|
|
4325
6435
|
if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
|
|
4326
|
-
throw new
|
|
6436
|
+
throw new import_common19.DidDocumentError('Invalid "verificationMethod"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
4327
6437
|
}
|
|
4328
6438
|
if (!this.isValidServices(didDocument?.service)) {
|
|
4329
|
-
throw new
|
|
6439
|
+
throw new import_common19.DidDocumentError('Invalid "service"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
4330
6440
|
}
|
|
4331
6441
|
if (!this.isValidVerificationRelationships(didDocument)) {
|
|
4332
|
-
throw new
|
|
6442
|
+
throw new import_common19.DidDocumentError("Invalid verification relationships", import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
4333
6443
|
}
|
|
4334
6444
|
return true;
|
|
4335
6445
|
}
|
|
@@ -4376,7 +6486,7 @@ var DidDocument = class _DidDocument {
|
|
|
4376
6486
|
* @returns {boolean} True if the services are valid.
|
|
4377
6487
|
*/
|
|
4378
6488
|
static isValidServices(service) {
|
|
4379
|
-
return Array.isArray(service) && service.every(
|
|
6489
|
+
return Array.isArray(service) && service.every(import_utils10.isDidService);
|
|
4380
6490
|
}
|
|
4381
6491
|
/**
|
|
4382
6492
|
* Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
|
|
@@ -4419,16 +6529,16 @@ var DidDocument = class _DidDocument {
|
|
|
4419
6529
|
*/
|
|
4420
6530
|
validateGenesis() {
|
|
4421
6531
|
if (this.id !== ID_PLACEHOLDER_VALUE) {
|
|
4422
|
-
throw new
|
|
6532
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument ID", import_common19.INVALID_DID_DOCUMENT, this);
|
|
4423
6533
|
}
|
|
4424
6534
|
if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
|
|
4425
|
-
throw new
|
|
6535
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
4426
6536
|
}
|
|
4427
6537
|
if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
|
|
4428
|
-
throw new
|
|
6538
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument service", import_common19.INVALID_DID_DOCUMENT, this);
|
|
4429
6539
|
}
|
|
4430
6540
|
if (!_DidDocument.isValidVerificationRelationships(this)) {
|
|
4431
|
-
throw new
|
|
6541
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
4432
6542
|
}
|
|
4433
6543
|
return true;
|
|
4434
6544
|
}
|
|
@@ -4438,7 +6548,7 @@ var DidDocument = class _DidDocument {
|
|
|
4438
6548
|
*/
|
|
4439
6549
|
toIntermediate() {
|
|
4440
6550
|
if (this.id.includes("k1")) {
|
|
4441
|
-
throw new
|
|
6551
|
+
throw new import_common19.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common19.INVALID_DID_DOCUMENT, this);
|
|
4442
6552
|
}
|
|
4443
6553
|
return new GenesisDocument(this);
|
|
4444
6554
|
}
|
|
@@ -4468,7 +6578,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
4468
6578
|
* @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
|
|
4469
6579
|
*/
|
|
4470
6580
|
static fromDidDocument(didDocument) {
|
|
4471
|
-
const intermediateDocument =
|
|
6581
|
+
const intermediateDocument = import_common19.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
|
|
4472
6582
|
return new _GenesisDocument(intermediateDocument);
|
|
4473
6583
|
}
|
|
4474
6584
|
/**
|
|
@@ -4487,9 +6597,9 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
4487
6597
|
* @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
|
|
4488
6598
|
*/
|
|
4489
6599
|
static fromPublicKey(publicKey, network) {
|
|
4490
|
-
const pk = new
|
|
6600
|
+
const pk = new import_keypair5.CompressedSecp256k1PublicKey(publicKey);
|
|
4491
6601
|
const id = ID_PLACEHOLDER_VALUE;
|
|
4492
|
-
const address =
|
|
6602
|
+
const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin3.getNetwork)(network)).address;
|
|
4493
6603
|
const services = [{
|
|
4494
6604
|
id: `${id}#service-0`,
|
|
4495
6605
|
serviceEndpoint: `bitcoin:${address}`,
|
|
@@ -4525,20 +6635,18 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
4525
6635
|
* @returns {Bytes} The genesis bytes.
|
|
4526
6636
|
*/
|
|
4527
6637
|
static toGenesisBytes(genesisDocument) {
|
|
4528
|
-
return (0,
|
|
6638
|
+
return (0, import_common19.hash)((0, import_common19.canonicalize)(genesisDocument));
|
|
4529
6639
|
}
|
|
4530
6640
|
};
|
|
4531
6641
|
|
|
4532
6642
|
// src/core/updater.ts
|
|
4533
6643
|
var Updater = class _Updater {
|
|
4534
|
-
#
|
|
6644
|
+
#state = { phase: "Construct" };
|
|
4535
6645
|
#sourceDocument;
|
|
4536
6646
|
#patches;
|
|
4537
6647
|
#sourceVersionId;
|
|
4538
6648
|
#verificationMethod;
|
|
4539
6649
|
#beaconService;
|
|
4540
|
-
#unsignedUpdate = null;
|
|
4541
|
-
#signedUpdate = null;
|
|
4542
6650
|
/**
|
|
4543
6651
|
* @internal — Use {@link DidBtcr2.update} to create instances.
|
|
4544
6652
|
*/
|
|
@@ -4572,19 +6680,26 @@ var Updater = class _Updater {
|
|
|
4572
6680
|
patch: patches,
|
|
4573
6681
|
targetHash: "",
|
|
4574
6682
|
targetVersionId: sourceVersionId + 1,
|
|
4575
|
-
sourceHash: (0,
|
|
6683
|
+
sourceHash: (0, import_common20.canonicalHash)(sourceDocument)
|
|
4576
6684
|
};
|
|
4577
|
-
const targetDocument =
|
|
6685
|
+
const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
|
|
6686
|
+
if (targetDocument.id !== sourceDocument.id) {
|
|
6687
|
+
throw new import_common20.UpdateError(
|
|
6688
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
|
|
6689
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6690
|
+
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
6691
|
+
);
|
|
6692
|
+
}
|
|
4578
6693
|
try {
|
|
4579
6694
|
DidDocument.isValid(targetDocument);
|
|
4580
6695
|
} catch (error) {
|
|
4581
|
-
throw new
|
|
6696
|
+
throw new import_common20.UpdateError(
|
|
4582
6697
|
"Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
|
|
4583
|
-
|
|
6698
|
+
import_common20.INVALID_DID_UPDATE,
|
|
4584
6699
|
targetDocument
|
|
4585
6700
|
);
|
|
4586
6701
|
}
|
|
4587
|
-
unsignedUpdate.targetHash = (0,
|
|
6702
|
+
unsignedUpdate.targetHash = (0, import_common20.canonicalHash)(targetDocument);
|
|
4588
6703
|
return unsignedUpdate;
|
|
4589
6704
|
}
|
|
4590
6705
|
/**
|
|
@@ -4593,13 +6708,28 @@ var Updater = class _Updater {
|
|
|
4593
6708
|
* @param {string} did The did-btcr2 identifier to derive the root capability from.
|
|
4594
6709
|
* @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
|
|
4595
6710
|
* @param {DidVerificationMethod} verificationMethod The verification method for signing.
|
|
4596
|
-
* @param {
|
|
6711
|
+
* @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
|
|
4597
6712
|
* @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
|
|
4598
6713
|
*/
|
|
4599
|
-
static sign(did, unsignedUpdate, verificationMethod,
|
|
6714
|
+
static sign(did, unsignedUpdate, verificationMethod, signer) {
|
|
6715
|
+
if (!did.startsWith("did:btcr2:")) {
|
|
6716
|
+
throw new import_common20.UpdateError(
|
|
6717
|
+
`Expected a did:btcr2 identifier for the root capability; got "${did}".`,
|
|
6718
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6719
|
+
{ did }
|
|
6720
|
+
);
|
|
6721
|
+
}
|
|
4600
6722
|
const controller = verificationMethod.controller;
|
|
4601
|
-
const
|
|
4602
|
-
|
|
6723
|
+
const hashIdx = verificationMethod.id.indexOf("#");
|
|
6724
|
+
if (hashIdx < 0) {
|
|
6725
|
+
throw new import_common20.UpdateError(
|
|
6726
|
+
`Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
|
|
6727
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6728
|
+
{ verificationMethodId: verificationMethod.id }
|
|
6729
|
+
);
|
|
6730
|
+
}
|
|
6731
|
+
const id = verificationMethod.id.slice(hashIdx);
|
|
6732
|
+
const multikey = import_cryptosuite2.SchnorrMultikey.fromSigner(id, controller, signer);
|
|
4603
6733
|
const config = {
|
|
4604
6734
|
"@context": [
|
|
4605
6735
|
"https://w3id.org/security/v2",
|
|
@@ -4623,24 +6753,20 @@ var Updater = class _Updater {
|
|
|
4623
6753
|
*
|
|
4624
6754
|
* @param {BeaconService} beaconService The beacon service to broadcast through.
|
|
4625
6755
|
* @param {SignedBTCR2Update} update The signed update to announce.
|
|
4626
|
-
* @param {
|
|
6756
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
4627
6757
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
4628
6758
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
4629
6759
|
*/
|
|
4630
|
-
static async announce(beaconService, update,
|
|
6760
|
+
static async announce(beaconService, update, signer, bitcoin) {
|
|
4631
6761
|
const beacon = BeaconFactory.establish(beaconService);
|
|
4632
|
-
return beacon.broadcastSignal(update,
|
|
6762
|
+
return beacon.broadcastSignal(update, signer, bitcoin);
|
|
4633
6763
|
}
|
|
4634
|
-
//
|
|
6764
|
+
// Private instance wrappers
|
|
4635
6765
|
// Delegate to the public statics with bound instance fields for cleaner
|
|
4636
6766
|
// advance/provide code.
|
|
4637
6767
|
#construct() {
|
|
4638
6768
|
return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
|
|
4639
6769
|
}
|
|
4640
|
-
#sign(secretKey) {
|
|
4641
|
-
return _Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate, this.#verificationMethod, secretKey);
|
|
4642
|
-
}
|
|
4643
|
-
// ─── State machine ─────────────────────────────────────────────────────────
|
|
4644
6770
|
/**
|
|
4645
6771
|
* Advance the state machine. Returns either:
|
|
4646
6772
|
* - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
|
|
@@ -4648,30 +6774,30 @@ var Updater = class _Updater {
|
|
|
4648
6774
|
*/
|
|
4649
6775
|
advance() {
|
|
4650
6776
|
while (true) {
|
|
4651
|
-
switch (this.#phase) {
|
|
6777
|
+
switch (this.#state.phase) {
|
|
4652
6778
|
// Phase: Construct
|
|
4653
6779
|
// Build the unsigned update from source doc + patches. Pure, synchronous.
|
|
4654
|
-
case "Construct"
|
|
4655
|
-
|
|
4656
|
-
this.#
|
|
6780
|
+
case "Construct": {
|
|
6781
|
+
const unsignedUpdate = this.#construct();
|
|
6782
|
+
this.#state = { phase: "Sign", unsignedUpdate };
|
|
4657
6783
|
continue;
|
|
4658
6784
|
}
|
|
4659
6785
|
// Phase: Sign
|
|
4660
6786
|
// Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
|
|
4661
|
-
case "Sign"
|
|
6787
|
+
case "Sign": {
|
|
4662
6788
|
return {
|
|
4663
6789
|
status: "action-required",
|
|
4664
6790
|
needs: [{
|
|
4665
6791
|
kind: "NeedSigningKey",
|
|
4666
6792
|
verificationMethodId: this.#verificationMethod.id,
|
|
4667
|
-
unsignedUpdate: this.#unsignedUpdate
|
|
6793
|
+
unsignedUpdate: this.#state.unsignedUpdate
|
|
4668
6794
|
}]
|
|
4669
6795
|
};
|
|
4670
6796
|
}
|
|
4671
6797
|
// Phase: Fund
|
|
4672
6798
|
// Emit NeedFunding with the beacon address. The caller checks UTXOs,
|
|
4673
6799
|
// funds the address if needed, and provides to continue.
|
|
4674
|
-
case "Fund"
|
|
6800
|
+
case "Fund": {
|
|
4675
6801
|
const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
|
|
4676
6802
|
return {
|
|
4677
6803
|
status: "action-required",
|
|
@@ -4685,21 +6811,21 @@ var Updater = class _Updater {
|
|
|
4685
6811
|
// Phase: Broadcast
|
|
4686
6812
|
// Emit NeedBroadcast with the signed update + beacon service. The caller performs
|
|
4687
6813
|
// the actual on-chain announcement (or hands off to the aggregation protocol).
|
|
4688
|
-
case "Broadcast"
|
|
6814
|
+
case "Broadcast": {
|
|
4689
6815
|
return {
|
|
4690
6816
|
status: "action-required",
|
|
4691
6817
|
needs: [{
|
|
4692
6818
|
kind: "NeedBroadcast",
|
|
4693
6819
|
beaconService: this.#beaconService,
|
|
4694
|
-
signedUpdate: this.#signedUpdate
|
|
6820
|
+
signedUpdate: this.#state.signedUpdate
|
|
4695
6821
|
}]
|
|
4696
6822
|
};
|
|
4697
6823
|
}
|
|
4698
6824
|
// Phase: Complete
|
|
4699
|
-
case "Complete"
|
|
6825
|
+
case "Complete": {
|
|
4700
6826
|
return {
|
|
4701
6827
|
status: "complete",
|
|
4702
|
-
result: { signedUpdate: this.#signedUpdate }
|
|
6828
|
+
result: { signedUpdate: this.#state.signedUpdate }
|
|
4703
6829
|
};
|
|
4704
6830
|
}
|
|
4705
6831
|
}
|
|
@@ -4708,49 +6834,63 @@ var Updater = class _Updater {
|
|
|
4708
6834
|
provide(need, data) {
|
|
4709
6835
|
switch (need.kind) {
|
|
4710
6836
|
case "NeedSigningKey": {
|
|
4711
|
-
if (this.#phase !== "Sign"
|
|
4712
|
-
throw new
|
|
4713
|
-
`Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
|
|
4714
|
-
|
|
4715
|
-
{ phase: this.#phase }
|
|
6837
|
+
if (this.#state.phase !== "Sign") {
|
|
6838
|
+
throw new import_common20.UpdateError(
|
|
6839
|
+
`Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
|
|
6840
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6841
|
+
{ phase: this.#state.phase }
|
|
4716
6842
|
);
|
|
4717
6843
|
}
|
|
4718
6844
|
if (!data) {
|
|
4719
|
-
throw new
|
|
4720
|
-
"NeedSigningKey requires
|
|
4721
|
-
|
|
6845
|
+
throw new import_common20.UpdateError(
|
|
6846
|
+
"NeedSigningKey requires a Signer.",
|
|
6847
|
+
import_common20.INVALID_DID_UPDATE
|
|
4722
6848
|
);
|
|
4723
6849
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
this.#
|
|
6850
|
+
const unsignedUpdate = this.#state.unsignedUpdate;
|
|
6851
|
+
const signedUpdate = _Updater.sign(
|
|
6852
|
+
this.#sourceDocument.id,
|
|
6853
|
+
unsignedUpdate,
|
|
6854
|
+
this.#verificationMethod,
|
|
6855
|
+
data
|
|
6856
|
+
);
|
|
6857
|
+
this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
|
|
4732
6858
|
break;
|
|
4733
6859
|
}
|
|
4734
6860
|
case "NeedFunding": {
|
|
4735
|
-
if (this.#phase !== "Fund"
|
|
4736
|
-
throw new
|
|
4737
|
-
`Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
|
|
4738
|
-
|
|
4739
|
-
{ phase: this.#phase }
|
|
6861
|
+
if (this.#state.phase !== "Fund") {
|
|
6862
|
+
throw new import_common20.UpdateError(
|
|
6863
|
+
`Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
|
|
6864
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6865
|
+
{ phase: this.#state.phase }
|
|
4740
6866
|
);
|
|
4741
6867
|
}
|
|
4742
|
-
|
|
6868
|
+
if (data !== void 0) {
|
|
6869
|
+
const proof = data;
|
|
6870
|
+
if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
|
|
6871
|
+
throw new import_common20.UpdateError(
|
|
6872
|
+
`NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
|
|
6873
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6874
|
+
{ utxoCount: proof.utxoCount }
|
|
6875
|
+
);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
this.#state = {
|
|
6879
|
+
phase: "Broadcast",
|
|
6880
|
+
unsignedUpdate: this.#state.unsignedUpdate,
|
|
6881
|
+
signedUpdate: this.#state.signedUpdate
|
|
6882
|
+
};
|
|
4743
6883
|
break;
|
|
4744
6884
|
}
|
|
4745
6885
|
case "NeedBroadcast": {
|
|
4746
|
-
if (this.#phase !== "Broadcast"
|
|
4747
|
-
throw new
|
|
4748
|
-
`Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
|
|
4749
|
-
|
|
4750
|
-
{ phase: this.#phase }
|
|
6886
|
+
if (this.#state.phase !== "Broadcast") {
|
|
6887
|
+
throw new import_common20.UpdateError(
|
|
6888
|
+
`Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
|
|
6889
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6890
|
+
{ phase: this.#state.phase }
|
|
4751
6891
|
);
|
|
4752
6892
|
}
|
|
4753
|
-
this.#
|
|
6893
|
+
this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
|
|
4754
6894
|
break;
|
|
4755
6895
|
}
|
|
4756
6896
|
}
|
|
@@ -4758,7 +6898,6 @@ var Updater = class _Updater {
|
|
|
4758
6898
|
};
|
|
4759
6899
|
|
|
4760
6900
|
// src/did-btcr2.ts
|
|
4761
|
-
(0, import_bitcoinjs_lib7.initEccLib)(ecc);
|
|
4762
6901
|
var DidBtcr2 = class {
|
|
4763
6902
|
/**
|
|
4764
6903
|
* Name of the DID method, as defined in the DID BTCR2 specification
|
|
@@ -4783,9 +6922,9 @@ var DidBtcr2 = class {
|
|
|
4783
6922
|
static create(genesisBytes, options) {
|
|
4784
6923
|
const { idType, version = 1, network = "bitcoin" } = options || {};
|
|
4785
6924
|
if (!idType) {
|
|
4786
|
-
throw new
|
|
6925
|
+
throw new import_common21.MethodError(
|
|
4787
6926
|
"idType is required for creating a did:btcr2 identifier",
|
|
4788
|
-
|
|
6927
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
4789
6928
|
options
|
|
4790
6929
|
);
|
|
4791
6930
|
}
|
|
@@ -4815,7 +6954,7 @@ var DidBtcr2 = class {
|
|
|
4815
6954
|
static resolve(did, resolutionOptions = {}) {
|
|
4816
6955
|
const didComponents = Identifier.decode(did);
|
|
4817
6956
|
const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
|
|
4818
|
-
const currentDocument = didComponents.hrp ===
|
|
6957
|
+
const currentDocument = didComponents.hrp === import_common21.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
|
|
4819
6958
|
return new Resolver(didComponents, sidecarData, currentDocument, {
|
|
4820
6959
|
versionId: resolutionOptions.versionId,
|
|
4821
6960
|
versionTime: resolutionOptions.versionTime,
|
|
@@ -4853,39 +6992,39 @@ var DidBtcr2 = class {
|
|
|
4853
6992
|
beaconId
|
|
4854
6993
|
}) {
|
|
4855
6994
|
if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
|
|
4856
|
-
throw new
|
|
6995
|
+
throw new import_common21.UpdateError(
|
|
4857
6996
|
"Invalid verificationMethodId: not authorized for capabilityInvocation",
|
|
4858
|
-
|
|
6997
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
4859
6998
|
sourceDocument
|
|
4860
6999
|
);
|
|
4861
7000
|
}
|
|
4862
7001
|
const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
|
|
4863
7002
|
if (!verificationMethod) {
|
|
4864
|
-
throw new
|
|
7003
|
+
throw new import_common21.UpdateError(
|
|
4865
7004
|
"Invalid verificationMethod: not found in source document",
|
|
4866
|
-
|
|
7005
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
4867
7006
|
{ sourceDocument, verificationMethodId }
|
|
4868
7007
|
);
|
|
4869
7008
|
}
|
|
4870
7009
|
if (verificationMethod.type !== "Multikey") {
|
|
4871
|
-
throw new
|
|
7010
|
+
throw new import_common21.UpdateError(
|
|
4872
7011
|
'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
|
|
4873
|
-
|
|
7012
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
4874
7013
|
verificationMethod
|
|
4875
7014
|
);
|
|
4876
7015
|
}
|
|
4877
7016
|
if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
|
|
4878
|
-
throw new
|
|
7017
|
+
throw new import_common21.UpdateError(
|
|
4879
7018
|
'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
|
|
4880
|
-
|
|
7019
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
4881
7020
|
verificationMethod
|
|
4882
7021
|
);
|
|
4883
7022
|
}
|
|
4884
7023
|
const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
|
|
4885
7024
|
if (!beaconService) {
|
|
4886
|
-
throw new
|
|
7025
|
+
throw new import_common21.UpdateError(
|
|
4887
7026
|
"No beacon service found for provided beaconId",
|
|
4888
|
-
|
|
7027
|
+
import_common21.INVALID_DID_UPDATE,
|
|
4889
7028
|
{ sourceDocument, beaconId }
|
|
4890
7029
|
);
|
|
4891
7030
|
}
|
|
@@ -4911,7 +7050,7 @@ var DidBtcr2 = class {
|
|
|
4911
7050
|
methodId ??= "#initialKey";
|
|
4912
7051
|
const parsedDid = import_dids2.Did.parse(didDocument.id);
|
|
4913
7052
|
if (parsedDid && parsedDid.method !== this.methodName) {
|
|
4914
|
-
throw new
|
|
7053
|
+
throw new import_common21.MethodError(`Method not supported: ${parsedDid.method}`, import_common21.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
|
|
4915
7054
|
}
|
|
4916
7055
|
const verificationMethod = didDocument.verificationMethod?.find(
|
|
4917
7056
|
(vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
|
|
@@ -4927,7 +7066,7 @@ var DidBtcr2 = class {
|
|
|
4927
7066
|
};
|
|
4928
7067
|
|
|
4929
7068
|
// src/core/resolver.ts
|
|
4930
|
-
var
|
|
7069
|
+
var import_utils12 = require("@noble/curves/utils.js");
|
|
4931
7070
|
var Resolver = class _Resolver {
|
|
4932
7071
|
// --- Immutable inputs ---
|
|
4933
7072
|
#didComponents;
|
|
@@ -4967,7 +7106,7 @@ var Resolver = class _Resolver {
|
|
|
4967
7106
|
static deterministic(didComponents) {
|
|
4968
7107
|
const genesisBytes = didComponents.genesisBytes;
|
|
4969
7108
|
const did = Identifier.encode(genesisBytes, didComponents);
|
|
4970
|
-
const { multibase } = new
|
|
7109
|
+
const { multibase } = new import_keypair6.CompressedSecp256k1PublicKey(genesisBytes);
|
|
4971
7110
|
const service = BeaconUtils.generateBeaconServices({
|
|
4972
7111
|
id: did,
|
|
4973
7112
|
publicKey: genesisBytes,
|
|
@@ -4993,14 +7132,14 @@ var Resolver = class _Resolver {
|
|
|
4993
7132
|
* @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
|
|
4994
7133
|
*/
|
|
4995
7134
|
static external(didComponents, genesisDocument) {
|
|
4996
|
-
const genesisDocumentHash = (0,
|
|
4997
|
-
if (!(0,
|
|
4998
|
-
throw new
|
|
7135
|
+
const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
|
|
7136
|
+
if (!(0, import_utils12.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
7137
|
+
throw new import_common22.ResolveError(
|
|
4999
7138
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
5000
|
-
|
|
7139
|
+
import_common22.INVALID_DID_DOCUMENT,
|
|
5001
7140
|
{
|
|
5002
|
-
genesisBytes: (0,
|
|
5003
|
-
genesisDocumentHash: (0,
|
|
7141
|
+
genesisBytes: (0, import_common22.encode)(didComponents.genesisBytes, "hex"),
|
|
7142
|
+
genesisDocumentHash: (0, import_common22.encode)(genesisDocumentHash, "hex")
|
|
5004
7143
|
}
|
|
5005
7144
|
);
|
|
5006
7145
|
}
|
|
@@ -5019,12 +7158,12 @@ var Resolver = class _Resolver {
|
|
|
5019
7158
|
const updateMap = /* @__PURE__ */ new Map();
|
|
5020
7159
|
if (sidecar.updates?.length)
|
|
5021
7160
|
for (const update of sidecar.updates) {
|
|
5022
|
-
updateMap.set((0,
|
|
7161
|
+
updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5023
7162
|
}
|
|
5024
7163
|
const casMap = /* @__PURE__ */ new Map();
|
|
5025
7164
|
if (sidecar.casUpdates?.length)
|
|
5026
7165
|
for (const update of sidecar.casUpdates) {
|
|
5027
|
-
casMap.set((0,
|
|
7166
|
+
casMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5028
7167
|
}
|
|
5029
7168
|
const smtMap = /* @__PURE__ */ new Map();
|
|
5030
7169
|
if (sidecar.smtProofs?.length)
|
|
@@ -5057,12 +7196,12 @@ var Resolver = class _Resolver {
|
|
|
5057
7196
|
}
|
|
5058
7197
|
};
|
|
5059
7198
|
for (const [update, block] of updates) {
|
|
5060
|
-
const currentDocumentHash = (0,
|
|
5061
|
-
const blocktime =
|
|
5062
|
-
response.metadata.updated =
|
|
7199
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(response.didDocument);
|
|
7200
|
+
const blocktime = import_common22.DateUtils.blocktimeToTimestamp(block.time);
|
|
7201
|
+
response.metadata.updated = import_common22.DateUtils.toISOStringNonFractional(blocktime);
|
|
5063
7202
|
response.metadata.confirmations = block.confirmations;
|
|
5064
7203
|
if (versionTime) {
|
|
5065
|
-
if (blocktime >
|
|
7204
|
+
if (blocktime > import_common22.DateUtils.dateStringToTimestamp(versionTime)) {
|
|
5066
7205
|
return response;
|
|
5067
7206
|
}
|
|
5068
7207
|
}
|
|
@@ -5070,22 +7209,22 @@ var Resolver = class _Resolver {
|
|
|
5070
7209
|
updateHashHistory.push(currentDocumentHash);
|
|
5071
7210
|
this.confirmDuplicate(update, updateHashHistory);
|
|
5072
7211
|
} else if (update.targetVersionId === currentVersionId + 1) {
|
|
5073
|
-
const sourceHashBytes = (0,
|
|
5074
|
-
if (!(0,
|
|
5075
|
-
throw new
|
|
7212
|
+
const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
|
|
7213
|
+
if (!(0, import_utils12.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
7214
|
+
throw new import_common22.ResolveError(
|
|
5076
7215
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
5077
|
-
|
|
7216
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5078
7217
|
{
|
|
5079
7218
|
sourceHash: update.sourceHash,
|
|
5080
|
-
currentDocumentHash: (0,
|
|
7219
|
+
currentDocumentHash: (0, import_common22.encode)(currentDocumentHash, "hex")
|
|
5081
7220
|
}
|
|
5082
7221
|
);
|
|
5083
7222
|
}
|
|
5084
7223
|
response.didDocument = this.applyUpdate(response.didDocument, update);
|
|
5085
|
-
const unsignedUpdate =
|
|
5086
|
-
updateHashHistory.push((0,
|
|
7224
|
+
const unsignedUpdate = import_common22.JSONUtils.deleteKeys(update, ["proof"]);
|
|
7225
|
+
updateHashHistory.push((0, import_common22.canonicalHashBytes)(unsignedUpdate));
|
|
5087
7226
|
} else if (update.targetVersionId > currentVersionId + 1) {
|
|
5088
|
-
throw new
|
|
7227
|
+
throw new import_common22.ResolveError(
|
|
5089
7228
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
5090
7229
|
"LATE_PUBLISHING_ERROR",
|
|
5091
7230
|
{
|
|
@@ -5116,15 +7255,15 @@ var Resolver = class _Resolver {
|
|
|
5116
7255
|
*/
|
|
5117
7256
|
static confirmDuplicate(update, updateHashHistory) {
|
|
5118
7257
|
const { proof: _, ...unsignedUpdate } = update;
|
|
5119
|
-
const unsignedUpdateHash = (0,
|
|
7258
|
+
const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
|
|
5120
7259
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
5121
|
-
if (!(0,
|
|
5122
|
-
throw new
|
|
7260
|
+
if (!(0, import_utils12.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
|
|
7261
|
+
throw new import_common22.ResolveError(
|
|
5123
7262
|
`Invalid duplicate: unsigned update hash does not match historical hash`,
|
|
5124
|
-
|
|
7263
|
+
import_common22.LATE_PUBLISHING_ERROR,
|
|
5125
7264
|
{
|
|
5126
|
-
unsignedUpdateHash: (0,
|
|
5127
|
-
historicalHash: (0,
|
|
7265
|
+
unsignedUpdateHash: (0, import_common22.encode)(unsignedUpdateHash, "hex"),
|
|
7266
|
+
historicalHash: (0, import_common22.encode)(historicalUpdateHash, "hex")
|
|
5128
7267
|
}
|
|
5129
7268
|
);
|
|
5130
7269
|
}
|
|
@@ -5139,42 +7278,42 @@ var Resolver = class _Resolver {
|
|
|
5139
7278
|
static applyUpdate(currentDocument, update) {
|
|
5140
7279
|
const capabilityId = update.proof?.capability;
|
|
5141
7280
|
if (!capabilityId) {
|
|
5142
|
-
throw new
|
|
7281
|
+
throw new import_common22.ResolveError("No root capability found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5143
7282
|
}
|
|
5144
7283
|
const rootCapability = Appendix.dereferenceZcapId(capabilityId);
|
|
5145
7284
|
const { invocationTarget, controller: rootController } = rootCapability;
|
|
5146
7285
|
if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
|
|
5147
|
-
throw new
|
|
7286
|
+
throw new import_common22.ResolveError(
|
|
5148
7287
|
"Invalid root capability",
|
|
5149
|
-
|
|
7288
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5150
7289
|
{ rootCapability, currentDocument }
|
|
5151
7290
|
);
|
|
5152
7291
|
}
|
|
5153
7292
|
const verificationMethodId = update.proof?.verificationMethod;
|
|
5154
7293
|
if (!verificationMethodId) {
|
|
5155
|
-
throw new
|
|
7294
|
+
throw new import_common22.ResolveError("No verificationMethod found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5156
7295
|
}
|
|
5157
7296
|
const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
|
|
5158
|
-
const multikey =
|
|
5159
|
-
const cryptosuite = new
|
|
5160
|
-
const canonicalUpdate = (0,
|
|
5161
|
-
const diProof = new
|
|
7297
|
+
const multikey = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm);
|
|
7298
|
+
const cryptosuite = new import_cryptosuite3.BIP340Cryptosuite(multikey);
|
|
7299
|
+
const canonicalUpdate = (0, import_common22.canonicalize)(update);
|
|
7300
|
+
const diProof = new import_cryptosuite3.BIP340DataIntegrityProof(cryptosuite);
|
|
5162
7301
|
const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
|
|
5163
7302
|
if (!verificationResult.verified) {
|
|
5164
|
-
throw new
|
|
7303
|
+
throw new import_common22.ResolveError(
|
|
5165
7304
|
"Invalid update: proof not verified",
|
|
5166
|
-
|
|
7305
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5167
7306
|
verificationResult
|
|
5168
7307
|
);
|
|
5169
7308
|
}
|
|
5170
|
-
const updatedDocument =
|
|
7309
|
+
const updatedDocument = import_common22.JSONPatch.apply(currentDocument, update.patch);
|
|
5171
7310
|
DidDocument.validate(updatedDocument);
|
|
5172
|
-
const currentDocumentHash = (0,
|
|
5173
|
-
const updateTargetHash = (0,
|
|
5174
|
-
if (!(0,
|
|
5175
|
-
throw new
|
|
7311
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
|
|
7312
|
+
const updateTargetHash = (0, import_common22.decode)(update.targetHash);
|
|
7313
|
+
if (!(0, import_utils12.equalBytes)(updateTargetHash, currentDocumentHash)) {
|
|
7314
|
+
throw new import_common22.ResolveError(
|
|
5176
7315
|
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
5177
|
-
|
|
7316
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5178
7317
|
{ updateTargetHash, currentDocumentHash }
|
|
5179
7318
|
);
|
|
5180
7319
|
}
|
|
@@ -5202,7 +7341,7 @@ var Resolver = class _Resolver {
|
|
|
5202
7341
|
this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
|
|
5203
7342
|
continue;
|
|
5204
7343
|
}
|
|
5205
|
-
const genesisHash = (0,
|
|
7344
|
+
const genesisHash = (0, import_common22.encode)(this.#didComponents.genesisBytes, "hex");
|
|
5206
7345
|
return {
|
|
5207
7346
|
status: "action-required",
|
|
5208
7347
|
needs: [{ kind: "NeedGenesisDocument", genesisHash }]
|
|
@@ -5306,21 +7445,21 @@ var Resolver = class _Resolver {
|
|
|
5306
7445
|
}
|
|
5307
7446
|
case "NeedCASAnnouncement": {
|
|
5308
7447
|
const announcement = data;
|
|
5309
|
-
this.#sidecarData.casMap.set((0,
|
|
7448
|
+
this.#sidecarData.casMap.set((0, import_common22.canonicalHash)(announcement, { encoding: "hex" }), announcement);
|
|
5310
7449
|
break;
|
|
5311
7450
|
}
|
|
5312
7451
|
case "NeedSignedUpdate": {
|
|
5313
7452
|
const update = data;
|
|
5314
|
-
this.#sidecarData.updateMap.set((0,
|
|
7453
|
+
this.#sidecarData.updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5315
7454
|
break;
|
|
5316
7455
|
}
|
|
5317
7456
|
case "NeedSMTProof": {
|
|
5318
7457
|
const smtNeed = need;
|
|
5319
7458
|
const proof = data;
|
|
5320
7459
|
if (proof.id !== smtNeed.smtRootHash) {
|
|
5321
|
-
throw new
|
|
7460
|
+
throw new import_common22.ResolveError(
|
|
5322
7461
|
`SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proof.id}`,
|
|
5323
|
-
|
|
7462
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5324
7463
|
{ expected: smtNeed.smtRootHash, actual: proof.id }
|
|
5325
7464
|
);
|
|
5326
7465
|
}
|
|
@@ -5332,12 +7471,12 @@ var Resolver = class _Resolver {
|
|
|
5332
7471
|
};
|
|
5333
7472
|
|
|
5334
7473
|
// src/utils/did-document-builder.ts
|
|
5335
|
-
var
|
|
7474
|
+
var import_common23 = require("@did-btcr2/common");
|
|
5336
7475
|
var DidDocumentBuilder = class {
|
|
5337
7476
|
document = {};
|
|
5338
7477
|
constructor(initialDocument) {
|
|
5339
7478
|
if (!initialDocument.id) {
|
|
5340
|
-
throw new
|
|
7479
|
+
throw new import_common23.DidDocumentError('Missing required "id" property', import_common23.INVALID_DID_DOCUMENT, initialDocument);
|
|
5341
7480
|
}
|
|
5342
7481
|
this.document.id = initialDocument.id;
|
|
5343
7482
|
this.document.verificationMethod = initialDocument.verificationMethod ?? [];
|
|
@@ -5389,6 +7528,7 @@ var DidDocumentBuilder = class {
|
|
|
5389
7528
|
0 && (module.exports = {
|
|
5390
7529
|
AGGREGATED_NONCE,
|
|
5391
7530
|
AGGREGATION_MESSAGE_PREFIX,
|
|
7531
|
+
AGGREGATION_WIRE_VERSION,
|
|
5392
7532
|
AUTHORIZATION_REQUEST,
|
|
5393
7533
|
AggregateBeaconError,
|
|
5394
7534
|
AggregationCohort,
|
|
@@ -5415,6 +7555,12 @@ var DidDocumentBuilder = class {
|
|
|
5415
7555
|
COHORT_OPT_IN,
|
|
5416
7556
|
COHORT_OPT_IN_ACCEPT,
|
|
5417
7557
|
COHORT_READY,
|
|
7558
|
+
CONSOLE_LOGGER,
|
|
7559
|
+
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
7560
|
+
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
7561
|
+
DEFAULT_CLOCK_SKEW_SEC,
|
|
7562
|
+
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
7563
|
+
DEFAULT_NONCE_LEN_BYTES,
|
|
5418
7564
|
DEFAULT_NOSTR_RELAYS,
|
|
5419
7565
|
DID_REGEX,
|
|
5420
7566
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
@@ -5425,15 +7571,31 @@ var DidDocumentBuilder = class {
|
|
|
5425
7571
|
DidVerificationMethod,
|
|
5426
7572
|
Document,
|
|
5427
7573
|
GenesisDocument,
|
|
7574
|
+
HTTP_ENVELOPE_VERSION,
|
|
7575
|
+
HTTP_ROUTE,
|
|
7576
|
+
HttpClientTransport,
|
|
7577
|
+
HttpServerTransport,
|
|
7578
|
+
HttpTransportError,
|
|
5428
7579
|
ID_PLACEHOLDER_VALUE,
|
|
5429
7580
|
Identifier,
|
|
7581
|
+
InMemoryRateLimitStore,
|
|
7582
|
+
InboxBuffer,
|
|
5430
7583
|
NONCE_CONTRIBUTION,
|
|
7584
|
+
NonceCache,
|
|
5431
7585
|
NostrTransport,
|
|
7586
|
+
P2PKH_BEACON_TX_VSIZE,
|
|
7587
|
+
P2TR_BEACON_TX_VSIZE,
|
|
7588
|
+
P2WPKH_BEACON_TX_VSIZE,
|
|
5432
7589
|
ParticipantCohortPhase,
|
|
7590
|
+
REQUEST_AUTH_SCHEME,
|
|
7591
|
+
RateLimiter,
|
|
5433
7592
|
Resolver,
|
|
5434
7593
|
SIGNATURE_AUTHORIZATION,
|
|
7594
|
+
SILENT_LOGGER,
|
|
7595
|
+
SINGLETON_BEACON_TX_VSIZE,
|
|
5435
7596
|
SMTBeacon,
|
|
5436
7597
|
SMTBeaconError,
|
|
7598
|
+
SSE_EVENT,
|
|
5437
7599
|
SUBMIT_UPDATE,
|
|
5438
7600
|
ServiceCohortPhase,
|
|
5439
7601
|
SigningSessionError,
|
|
@@ -5447,6 +7609,8 @@ var DidDocumentBuilder = class {
|
|
|
5447
7609
|
TypedEventEmitter,
|
|
5448
7610
|
Updater,
|
|
5449
7611
|
VALIDATION_ACK,
|
|
7612
|
+
buildAggregationBeaconTx,
|
|
7613
|
+
buildRequestAuth,
|
|
5450
7614
|
createAggregatedNonceMessage,
|
|
5451
7615
|
createAuthorizationRequestMessage,
|
|
5452
7616
|
createCohortAdvertMessage,
|
|
@@ -5458,8 +7622,34 @@ var DidDocumentBuilder = class {
|
|
|
5458
7622
|
createSignatureAuthorizationMessage,
|
|
5459
7623
|
createSubmitUpdateMessage,
|
|
5460
7624
|
createValidationAckMessage,
|
|
7625
|
+
defaultReconnectBackoff,
|
|
7626
|
+
deriveSingletonAddress,
|
|
7627
|
+
detectSingletonScriptKind,
|
|
7628
|
+
formatSseComment,
|
|
7629
|
+
formatSseEvent,
|
|
7630
|
+
getBeaconStrategy,
|
|
7631
|
+
isAggregatedNonceMessage,
|
|
5461
7632
|
isAggregationMessageType,
|
|
7633
|
+
isAuthorizationRequestMessage,
|
|
7634
|
+
isCohortAdvertMessage,
|
|
7635
|
+
isCohortOptInAcceptMessage,
|
|
7636
|
+
isCohortOptInMessage,
|
|
7637
|
+
isCohortReadyMessage,
|
|
7638
|
+
isDistributeAggregatedDataMessage,
|
|
5462
7639
|
isKeygenMessageType,
|
|
7640
|
+
isNonceContributionMessage,
|
|
5463
7641
|
isSignMessageType,
|
|
5464
|
-
|
|
7642
|
+
isSignatureAuthorizationMessage,
|
|
7643
|
+
isSubmitUpdateMessage,
|
|
7644
|
+
isUpdateMessageType,
|
|
7645
|
+
isValidationAckMessage,
|
|
7646
|
+
normalizeForWire,
|
|
7647
|
+
opReturnScript,
|
|
7648
|
+
parseRequestAuth,
|
|
7649
|
+
parseSseStream,
|
|
7650
|
+
registerBeaconStrategy,
|
|
7651
|
+
reviveFromWire,
|
|
7652
|
+
signEnvelope,
|
|
7653
|
+
verifyEnvelope,
|
|
7654
|
+
verifyRequestAuth
|
|
5465
7655
|
});
|