@did-btcr2/method 0.29.0 → 0.33.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 +36 -16
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +6763 -6170
- package/dist/browser.mjs +6763 -6170
- package/dist/cjs/index.js +1860 -467
- package/dist/esm/core/aggregation/beacon-strategy.js +5 -4
- package/dist/esm/core/aggregation/beacon-strategy.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/beacon/beacon.js +197 -51
- 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 +22 -14
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/resolver.js +7 -4
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/updater.js +63 -55
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.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/transport.d.ts +1 -1
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +72 -12
- 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/interfaces.d.ts +14 -11
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolver.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/package.json +20 -8
- package/src/core/aggregation/beacon-strategy.ts +5 -4
- 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/transport.ts +1 -1
- package/src/core/beacon/beacon.ts +255 -64
- 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 +24 -16
- package/src/core/interfaces.ts +14 -11
- package/src/core/resolver.ts +8 -5
- package/src/core/updater.ts +113 -67
package/dist/cjs/index.js
CHANGED
|
@@ -62,7 +62,9 @@ __export(index_exports, {
|
|
|
62
62
|
CONSOLE_LOGGER: () => CONSOLE_LOGGER,
|
|
63
63
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
64
64
|
DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
65
|
+
DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
|
|
65
66
|
DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
67
|
+
DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
|
|
66
68
|
DEFAULT_NOSTR_RELAYS: () => DEFAULT_NOSTR_RELAYS,
|
|
67
69
|
DID_REGEX: () => DID_REGEX,
|
|
68
70
|
DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
|
|
@@ -73,16 +75,31 @@ __export(index_exports, {
|
|
|
73
75
|
DidVerificationMethod: () => DidVerificationMethod,
|
|
74
76
|
Document: () => Document,
|
|
75
77
|
GenesisDocument: () => GenesisDocument,
|
|
78
|
+
HTTP_ENVELOPE_VERSION: () => HTTP_ENVELOPE_VERSION,
|
|
79
|
+
HTTP_ROUTE: () => HTTP_ROUTE,
|
|
80
|
+
HttpClientTransport: () => HttpClientTransport,
|
|
81
|
+
HttpServerTransport: () => HttpServerTransport,
|
|
82
|
+
HttpTransportError: () => HttpTransportError,
|
|
76
83
|
ID_PLACEHOLDER_VALUE: () => ID_PLACEHOLDER_VALUE,
|
|
77
84
|
Identifier: () => Identifier,
|
|
85
|
+
InMemoryRateLimitStore: () => InMemoryRateLimitStore,
|
|
86
|
+
InboxBuffer: () => InboxBuffer,
|
|
78
87
|
NONCE_CONTRIBUTION: () => NONCE_CONTRIBUTION,
|
|
88
|
+
NonceCache: () => NonceCache,
|
|
79
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,
|
|
80
93
|
ParticipantCohortPhase: () => ParticipantCohortPhase,
|
|
94
|
+
REQUEST_AUTH_SCHEME: () => REQUEST_AUTH_SCHEME,
|
|
95
|
+
RateLimiter: () => RateLimiter,
|
|
81
96
|
Resolver: () => Resolver,
|
|
82
97
|
SIGNATURE_AUTHORIZATION: () => SIGNATURE_AUTHORIZATION,
|
|
83
98
|
SILENT_LOGGER: () => SILENT_LOGGER,
|
|
99
|
+
SINGLETON_BEACON_TX_VSIZE: () => SINGLETON_BEACON_TX_VSIZE,
|
|
84
100
|
SMTBeacon: () => SMTBeacon,
|
|
85
101
|
SMTBeaconError: () => SMTBeaconError,
|
|
102
|
+
SSE_EVENT: () => SSE_EVENT,
|
|
86
103
|
SUBMIT_UPDATE: () => SUBMIT_UPDATE,
|
|
87
104
|
ServiceCohortPhase: () => ServiceCohortPhase,
|
|
88
105
|
SigningSessionError: () => SigningSessionError,
|
|
@@ -97,6 +114,7 @@ __export(index_exports, {
|
|
|
97
114
|
Updater: () => Updater,
|
|
98
115
|
VALIDATION_ACK: () => VALIDATION_ACK,
|
|
99
116
|
buildAggregationBeaconTx: () => buildAggregationBeaconTx,
|
|
117
|
+
buildRequestAuth: () => buildRequestAuth,
|
|
100
118
|
createAggregatedNonceMessage: () => createAggregatedNonceMessage,
|
|
101
119
|
createAuthorizationRequestMessage: () => createAuthorizationRequestMessage,
|
|
102
120
|
createCohortAdvertMessage: () => createCohortAdvertMessage,
|
|
@@ -108,6 +126,11 @@ __export(index_exports, {
|
|
|
108
126
|
createSignatureAuthorizationMessage: () => createSignatureAuthorizationMessage,
|
|
109
127
|
createSubmitUpdateMessage: () => createSubmitUpdateMessage,
|
|
110
128
|
createValidationAckMessage: () => createValidationAckMessage,
|
|
129
|
+
defaultReconnectBackoff: () => defaultReconnectBackoff,
|
|
130
|
+
deriveSingletonAddress: () => deriveSingletonAddress,
|
|
131
|
+
detectSingletonScriptKind: () => detectSingletonScriptKind,
|
|
132
|
+
formatSseComment: () => formatSseComment,
|
|
133
|
+
formatSseEvent: () => formatSseEvent,
|
|
111
134
|
getBeaconStrategy: () => getBeaconStrategy,
|
|
112
135
|
isAggregatedNonceMessage: () => isAggregatedNonceMessage,
|
|
113
136
|
isAggregationMessageType: () => isAggregationMessageType,
|
|
@@ -124,8 +147,15 @@ __export(index_exports, {
|
|
|
124
147
|
isSubmitUpdateMessage: () => isSubmitUpdateMessage,
|
|
125
148
|
isUpdateMessageType: () => isUpdateMessageType,
|
|
126
149
|
isValidationAckMessage: () => isValidationAckMessage,
|
|
150
|
+
normalizeForWire: () => normalizeForWire,
|
|
127
151
|
opReturnScript: () => opReturnScript,
|
|
128
|
-
|
|
152
|
+
parseRequestAuth: () => parseRequestAuth,
|
|
153
|
+
parseSseStream: () => parseSseStream,
|
|
154
|
+
registerBeaconStrategy: () => registerBeaconStrategy,
|
|
155
|
+
reviveFromWire: () => reviveFromWire,
|
|
156
|
+
signEnvelope: () => signEnvelope,
|
|
157
|
+
verifyEnvelope: () => verifyEnvelope,
|
|
158
|
+
verifyRequestAuth: () => verifyRequestAuth
|
|
129
159
|
});
|
|
130
160
|
module.exports = __toCommonJS(index_exports);
|
|
131
161
|
|
|
@@ -167,12 +197,12 @@ var SMT_STRATEGY = {
|
|
|
167
197
|
const smtProof = body.smtProof;
|
|
168
198
|
if (!smtProof?.updateId || !smtProof?.nonce) return { matches: false };
|
|
169
199
|
const canonicalBytes = new TextEncoder().encode((0, import_common.canonicalize)(submittedUpdate));
|
|
170
|
-
const expectedUpdateId = (0, import_smt.
|
|
200
|
+
const expectedUpdateId = (0, import_smt.hashToBase64Url)((0, import_smt.blockHash)(canonicalBytes));
|
|
171
201
|
if (smtProof.updateId !== expectedUpdateId) {
|
|
172
202
|
return { matches: false, smtProof };
|
|
173
203
|
}
|
|
174
204
|
const index = (0, import_smt.didToIndex)(participantDid);
|
|
175
|
-
const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.
|
|
205
|
+
const candidateHash = (0, import_smt.blockHash)((0, import_smt.blockHash)((0, import_smt.base64UrlToHash)(smtProof.nonce)), (0, import_smt.base64UrlToHash)(smtProof.updateId));
|
|
176
206
|
return {
|
|
177
207
|
matches: (0, import_smt.verifySerializedProof)(smtProof, index, candidateHash),
|
|
178
208
|
smtProof
|
|
@@ -1751,11 +1781,1392 @@ var TransportAdapterError = class extends import_common6.MethodError {
|
|
|
1751
1781
|
};
|
|
1752
1782
|
|
|
1753
1783
|
// src/core/aggregation/transport/factory.ts
|
|
1784
|
+
var import_common10 = require("@did-btcr2/common");
|
|
1785
|
+
|
|
1786
|
+
// src/core/aggregation/transport/http/client.ts
|
|
1787
|
+
var import_keypair2 = require("@did-btcr2/keypair");
|
|
1788
|
+
|
|
1789
|
+
// src/core/identifier.ts
|
|
1754
1790
|
var import_common7 = require("@did-btcr2/common");
|
|
1791
|
+
var import_keypair = require("@did-btcr2/keypair");
|
|
1792
|
+
var import_base4 = require("@scure/base");
|
|
1793
|
+
var Identifier = class _Identifier {
|
|
1794
|
+
/**
|
|
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.
|
|
1807
|
+
*/
|
|
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 });
|
|
1812
|
+
}
|
|
1813
|
+
if (isNaN(version) || version > 1) {
|
|
1814
|
+
throw new import_common7.IdentifierError('Expected "version" to be 1', import_common7.INVALID_DID, { version });
|
|
1815
|
+
}
|
|
1816
|
+
if (typeof network === "string" && !(network in import_common7.BitcoinNetworkNames)) {
|
|
1817
|
+
throw new import_common7.IdentifierError('Invalid "network" name', import_common7.INVALID_DID, { network });
|
|
1818
|
+
}
|
|
1819
|
+
if (typeof network === "number" && (network < 0 || network > 8)) {
|
|
1820
|
+
throw new import_common7.IdentifierError('Invalid "network" number', import_common7.INVALID_DID, { network });
|
|
1821
|
+
}
|
|
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
|
+
}
|
|
1832
|
+
}
|
|
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
|
+
}
|
|
1755
3166
|
|
|
1756
3167
|
// src/core/aggregation/transport/nostr.ts
|
|
1757
|
-
var
|
|
1758
|
-
var
|
|
3168
|
+
var import_keypair4 = require("@did-btcr2/keypair");
|
|
3169
|
+
var import_utils6 = require("@noble/hashes/utils");
|
|
1759
3170
|
var import_nostr_tools = require("nostr-tools");
|
|
1760
3171
|
var import_pool = require("nostr-tools/pool");
|
|
1761
3172
|
var DEFAULT_NOSTR_RELAYS = [
|
|
@@ -1852,7 +3263,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
1852
3263
|
*/
|
|
1853
3264
|
registerPeer(did, communicationPk) {
|
|
1854
3265
|
try {
|
|
1855
|
-
new
|
|
3266
|
+
new import_keypair4.CompressedSecp256k1PublicKey(communicationPk);
|
|
1856
3267
|
} catch {
|
|
1857
3268
|
throw new TransportAdapterError(
|
|
1858
3269
|
`Invalid communication public key for peer ${did}: expected a 33-byte compressed secp256k1 key.`,
|
|
@@ -1953,14 +3364,14 @@ var NostrTransport = class _NostrTransport {
|
|
|
1953
3364
|
}
|
|
1954
3365
|
const senderKeys = actor.keys;
|
|
1955
3366
|
const tags = [
|
|
1956
|
-
["p", (0,
|
|
3367
|
+
["p", (0, import_utils6.bytesToHex)(senderKeys.publicKey.x)],
|
|
1957
3368
|
["t", type]
|
|
1958
3369
|
];
|
|
1959
3370
|
if (to) {
|
|
1960
3371
|
const recipientPkBytes = this.#peerRegistry.get(to);
|
|
1961
3372
|
if (recipientPkBytes) {
|
|
1962
|
-
const recipientPk = new
|
|
1963
|
-
tags.push(["p", (0,
|
|
3373
|
+
const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
3374
|
+
tags.push(["p", (0, import_utils6.bytesToHex)(recipientPk.x)]);
|
|
1964
3375
|
}
|
|
1965
3376
|
}
|
|
1966
3377
|
if (isKeygenMessageType(type)) {
|
|
@@ -1990,10 +3401,10 @@ var NostrTransport = class _NostrTransport {
|
|
|
1990
3401
|
{ adapter: this.name, did: to }
|
|
1991
3402
|
);
|
|
1992
3403
|
}
|
|
1993
|
-
const recipientPk = new
|
|
3404
|
+
const recipientPk = new import_keypair4.CompressedSecp256k1PublicKey(recipientPkBytes);
|
|
1994
3405
|
const conversationKey = import_nostr_tools.nip44.v2.utils.getConversationKey(
|
|
1995
3406
|
senderKeys.secretKey.bytes,
|
|
1996
|
-
(0,
|
|
3407
|
+
(0, import_utils6.bytesToHex)(recipientPk.x)
|
|
1997
3408
|
);
|
|
1998
3409
|
const content = import_nostr_tools.nip44.v2.encrypt(JSON.stringify(message, _NostrTransport.#jsonReplacer), conversationKey);
|
|
1999
3410
|
const event = (0, import_nostr_tools.finalizeEvent)({
|
|
@@ -2047,7 +3458,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
2047
3458
|
*/
|
|
2048
3459
|
#subscribeDirected(did, entry) {
|
|
2049
3460
|
if (!this.pool) return;
|
|
2050
|
-
const pkHex = (0,
|
|
3461
|
+
const pkHex = (0, import_utils6.bytesToHex)(entry.keys.publicKey.x);
|
|
2051
3462
|
const sub = this.pool.subscribeMany(this.#relays, { kinds: [1, 1059], "#p": [pkHex] }, {
|
|
2052
3463
|
onclose: (reasons) => this.#logger.debug(`Nostr directed subscription closed for ${did}`, reasons),
|
|
2053
3464
|
onevent: this.#makeActorEventHandler(did)
|
|
@@ -2065,7 +3476,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
2065
3476
|
return async (event) => {
|
|
2066
3477
|
const actor = this.#actors.get(actorDid);
|
|
2067
3478
|
if (!actor) return;
|
|
2068
|
-
if (event.pubkey === (0,
|
|
3479
|
+
if (event.pubkey === (0, import_utils6.bytesToHex)(actor.keys.publicKey.x)) return;
|
|
2069
3480
|
let message;
|
|
2070
3481
|
try {
|
|
2071
3482
|
if (event.kind === 1) {
|
|
@@ -2178,7 +3589,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
2178
3589
|
*/
|
|
2179
3590
|
static #jsonReplacer(_key, value) {
|
|
2180
3591
|
if (value instanceof Uint8Array) {
|
|
2181
|
-
return { __bytes: (0,
|
|
3592
|
+
return { __bytes: (0, import_utils6.bytesToHex)(value) };
|
|
2182
3593
|
}
|
|
2183
3594
|
return value;
|
|
2184
3595
|
}
|
|
@@ -2196,7 +3607,7 @@ var NostrTransport = class _NostrTransport {
|
|
|
2196
3607
|
*/
|
|
2197
3608
|
static #jsonReviver(_key, value) {
|
|
2198
3609
|
if (value && typeof value === "object" && "__bytes" in value) {
|
|
2199
|
-
return (0,
|
|
3610
|
+
return (0, import_utils6.hexToBytes)(value.__bytes);
|
|
2200
3611
|
}
|
|
2201
3612
|
return value;
|
|
2202
3613
|
}
|
|
@@ -2207,9 +3618,19 @@ var TransportFactory = class {
|
|
|
2207
3618
|
static establish(config) {
|
|
2208
3619
|
switch (config.type) {
|
|
2209
3620
|
case "nostr":
|
|
2210
|
-
return new NostrTransport({
|
|
3621
|
+
return new NostrTransport({
|
|
3622
|
+
relays: config.relays,
|
|
3623
|
+
logger: config.logger,
|
|
3624
|
+
broadcastLookbackMs: config.broadcastLookbackMs
|
|
3625
|
+
});
|
|
2211
3626
|
case "didcomm":
|
|
2212
|
-
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
|
+
);
|
|
2213
3634
|
default:
|
|
2214
3635
|
throw new TransportError(
|
|
2215
3636
|
`Invalid transport type: ${config.type}`,
|
|
@@ -2221,41 +3642,58 @@ var TransportFactory = class {
|
|
|
2221
3642
|
};
|
|
2222
3643
|
|
|
2223
3644
|
// src/core/aggregation/transport/didcomm.ts
|
|
2224
|
-
var
|
|
3645
|
+
var import_common11 = require("@did-btcr2/common");
|
|
2225
3646
|
var DidCommTransport = class {
|
|
2226
3647
|
name = "didcomm";
|
|
2227
3648
|
start() {
|
|
2228
|
-
throw new
|
|
3649
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented. Use NostrTransport instead.");
|
|
2229
3650
|
}
|
|
2230
3651
|
registerActor(_did, _keys) {
|
|
2231
|
-
throw new
|
|
3652
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2232
3653
|
}
|
|
2233
3654
|
getActorPk(_did) {
|
|
2234
|
-
throw new
|
|
3655
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2235
3656
|
}
|
|
2236
3657
|
registerPeer(_did, _communicationPk) {
|
|
2237
|
-
throw new
|
|
3658
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2238
3659
|
}
|
|
2239
3660
|
getPeerPk(_did) {
|
|
2240
|
-
throw new
|
|
3661
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2241
3662
|
}
|
|
2242
3663
|
registerMessageHandler(_actorDid, _messageType, _handler) {
|
|
2243
|
-
throw new
|
|
3664
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2244
3665
|
}
|
|
2245
3666
|
unregisterMessageHandler(_actorDid, _messageType) {
|
|
2246
|
-
throw new
|
|
3667
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2247
3668
|
}
|
|
2248
3669
|
unregisterActor(_did) {
|
|
2249
|
-
throw new
|
|
3670
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2250
3671
|
}
|
|
2251
3672
|
async sendMessage(_message, _sender, _recipient) {
|
|
2252
|
-
throw new
|
|
3673
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2253
3674
|
}
|
|
2254
3675
|
publishRepeating(_message, _sender, _intervalMs, _recipient) {
|
|
2255
|
-
throw new
|
|
3676
|
+
throw new import_common11.NotImplementedError("DidCommTransport not implemented.");
|
|
2256
3677
|
}
|
|
2257
3678
|
};
|
|
2258
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
|
+
|
|
2259
3697
|
// src/core/aggregation/runner/typed-emitter.ts
|
|
2260
3698
|
var TypedEventEmitter = class {
|
|
2261
3699
|
#listeners = /* @__PURE__ */ new Map();
|
|
@@ -2931,33 +4369,32 @@ var AggregationParticipantRunner = class _AggregationParticipantRunner extends T
|
|
|
2931
4369
|
};
|
|
2932
4370
|
|
|
2933
4371
|
// src/core/beacon/beacon.ts
|
|
2934
|
-
var
|
|
2935
|
-
var import_utils5 = require("@noble/hashes/utils");
|
|
4372
|
+
var import_utils7 = require("@noble/hashes/utils.js");
|
|
2936
4373
|
var import_btc_signer4 = require("@scure/btc-signer");
|
|
2937
4374
|
|
|
2938
4375
|
// src/core/beacon/error.ts
|
|
2939
|
-
var
|
|
2940
|
-
var BeaconError = class extends
|
|
4376
|
+
var import_common12 = require("@did-btcr2/common");
|
|
4377
|
+
var BeaconError = class extends import_common12.MethodError {
|
|
2941
4378
|
constructor(message, type = "BeaconError", data) {
|
|
2942
4379
|
super(message, type, data);
|
|
2943
4380
|
}
|
|
2944
4381
|
};
|
|
2945
|
-
var SingletonBeaconError = class extends
|
|
4382
|
+
var SingletonBeaconError = class extends import_common12.MethodError {
|
|
2946
4383
|
constructor(message, type = "SingletonBeaconError", data) {
|
|
2947
4384
|
super(message, type, data);
|
|
2948
4385
|
}
|
|
2949
4386
|
};
|
|
2950
|
-
var AggregateBeaconError = class extends
|
|
4387
|
+
var AggregateBeaconError = class extends import_common12.MethodError {
|
|
2951
4388
|
constructor(message, type = "AggregateBeaconError", data) {
|
|
2952
4389
|
super(message, type, data);
|
|
2953
4390
|
}
|
|
2954
4391
|
};
|
|
2955
|
-
var CASBeaconError = class extends
|
|
4392
|
+
var CASBeaconError = class extends import_common12.MethodError {
|
|
2956
4393
|
constructor(message, type = "CASBeaconError", data) {
|
|
2957
4394
|
super(message, type, data);
|
|
2958
4395
|
}
|
|
2959
4396
|
};
|
|
2960
|
-
var SMTBeaconError = class extends
|
|
4397
|
+
var SMTBeaconError = class extends import_common12.MethodError {
|
|
2961
4398
|
constructor(message, type = "SMTBeaconError", data) {
|
|
2962
4399
|
super(message, type, data);
|
|
2963
4400
|
}
|
|
@@ -2985,9 +4422,32 @@ var StaticFeeEstimator = class {
|
|
|
2985
4422
|
|
|
2986
4423
|
// src/core/beacon/beacon.ts
|
|
2987
4424
|
var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
|
|
2988
|
-
var P2TR_BEACON_TX_VSIZE =
|
|
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
|
+
}
|
|
2989
4449
|
function opReturnScript(signalBytes) {
|
|
2990
|
-
return import_btc_signer4.Script.encode([
|
|
4450
|
+
return import_btc_signer4.Script.encode(["RETURN", signalBytes]);
|
|
2991
4451
|
}
|
|
2992
4452
|
async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
2993
4453
|
const utxos = await bitcoin.rest.address.getUtxos(bitcoinAddress);
|
|
@@ -2995,7 +4455,7 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
|
2995
4455
|
throw new BeaconError(
|
|
2996
4456
|
"No UTXOs found, please fund address!",
|
|
2997
4457
|
"UNFUNDED_BEACON_ADDRESS",
|
|
2998
|
-
{ bitcoinAddress }
|
|
4458
|
+
{ address: bitcoinAddress }
|
|
2999
4459
|
);
|
|
3000
4460
|
}
|
|
3001
4461
|
const utxo = utxos.sort((a, b) => b.status.block_height - a.status.block_height).shift();
|
|
@@ -3003,11 +4463,11 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
|
|
|
3003
4463
|
throw new BeaconError(
|
|
3004
4464
|
"Beacon bitcoin address unfunded or utxos unconfirmed.",
|
|
3005
4465
|
"UNFUNDED_BEACON_ADDRESS",
|
|
3006
|
-
{ bitcoinAddress }
|
|
4466
|
+
{ address: bitcoinAddress }
|
|
3007
4467
|
);
|
|
3008
4468
|
}
|
|
3009
4469
|
const prevTxHex = await bitcoin.rest.transaction.getHex(utxo.txid);
|
|
3010
|
-
return { utxo, prevTxBytes: (0,
|
|
4470
|
+
return { utxo, prevTxBytes: (0, import_utils7.hexToBytes)(prevTxHex) };
|
|
3011
4471
|
}
|
|
3012
4472
|
async function buildAggregationBeaconTx(opts) {
|
|
3013
4473
|
const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
@@ -3019,10 +4479,10 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
3019
4479
|
throw new BeaconError(
|
|
3020
4480
|
`UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
3021
4481
|
"INSUFFICIENT_FUNDS",
|
|
3022
|
-
{
|
|
4482
|
+
{ address: opts.beaconAddress, valueSats: utxo.value, feeSats }
|
|
3023
4483
|
);
|
|
3024
4484
|
}
|
|
3025
|
-
const tx = new import_btc_signer4.Transaction();
|
|
4485
|
+
const tx = new import_btc_signer4.Transaction({ allowUnknownOutputs: true });
|
|
3026
4486
|
tx.addInput({
|
|
3027
4487
|
txid: utxo.txid,
|
|
3028
4488
|
index: utxo.vout,
|
|
@@ -3038,9 +4498,45 @@ async function buildAggregationBeaconTx(opts) {
|
|
|
3038
4498
|
prevOutValues: [BigInt(utxo.value)],
|
|
3039
4499
|
beaconAddress: opts.beaconAddress,
|
|
3040
4500
|
utxo,
|
|
3041
|
-
feeSats
|
|
4501
|
+
feeSats,
|
|
4502
|
+
scriptKind: "p2tr"
|
|
3042
4503
|
};
|
|
3043
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
|
+
}
|
|
3044
4540
|
var Beacon = class {
|
|
3045
4541
|
/**
|
|
3046
4542
|
* The Beacon service configuration parsed from the DID Document.
|
|
@@ -3050,7 +4546,9 @@ var Beacon = class {
|
|
|
3050
4546
|
this.service = service;
|
|
3051
4547
|
}
|
|
3052
4548
|
/**
|
|
3053
|
-
* Build + sign + broadcast a
|
|
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.
|
|
3054
4552
|
*
|
|
3055
4553
|
* Composed from the three extracted phases ({@link buildSinglePartyTx},
|
|
3056
4554
|
* {@link signSinglePartyTx}, {@link broadcastRawTx}) so each piece can be exercised
|
|
@@ -3059,13 +4557,13 @@ var Beacon = class {
|
|
|
3059
4557
|
* plumbing (UTXO fetch + OP_RETURN output + change output) is shared.
|
|
3060
4558
|
*
|
|
3061
4559
|
* @param signalBytes 32-byte payload to embed in OP_RETURN.
|
|
3062
|
-
* @param
|
|
4560
|
+
* @param signer Signer used to sign the spending input.
|
|
3063
4561
|
* @param bitcoin Bitcoin network connection.
|
|
3064
4562
|
* @param options Broadcast options (fee estimator, etc.).
|
|
3065
4563
|
* @returns The txid of the broadcast transaction.
|
|
3066
4564
|
* @throws {BeaconError} if the address is unfunded, no UTXO is available, or fee exceeds value.
|
|
3067
4565
|
*/
|
|
3068
|
-
async buildSignAndBroadcast(signalBytes,
|
|
4566
|
+
async buildSignAndBroadcast(signalBytes, signer, bitcoin, options) {
|
|
3069
4567
|
const feeEstimator = options?.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
|
|
3070
4568
|
const beaconAddress = this.service.serviceEndpoint.replace("bitcoin:", "");
|
|
3071
4569
|
const { utxo, prevTxBytes } = await fetchSpendableUtxo(beaconAddress, bitcoin);
|
|
@@ -3074,69 +4572,97 @@ var Beacon = class {
|
|
|
3074
4572
|
beaconAddress,
|
|
3075
4573
|
utxo,
|
|
3076
4574
|
prevTxBytes,
|
|
3077
|
-
|
|
4575
|
+
signer,
|
|
3078
4576
|
bitcoin,
|
|
3079
4577
|
feeEstimator
|
|
3080
4578
|
});
|
|
3081
|
-
const signedHex = this.signSinglePartyTx(plan
|
|
4579
|
+
const signedHex = await this.signSinglePartyTx(plan, signer);
|
|
3082
4580
|
return this.broadcastRawTx(bitcoin, signedHex);
|
|
3083
4581
|
}
|
|
3084
4582
|
/**
|
|
3085
|
-
* Build an unsigned
|
|
3086
|
-
* then rebuild with the real fee. Returns the tx and prev-output metadata.
|
|
4583
|
+
* Build an unsigned singleton beacon tx ready for {@link signSinglePartyTx}.
|
|
3087
4584
|
*
|
|
3088
|
-
*
|
|
3089
|
-
*
|
|
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.
|
|
3090
4591
|
*/
|
|
3091
4592
|
async buildSinglePartyTx(opts) {
|
|
3092
|
-
const
|
|
3093
|
-
const
|
|
3094
|
-
const
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
witnessUtxo: { amount: BigInt(opts.utxo.value), script: witnessScript }
|
|
3102
|
-
});
|
|
3103
|
-
tx2.addOutputAddress(
|
|
3104
|
-
opts.beaconAddress,
|
|
3105
|
-
BigInt(opts.utxo.value) - feeSats2,
|
|
3106
|
-
opts.bitcoin.data
|
|
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) {
|
|
4598
|
+
throw new BeaconError(
|
|
4599
|
+
`Signer pubkey produces ${kind.toUpperCase()} address "${derivedAddress}", but beacon address is "${opts.beaconAddress}".`,
|
|
4600
|
+
"SIGNER_KEY_MISMATCH",
|
|
4601
|
+
{ kind, address: opts.beaconAddress, derivedAddress }
|
|
3107
4602
|
);
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
probe.signIdx(opts.secretKey, 0);
|
|
3113
|
-
probe.finalize();
|
|
3114
|
-
const vsize = probe.vsize;
|
|
3115
|
-
const feeSats = await opts.feeEstimator.estimateFee(vsize);
|
|
3116
|
-
if (BigInt(opts.utxo.value) <= feeSats) {
|
|
4603
|
+
}
|
|
4604
|
+
const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
|
|
4605
|
+
const amount = BigInt(opts.utxo.value);
|
|
4606
|
+
if (amount <= feeSats) {
|
|
3117
4607
|
throw new BeaconError(
|
|
3118
4608
|
`UTXO value (${opts.utxo.value}) insufficient to cover fee (${feeSats}).`,
|
|
3119
4609
|
"INSUFFICIENT_FUNDS",
|
|
3120
|
-
{
|
|
4610
|
+
{ address: opts.beaconAddress, valueSats: opts.utxo.value, feeSats }
|
|
3121
4611
|
);
|
|
3122
4612
|
}
|
|
3123
|
-
const tx =
|
|
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 });
|
|
3124
4643
|
return {
|
|
3125
4644
|
tx,
|
|
3126
|
-
prevOutScripts: [
|
|
3127
|
-
prevOutValues: [
|
|
4645
|
+
prevOutScripts: [prevOutScript],
|
|
4646
|
+
prevOutValues: [amount],
|
|
3128
4647
|
beaconAddress: opts.beaconAddress,
|
|
3129
4648
|
utxo: opts.utxo,
|
|
3130
|
-
feeSats
|
|
4649
|
+
feeSats,
|
|
4650
|
+
scriptKind: kind
|
|
3131
4651
|
};
|
|
3132
4652
|
}
|
|
3133
4653
|
/**
|
|
3134
4654
|
* Sign + finalize the unsigned single-party tx and return its raw hex.
|
|
4655
|
+
* Dispatches to the correct signing primitive based on `plan.scriptKind`.
|
|
3135
4656
|
*/
|
|
3136
|
-
signSinglePartyTx(
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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
|
+
);
|
|
3140
4666
|
}
|
|
3141
4667
|
/**
|
|
3142
4668
|
* Broadcast raw transaction hex via the Bitcoin REST endpoint. Returns the txid.
|
|
@@ -3144,14 +4670,10 @@ var Beacon = class {
|
|
|
3144
4670
|
async broadcastRawTx(bitcoin, rawHex) {
|
|
3145
4671
|
return bitcoin.rest.transaction.send(rawHex);
|
|
3146
4672
|
}
|
|
3147
|
-
/** Derive the compressed secp256k1 public key from a raw secret key. */
|
|
3148
|
-
#derivePubkey(secretKey) {
|
|
3149
|
-
return (0, import_secp256k12.getPublicKey)(secretKey, true);
|
|
3150
|
-
}
|
|
3151
4673
|
};
|
|
3152
4674
|
|
|
3153
4675
|
// src/core/beacon/cas-beacon.ts
|
|
3154
|
-
var
|
|
4676
|
+
var import_common13 = require("@did-btcr2/common");
|
|
3155
4677
|
var CASBeacon = class extends Beacon {
|
|
3156
4678
|
/**
|
|
3157
4679
|
* Creates an instance of CASBeacon.
|
|
@@ -3192,7 +4714,7 @@ var CASBeacon = class extends Beacon {
|
|
|
3192
4714
|
if (!updateHashEncoded) {
|
|
3193
4715
|
continue;
|
|
3194
4716
|
}
|
|
3195
|
-
const updateHash = (0,
|
|
4717
|
+
const updateHash = (0, import_common13.encode)((0, import_common13.decode)(updateHashEncoded, "base64urlnopad"), "hex");
|
|
3196
4718
|
const signedUpdate = sidecar.updateMap.get(updateHash);
|
|
3197
4719
|
if (!signedUpdate) {
|
|
3198
4720
|
needs.push({
|
|
@@ -3215,19 +4737,19 @@ var CASBeacon = class extends Beacon {
|
|
|
3215
4737
|
* and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
3216
4738
|
*
|
|
3217
4739
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
3218
|
-
* @param {
|
|
4740
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
3219
4741
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
3220
4742
|
* @param {CASBroadcastOptions} [options] Optional broadcast configuration, including a
|
|
3221
4743
|
* `casPublish` callback to publish the announcement off-chain and a `feeEstimator`.
|
|
3222
4744
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
3223
4745
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
3224
4746
|
*/
|
|
3225
|
-
async broadcastSignal(signedUpdate,
|
|
4747
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
3226
4748
|
const did = this.service.id.split("#")[0];
|
|
3227
|
-
const updateHash = (0,
|
|
4749
|
+
const updateHash = (0, import_common13.canonicalHash)(signedUpdate);
|
|
3228
4750
|
const casAnnouncement = { [did]: updateHash };
|
|
3229
|
-
const announcementHash = (0,
|
|
3230
|
-
await this.buildSignAndBroadcast(announcementHash,
|
|
4751
|
+
const announcementHash = (0, import_common13.hash)((0, import_common13.canonicalize)(casAnnouncement));
|
|
4752
|
+
await this.buildSignAndBroadcast(announcementHash, signer, bitcoin, options);
|
|
3231
4753
|
if (options?.casPublish) {
|
|
3232
4754
|
await options.casPublish(casAnnouncement);
|
|
3233
4755
|
}
|
|
@@ -3236,10 +4758,10 @@ var CASBeacon = class extends Beacon {
|
|
|
3236
4758
|
};
|
|
3237
4759
|
|
|
3238
4760
|
// src/core/beacon/factory.ts
|
|
3239
|
-
var
|
|
4761
|
+
var import_common16 = require("@did-btcr2/common");
|
|
3240
4762
|
|
|
3241
4763
|
// src/core/beacon/singleton-beacon.ts
|
|
3242
|
-
var
|
|
4764
|
+
var import_common14 = require("@did-btcr2/common");
|
|
3243
4765
|
var SingletonBeacon = class extends Beacon {
|
|
3244
4766
|
/**
|
|
3245
4767
|
* Creates an instance of SingletonBeacon.
|
|
@@ -3280,23 +4802,23 @@ var SingletonBeacon = class extends Beacon {
|
|
|
3280
4802
|
* {@link Beacon.buildSignAndBroadcast}.
|
|
3281
4803
|
*
|
|
3282
4804
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
3283
|
-
* @param {
|
|
4805
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
3284
4806
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
3285
4807
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
3286
4808
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
3287
4809
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
3288
4810
|
*/
|
|
3289
|
-
async broadcastSignal(signedUpdate,
|
|
3290
|
-
const signalBytes = (0,
|
|
3291
|
-
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);
|
|
3292
4814
|
return signedUpdate;
|
|
3293
4815
|
}
|
|
3294
4816
|
};
|
|
3295
4817
|
|
|
3296
4818
|
// src/core/beacon/smt-beacon.ts
|
|
3297
|
-
var
|
|
4819
|
+
var import_common15 = require("@did-btcr2/common");
|
|
3298
4820
|
var import_smt3 = require("@did-btcr2/smt");
|
|
3299
|
-
var
|
|
4821
|
+
var import_utils8 = require("@noble/hashes/utils");
|
|
3300
4822
|
var SMTBeacon = class extends Beacon {
|
|
3301
4823
|
/**
|
|
3302
4824
|
* Creates an instance of SMTBeacon.
|
|
@@ -3332,9 +4854,6 @@ var SMTBeacon = class extends Beacon {
|
|
|
3332
4854
|
});
|
|
3333
4855
|
continue;
|
|
3334
4856
|
}
|
|
3335
|
-
if (!smtProof.updateId) {
|
|
3336
|
-
continue;
|
|
3337
|
-
}
|
|
3338
4857
|
if (!smtProof.nonce) {
|
|
3339
4858
|
throw new SMTBeaconError(
|
|
3340
4859
|
"SMT proof missing required nonce field.",
|
|
@@ -3343,7 +4862,8 @@ var SMTBeacon = class extends Beacon {
|
|
|
3343
4862
|
);
|
|
3344
4863
|
}
|
|
3345
4864
|
const index = (0, import_smt3.didToIndex)(did);
|
|
3346
|
-
const
|
|
4865
|
+
const nonceHash = (0, import_smt3.base64UrlToHash)(smtProof.nonce);
|
|
4866
|
+
const candidateHash = smtProof.updateId ? (0, import_smt3.blockHash)((0, import_smt3.blockHash)(nonceHash), (0, import_smt3.base64UrlToHash)(smtProof.updateId)) : (0, import_smt3.blockHash)((0, import_smt3.blockHash)(nonceHash));
|
|
3347
4867
|
const valid = (0, import_smt3.verifySerializedProof)(smtProof, index, candidateHash);
|
|
3348
4868
|
if (!valid) {
|
|
3349
4869
|
throw new SMTBeaconError(
|
|
@@ -3352,11 +4872,15 @@ var SMTBeacon = class extends Beacon {
|
|
|
3352
4872
|
{ smtProof, did }
|
|
3353
4873
|
);
|
|
3354
4874
|
}
|
|
3355
|
-
|
|
4875
|
+
if (!smtProof.updateId) {
|
|
4876
|
+
continue;
|
|
4877
|
+
}
|
|
4878
|
+
const updateHashHex = (0, import_smt3.hashToHex)((0, import_smt3.base64UrlToHash)(smtProof.updateId));
|
|
4879
|
+
const signedUpdate = sidecar.updateMap.get(updateHashHex);
|
|
3356
4880
|
if (!signedUpdate) {
|
|
3357
4881
|
needs.push({
|
|
3358
4882
|
kind: "NeedSignedUpdate",
|
|
3359
|
-
updateHash:
|
|
4883
|
+
updateHash: updateHashHex,
|
|
3360
4884
|
beaconServiceId: this.service.id
|
|
3361
4885
|
});
|
|
3362
4886
|
continue;
|
|
@@ -3374,20 +4898,20 @@ var SMTBeacon = class extends Beacon {
|
|
|
3374
4898
|
* signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
3375
4899
|
*
|
|
3376
4900
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
3377
|
-
* @param {
|
|
4901
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
3378
4902
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
3379
4903
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
3380
4904
|
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
3381
4905
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
3382
4906
|
*/
|
|
3383
|
-
async broadcastSignal(signedUpdate,
|
|
4907
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
3384
4908
|
const did = this.service.id.split("#")[0];
|
|
3385
|
-
const canonicalBytes = new TextEncoder().encode((0,
|
|
3386
|
-
const nonce = (0,
|
|
4909
|
+
const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
|
|
4910
|
+
const nonce = (0, import_utils8.randomBytes)(32);
|
|
3387
4911
|
const tree = new import_smt3.BTCR2MerkleTree();
|
|
3388
4912
|
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
3389
4913
|
tree.finalize();
|
|
3390
|
-
await this.buildSignAndBroadcast(tree.rootHash,
|
|
4914
|
+
await this.buildSignAndBroadcast(tree.rootHash, signer, bitcoin, options);
|
|
3391
4915
|
return signedUpdate;
|
|
3392
4916
|
}
|
|
3393
4917
|
};
|
|
@@ -3408,18 +4932,18 @@ var BeaconFactory = class {
|
|
|
3408
4932
|
case "SMTBeacon":
|
|
3409
4933
|
return new SMTBeacon(service);
|
|
3410
4934
|
default:
|
|
3411
|
-
throw new
|
|
4935
|
+
throw new import_common16.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
|
|
3412
4936
|
}
|
|
3413
4937
|
}
|
|
3414
4938
|
};
|
|
3415
4939
|
|
|
3416
4940
|
// src/core/beacon/signal-discovery.ts
|
|
3417
4941
|
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
3418
|
-
var
|
|
4942
|
+
var import_common18 = require("@did-btcr2/common");
|
|
3419
4943
|
|
|
3420
4944
|
// src/core/beacon/utils.ts
|
|
3421
4945
|
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
3422
|
-
var
|
|
4946
|
+
var import_common17 = require("@did-btcr2/common");
|
|
3423
4947
|
var import_btc_signer5 = require("@scure/btc-signer");
|
|
3424
4948
|
|
|
3425
4949
|
// src/utils/appendix.ts
|
|
@@ -4460,198 +5984,6 @@ var Appendix = class _Appendix {
|
|
|
4460
5984
|
}
|
|
4461
5985
|
};
|
|
4462
5986
|
|
|
4463
|
-
// src/core/identifier.ts
|
|
4464
|
-
var import_common14 = require("@did-btcr2/common");
|
|
4465
|
-
var import_keypair2 = require("@did-btcr2/keypair");
|
|
4466
|
-
var import_base7 = require("@scure/base");
|
|
4467
|
-
var Identifier = class _Identifier {
|
|
4468
|
-
/**
|
|
4469
|
-
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-encoding | 3.2 did:btcr2 Identifier Encoding}.
|
|
4470
|
-
*
|
|
4471
|
-
* A did:btcr2 DID consists of a did:btcr2 prefix, followed by an id-bech32 value, which is a Bech32m encoding of:
|
|
4472
|
-
* - the specification version;
|
|
4473
|
-
* - the Bitcoin network identifier; and
|
|
4474
|
-
* - either:
|
|
4475
|
-
* - a key-value representing a secp256k1 public key; or
|
|
4476
|
-
* - a hash-value representing the hash of an initiating external DID document.
|
|
4477
|
-
*
|
|
4478
|
-
* @param {KeyBytes | DocumentBytes} genesisBytes The genesis bytes (public key or document bytes).
|
|
4479
|
-
* @param {DidCreateOptions} options The DID creation options.
|
|
4480
|
-
* @returns {string} The new did:btcr2 identifier.
|
|
4481
|
-
*/
|
|
4482
|
-
static encode(genesisBytes, options) {
|
|
4483
|
-
const { idType, version = 1, network } = options;
|
|
4484
|
-
if (!(idType in import_common14.IdentifierTypes)) {
|
|
4485
|
-
throw new import_common14.IdentifierError('Expected "idType" to be "KEY" or "EXTERNAL"', import_common14.INVALID_DID, { idType });
|
|
4486
|
-
}
|
|
4487
|
-
if (isNaN(version) || version > 1) {
|
|
4488
|
-
throw new import_common14.IdentifierError('Expected "version" to be 1', import_common14.INVALID_DID, { version });
|
|
4489
|
-
}
|
|
4490
|
-
if (typeof network === "string" && !(network in import_common14.BitcoinNetworkNames)) {
|
|
4491
|
-
throw new import_common14.IdentifierError('Invalid "network" name', import_common14.INVALID_DID, { network });
|
|
4492
|
-
}
|
|
4493
|
-
if (typeof network === "number" && (network < 0 || network > 8)) {
|
|
4494
|
-
throw new import_common14.IdentifierError('Invalid "network" number', import_common14.INVALID_DID, { network });
|
|
4495
|
-
}
|
|
4496
|
-
if (idType === "KEY") {
|
|
4497
|
-
try {
|
|
4498
|
-
new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
|
|
4499
|
-
} catch {
|
|
4500
|
-
throw new import_common14.IdentifierError(
|
|
4501
|
-
'Expected "genesisBytes" to be a valid compressed secp256k1 public key',
|
|
4502
|
-
import_common14.INVALID_DID,
|
|
4503
|
-
{ genesisBytes }
|
|
4504
|
-
);
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
const hrp = idType === "KEY" ? "k" : "x";
|
|
4508
|
-
const nibbles = [];
|
|
4509
|
-
const fCount = Math.floor((version - 1) / 15);
|
|
4510
|
-
for (let i = 0; i < fCount; i++) {
|
|
4511
|
-
nibbles.push(15);
|
|
4512
|
-
}
|
|
4513
|
-
nibbles.push((version - 1) % 15);
|
|
4514
|
-
if (typeof network === "string") {
|
|
4515
|
-
nibbles.push(import_common14.BitcoinNetworkNames[network]);
|
|
4516
|
-
} else if (typeof network === "number") {
|
|
4517
|
-
nibbles.push(network + 11);
|
|
4518
|
-
}
|
|
4519
|
-
if (nibbles.length % 2 !== 0) {
|
|
4520
|
-
nibbles.push(0);
|
|
4521
|
-
}
|
|
4522
|
-
if (fCount !== 0) {
|
|
4523
|
-
for (const index in Array.from({ length: nibbles.length / 2 - 1 })) {
|
|
4524
|
-
throw new import_common14.IdentifierError("Not implemented", "NOT_IMPLEMENTED", { index });
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
const dataBytes = new Uint8Array([nibbles[2 * 0] << 4 | nibbles[2 * 0 + 1], ...genesisBytes]);
|
|
4528
|
-
return `did:btcr2:${import_base7.bech32m.encodeFromBytes(hrp, dataBytes)}`;
|
|
4529
|
-
}
|
|
4530
|
-
/**
|
|
4531
|
-
* Implements {@link https://dcdpr.github.io/did-btcr2/#didbtcr2-identifier-decoding | 3.3 did:btcr2 Identifier Decoding}.
|
|
4532
|
-
* @param {string} identifier The BTCR2 DID to be parsed
|
|
4533
|
-
* @returns {DidComponents} The parsed identifier components. See {@link DidComponents} for details.
|
|
4534
|
-
* @throws {DidError} if an error occurs while parsing the identifier
|
|
4535
|
-
* @throws {DidErrorCode.InvalidDid} if identifier is invalid
|
|
4536
|
-
* @throws {DidErrorCode.MethodNotSupported} if the method is not supported
|
|
4537
|
-
*/
|
|
4538
|
-
static decode(identifier) {
|
|
4539
|
-
const components = identifier.split(":");
|
|
4540
|
-
if (components.length !== 3) {
|
|
4541
|
-
throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
|
|
4542
|
-
}
|
|
4543
|
-
const [scheme, method, encoded] = components;
|
|
4544
|
-
if (scheme !== "did") {
|
|
4545
|
-
throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
|
|
4546
|
-
}
|
|
4547
|
-
if (method !== "btcr2") {
|
|
4548
|
-
throw new import_common14.IdentifierError(`Invalid did method: ${method}`, import_common14.METHOD_NOT_SUPPORTED, { identifier });
|
|
4549
|
-
}
|
|
4550
|
-
if (!encoded) {
|
|
4551
|
-
throw new import_common14.IdentifierError(`Invalid method-specific id: ${identifier}`, import_common14.INVALID_DID, { identifier });
|
|
4552
|
-
}
|
|
4553
|
-
const { prefix: hrp, bytes: dataBytes } = import_base7.bech32m.decodeToBytes(encoded);
|
|
4554
|
-
if (!["x", "k"].includes(hrp)) {
|
|
4555
|
-
throw new import_common14.IdentifierError(`Invalid hrp: ${hrp}`, import_common14.INVALID_DID, { identifier });
|
|
4556
|
-
}
|
|
4557
|
-
if (!dataBytes) {
|
|
4558
|
-
throw new import_common14.IdentifierError(`Failed to decode id: ${encoded}`, import_common14.INVALID_DID, { identifier });
|
|
4559
|
-
}
|
|
4560
|
-
const idType = hrp === "k" ? "KEY" : "EXTERNAL";
|
|
4561
|
-
let version = 1;
|
|
4562
|
-
let byteIndex = 0;
|
|
4563
|
-
let nibblesConsumed = 0;
|
|
4564
|
-
let currentByte = dataBytes[byteIndex];
|
|
4565
|
-
let versionNibble = currentByte >>> 4;
|
|
4566
|
-
while (versionNibble === 15) {
|
|
4567
|
-
version += 15;
|
|
4568
|
-
if (nibblesConsumed % 2 === 0) {
|
|
4569
|
-
versionNibble = currentByte & 15;
|
|
4570
|
-
} else {
|
|
4571
|
-
currentByte = dataBytes[++byteIndex];
|
|
4572
|
-
versionNibble = currentByte >>> 4;
|
|
4573
|
-
}
|
|
4574
|
-
nibblesConsumed += 1;
|
|
4575
|
-
if (version > 1) {
|
|
4576
|
-
throw new import_common14.IdentifierError(`Invalid version: ${version}`, import_common14.INVALID_DID, { identifier });
|
|
4577
|
-
}
|
|
4578
|
-
}
|
|
4579
|
-
version += versionNibble;
|
|
4580
|
-
nibblesConsumed += 1;
|
|
4581
|
-
let networkValue = nibblesConsumed % 2 === 0 ? dataBytes[++byteIndex] >>> 4 : currentByte & 15;
|
|
4582
|
-
nibblesConsumed += 1;
|
|
4583
|
-
let network = import_common14.BitcoinNetworkNames[networkValue];
|
|
4584
|
-
if (!network) {
|
|
4585
|
-
if (networkValue >= 8 && networkValue <= 15) {
|
|
4586
|
-
network = networkValue - 11;
|
|
4587
|
-
} else {
|
|
4588
|
-
throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
if (nibblesConsumed % 2 === 1) {
|
|
4592
|
-
const fillerNibble = currentByte & 15;
|
|
4593
|
-
if (fillerNibble !== 0) {
|
|
4594
|
-
throw new import_common14.IdentifierError(`Invalid did: ${identifier}`, import_common14.INVALID_DID, { identifier });
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
const genesisBytes = dataBytes.slice(byteIndex + 1);
|
|
4598
|
-
if (idType === "KEY") {
|
|
4599
|
-
try {
|
|
4600
|
-
new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
|
|
4601
|
-
} catch {
|
|
4602
|
-
throw new import_common14.IdentifierError(`Invalid genesisBytes: ${genesisBytes}`, import_common14.INVALID_DID, { identifier });
|
|
4603
|
-
}
|
|
4604
|
-
}
|
|
4605
|
-
return { idType, hrp, version, network, genesisBytes };
|
|
4606
|
-
}
|
|
4607
|
-
/**
|
|
4608
|
-
* Generates a new did:btcr2 identifier based on a newly generated key pair.
|
|
4609
|
-
* @returns {string} The new did:btcr2 identifier.
|
|
4610
|
-
*/
|
|
4611
|
-
static generate() {
|
|
4612
|
-
const keyPair = import_keypair2.SchnorrKeyPair.generate();
|
|
4613
|
-
const did = this.encode(
|
|
4614
|
-
keyPair.publicKey.compressed,
|
|
4615
|
-
{
|
|
4616
|
-
idType: "KEY",
|
|
4617
|
-
version: 1,
|
|
4618
|
-
network: "regtest"
|
|
4619
|
-
}
|
|
4620
|
-
);
|
|
4621
|
-
return { keyPair: keyPair.exportJSON(), did };
|
|
4622
|
-
}
|
|
4623
|
-
/**
|
|
4624
|
-
* Extracts the compressed secp256k1 public key from a KEY-type did:btcr2 identifier.
|
|
4625
|
-
* @param {string} did The did:btcr2 identifier to extract the public key from.
|
|
4626
|
-
* @returns {CompressedSecp256k1PublicKey} The compressed public key.
|
|
4627
|
-
* @throws {IdentifierError} If the DID is EXTERNAL type (genesis bytes are a hash, not a pubkey).
|
|
4628
|
-
*/
|
|
4629
|
-
static getPublicKey(did) {
|
|
4630
|
-
const { idType, genesisBytes } = _Identifier.decode(did);
|
|
4631
|
-
if (idType !== "KEY") {
|
|
4632
|
-
throw new import_common14.IdentifierError(
|
|
4633
|
-
`Cannot extract public key from EXTERNAL DID: ${did}. EXTERNAL DIDs encode a document hash, not a public key.`,
|
|
4634
|
-
import_common14.INVALID_DID,
|
|
4635
|
-
{ did, idType }
|
|
4636
|
-
);
|
|
4637
|
-
}
|
|
4638
|
-
return new import_keypair2.CompressedSecp256k1PublicKey(genesisBytes);
|
|
4639
|
-
}
|
|
4640
|
-
/**
|
|
4641
|
-
* Validates a did:btcr2 identifier.
|
|
4642
|
-
* @param {string} identifier The did:btcr2 identifier to validate.
|
|
4643
|
-
* @returns {boolean} True if the identifier is valid, false otherwise.
|
|
4644
|
-
*/
|
|
4645
|
-
static isValid(identifier) {
|
|
4646
|
-
try {
|
|
4647
|
-
this.decode(identifier);
|
|
4648
|
-
return true;
|
|
4649
|
-
} catch {
|
|
4650
|
-
return false;
|
|
4651
|
-
}
|
|
4652
|
-
}
|
|
4653
|
-
};
|
|
4654
|
-
|
|
4655
5987
|
// src/core/beacon/utils.ts
|
|
4656
5988
|
var BeaconUtils = class {
|
|
4657
5989
|
/**
|
|
@@ -4662,7 +5994,7 @@ var BeaconUtils = class {
|
|
|
4662
5994
|
*/
|
|
4663
5995
|
static parseBitcoinAddress(uri) {
|
|
4664
5996
|
if (!uri.startsWith("bitcoin:")) {
|
|
4665
|
-
throw new
|
|
5997
|
+
throw new import_common17.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
|
|
4666
5998
|
}
|
|
4667
5999
|
return uri.replace("bitcoin:", "").split("?")[0];
|
|
4668
6000
|
}
|
|
@@ -4743,7 +6075,7 @@ var BeaconUtils = class {
|
|
|
4743
6075
|
const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
|
|
4744
6076
|
const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
|
|
4745
6077
|
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
4746
|
-
throw new
|
|
6078
|
+
throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
|
|
4747
6079
|
}
|
|
4748
6080
|
return [
|
|
4749
6081
|
{
|
|
@@ -4859,7 +6191,7 @@ var BeaconSignalDiscovery = class {
|
|
|
4859
6191
|
}
|
|
4860
6192
|
const rpc = bitcoin.rpc;
|
|
4861
6193
|
if (!rpc) {
|
|
4862
|
-
throw new
|
|
6194
|
+
throw new import_common18.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
|
|
4863
6195
|
}
|
|
4864
6196
|
const targetHeight = await rpc.getBlockCount();
|
|
4865
6197
|
const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
|
|
@@ -4930,23 +6262,23 @@ var BeaconSignalDiscovery = class {
|
|
|
4930
6262
|
|
|
4931
6263
|
// src/core/resolver.ts
|
|
4932
6264
|
var import_bitcoin4 = require("@did-btcr2/bitcoin");
|
|
4933
|
-
var
|
|
6265
|
+
var import_common22 = require("@did-btcr2/common");
|
|
4934
6266
|
var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
|
|
4935
|
-
var
|
|
6267
|
+
var import_keypair6 = require("@did-btcr2/keypair");
|
|
4936
6268
|
|
|
4937
6269
|
// src/did-btcr2.ts
|
|
4938
|
-
var
|
|
6270
|
+
var import_common21 = require("@did-btcr2/common");
|
|
4939
6271
|
var import_dids2 = require("@web5/dids");
|
|
4940
6272
|
|
|
4941
6273
|
// src/core/updater.ts
|
|
4942
|
-
var
|
|
6274
|
+
var import_common20 = require("@did-btcr2/common");
|
|
4943
6275
|
var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
|
|
4944
6276
|
|
|
4945
6277
|
// src/utils/did-document.ts
|
|
4946
6278
|
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
4947
|
-
var
|
|
4948
|
-
var
|
|
4949
|
-
var
|
|
6279
|
+
var import_common19 = require("@did-btcr2/common");
|
|
6280
|
+
var import_keypair5 = require("@did-btcr2/keypair");
|
|
6281
|
+
var import_utils10 = require("@web5/dids/utils");
|
|
4950
6282
|
var import_btc_signer6 = require("@scure/btc-signer");
|
|
4951
6283
|
var BTCR2_DID_DOCUMENT_CONTEXT = [
|
|
4952
6284
|
"https://www.w3.org/ns/did/v1.1",
|
|
@@ -4988,20 +6320,20 @@ var DidDocument = class _DidDocument {
|
|
|
4988
6320
|
deactivated;
|
|
4989
6321
|
constructor(document) {
|
|
4990
6322
|
if (!document.id) {
|
|
4991
|
-
throw new
|
|
6323
|
+
throw new import_common19.DidDocumentError("DID Document must have an id", import_common19.INVALID_DID_DOCUMENT, document);
|
|
4992
6324
|
}
|
|
4993
|
-
const idType = document.id.includes("k1") ?
|
|
6325
|
+
const idType = document.id.includes("k1") ? import_common19.IdentifierTypes.KEY : import_common19.IdentifierTypes.EXTERNAL;
|
|
4994
6326
|
const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
|
|
4995
6327
|
const { id, verificationMethod: vm, service } = document;
|
|
4996
6328
|
if (!isGenesis) {
|
|
4997
6329
|
if (!_DidDocument.isValidId(id)) {
|
|
4998
|
-
throw new
|
|
6330
|
+
throw new import_common19.DidDocumentError(`Invalid id: ${id}`, import_common19.INVALID_DID_DOCUMENT, document);
|
|
4999
6331
|
}
|
|
5000
6332
|
if (!_DidDocument.isValidVerificationMethods(vm)) {
|
|
5001
|
-
throw new
|
|
6333
|
+
throw new import_common19.DidDocumentError("Invalid verificationMethod: " + vm, import_common19.INVALID_DID_DOCUMENT, document);
|
|
5002
6334
|
}
|
|
5003
6335
|
if (!_DidDocument.isValidServices(service)) {
|
|
5004
|
-
throw new
|
|
6336
|
+
throw new import_common19.DidDocumentError("Invalid service: " + service, import_common19.INVALID_DID_DOCUMENT, document);
|
|
5005
6337
|
}
|
|
5006
6338
|
}
|
|
5007
6339
|
this.id = document.id;
|
|
@@ -5011,7 +6343,7 @@ var DidDocument = class _DidDocument {
|
|
|
5011
6343
|
"https://www.w3.org/ns/did/v1.1",
|
|
5012
6344
|
"https://btcr2.dev/context/v1"
|
|
5013
6345
|
];
|
|
5014
|
-
if (idType ===
|
|
6346
|
+
if (idType === import_common19.IdentifierTypes.KEY) {
|
|
5015
6347
|
const keyRef = `${this.id}#initialKey`;
|
|
5016
6348
|
this.authentication = document.authentication || [keyRef];
|
|
5017
6349
|
this.assertionMethod = document.assertionMethod || [keyRef];
|
|
@@ -5097,19 +6429,19 @@ var DidDocument = class _DidDocument {
|
|
|
5097
6429
|
*/
|
|
5098
6430
|
static isValid(didDocument) {
|
|
5099
6431
|
if (!this.isValidContext(didDocument?.["@context"])) {
|
|
5100
|
-
throw new
|
|
6432
|
+
throw new import_common19.DidDocumentError('Invalid "@context"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5101
6433
|
}
|
|
5102
6434
|
if (!this.isValidId(didDocument?.id)) {
|
|
5103
|
-
throw new
|
|
6435
|
+
throw new import_common19.DidDocumentError('Invalid "id"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5104
6436
|
}
|
|
5105
6437
|
if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
|
|
5106
|
-
throw new
|
|
6438
|
+
throw new import_common19.DidDocumentError('Invalid "verificationMethod"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5107
6439
|
}
|
|
5108
6440
|
if (!this.isValidServices(didDocument?.service)) {
|
|
5109
|
-
throw new
|
|
6441
|
+
throw new import_common19.DidDocumentError('Invalid "service"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5110
6442
|
}
|
|
5111
6443
|
if (!this.isValidVerificationRelationships(didDocument)) {
|
|
5112
|
-
throw new
|
|
6444
|
+
throw new import_common19.DidDocumentError("Invalid verification relationships", import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5113
6445
|
}
|
|
5114
6446
|
return true;
|
|
5115
6447
|
}
|
|
@@ -5156,7 +6488,7 @@ var DidDocument = class _DidDocument {
|
|
|
5156
6488
|
* @returns {boolean} True if the services are valid.
|
|
5157
6489
|
*/
|
|
5158
6490
|
static isValidServices(service) {
|
|
5159
|
-
return Array.isArray(service) && service.every(
|
|
6491
|
+
return Array.isArray(service) && service.every(import_utils10.isDidService);
|
|
5160
6492
|
}
|
|
5161
6493
|
/**
|
|
5162
6494
|
* Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
|
|
@@ -5199,16 +6531,16 @@ var DidDocument = class _DidDocument {
|
|
|
5199
6531
|
*/
|
|
5200
6532
|
validateGenesis() {
|
|
5201
6533
|
if (this.id !== ID_PLACEHOLDER_VALUE) {
|
|
5202
|
-
throw new
|
|
6534
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument ID", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5203
6535
|
}
|
|
5204
6536
|
if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
|
|
5205
|
-
throw new
|
|
6537
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5206
6538
|
}
|
|
5207
6539
|
if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
|
|
5208
|
-
throw new
|
|
6540
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument service", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5209
6541
|
}
|
|
5210
6542
|
if (!_DidDocument.isValidVerificationRelationships(this)) {
|
|
5211
|
-
throw new
|
|
6543
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5212
6544
|
}
|
|
5213
6545
|
return true;
|
|
5214
6546
|
}
|
|
@@ -5218,7 +6550,7 @@ var DidDocument = class _DidDocument {
|
|
|
5218
6550
|
*/
|
|
5219
6551
|
toIntermediate() {
|
|
5220
6552
|
if (this.id.includes("k1")) {
|
|
5221
|
-
throw new
|
|
6553
|
+
throw new import_common19.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5222
6554
|
}
|
|
5223
6555
|
return new GenesisDocument(this);
|
|
5224
6556
|
}
|
|
@@ -5248,7 +6580,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5248
6580
|
* @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
|
|
5249
6581
|
*/
|
|
5250
6582
|
static fromDidDocument(didDocument) {
|
|
5251
|
-
const intermediateDocument =
|
|
6583
|
+
const intermediateDocument = import_common19.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
|
|
5252
6584
|
return new _GenesisDocument(intermediateDocument);
|
|
5253
6585
|
}
|
|
5254
6586
|
/**
|
|
@@ -5267,7 +6599,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5267
6599
|
* @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
|
|
5268
6600
|
*/
|
|
5269
6601
|
static fromPublicKey(publicKey, network) {
|
|
5270
|
-
const pk = new
|
|
6602
|
+
const pk = new import_keypair5.CompressedSecp256k1PublicKey(publicKey);
|
|
5271
6603
|
const id = ID_PLACEHOLDER_VALUE;
|
|
5272
6604
|
const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin3.getNetwork)(network)).address;
|
|
5273
6605
|
const services = [{
|
|
@@ -5305,20 +6637,18 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5305
6637
|
* @returns {Bytes} The genesis bytes.
|
|
5306
6638
|
*/
|
|
5307
6639
|
static toGenesisBytes(genesisDocument) {
|
|
5308
|
-
return (0,
|
|
6640
|
+
return (0, import_common19.hash)((0, import_common19.canonicalize)(genesisDocument));
|
|
5309
6641
|
}
|
|
5310
6642
|
};
|
|
5311
6643
|
|
|
5312
6644
|
// src/core/updater.ts
|
|
5313
6645
|
var Updater = class _Updater {
|
|
5314
|
-
#
|
|
6646
|
+
#state = { phase: "Construct" };
|
|
5315
6647
|
#sourceDocument;
|
|
5316
6648
|
#patches;
|
|
5317
6649
|
#sourceVersionId;
|
|
5318
6650
|
#verificationMethod;
|
|
5319
6651
|
#beaconService;
|
|
5320
|
-
#unsignedUpdate = null;
|
|
5321
|
-
#signedUpdate = null;
|
|
5322
6652
|
/**
|
|
5323
6653
|
* @internal — Use {@link DidBtcr2.update} to create instances.
|
|
5324
6654
|
*/
|
|
@@ -5352,19 +6682,26 @@ var Updater = class _Updater {
|
|
|
5352
6682
|
patch: patches,
|
|
5353
6683
|
targetHash: "",
|
|
5354
6684
|
targetVersionId: sourceVersionId + 1,
|
|
5355
|
-
sourceHash: (0,
|
|
6685
|
+
sourceHash: (0, import_common20.canonicalHash)(sourceDocument)
|
|
5356
6686
|
};
|
|
5357
|
-
const targetDocument =
|
|
6687
|
+
const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
|
|
6688
|
+
if (targetDocument.id !== sourceDocument.id) {
|
|
6689
|
+
throw new import_common20.UpdateError(
|
|
6690
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
|
|
6691
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6692
|
+
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
6693
|
+
);
|
|
6694
|
+
}
|
|
5358
6695
|
try {
|
|
5359
6696
|
DidDocument.isValid(targetDocument);
|
|
5360
6697
|
} catch (error) {
|
|
5361
|
-
throw new
|
|
6698
|
+
throw new import_common20.UpdateError(
|
|
5362
6699
|
"Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
|
|
5363
|
-
|
|
6700
|
+
import_common20.INVALID_DID_UPDATE,
|
|
5364
6701
|
targetDocument
|
|
5365
6702
|
);
|
|
5366
6703
|
}
|
|
5367
|
-
unsignedUpdate.targetHash = (0,
|
|
6704
|
+
unsignedUpdate.targetHash = (0, import_common20.canonicalHash)(targetDocument);
|
|
5368
6705
|
return unsignedUpdate;
|
|
5369
6706
|
}
|
|
5370
6707
|
/**
|
|
@@ -5373,13 +6710,28 @@ var Updater = class _Updater {
|
|
|
5373
6710
|
* @param {string} did The did-btcr2 identifier to derive the root capability from.
|
|
5374
6711
|
* @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
|
|
5375
6712
|
* @param {DidVerificationMethod} verificationMethod The verification method for signing.
|
|
5376
|
-
* @param {
|
|
6713
|
+
* @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
|
|
5377
6714
|
* @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
|
|
5378
6715
|
*/
|
|
5379
|
-
static sign(did, unsignedUpdate, verificationMethod,
|
|
6716
|
+
static sign(did, unsignedUpdate, verificationMethod, signer) {
|
|
6717
|
+
if (!did.startsWith("did:btcr2:")) {
|
|
6718
|
+
throw new import_common20.UpdateError(
|
|
6719
|
+
`Expected a did:btcr2 identifier for the root capability; got "${did}".`,
|
|
6720
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6721
|
+
{ did }
|
|
6722
|
+
);
|
|
6723
|
+
}
|
|
5380
6724
|
const controller = verificationMethod.controller;
|
|
5381
|
-
const
|
|
5382
|
-
|
|
6725
|
+
const hashIdx = verificationMethod.id.indexOf("#");
|
|
6726
|
+
if (hashIdx < 0) {
|
|
6727
|
+
throw new import_common20.UpdateError(
|
|
6728
|
+
`Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
|
|
6729
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6730
|
+
{ verificationMethodId: verificationMethod.id }
|
|
6731
|
+
);
|
|
6732
|
+
}
|
|
6733
|
+
const id = verificationMethod.id.slice(hashIdx);
|
|
6734
|
+
const multikey = import_cryptosuite2.SchnorrMultikey.fromSigner(id, controller, signer);
|
|
5383
6735
|
const config = {
|
|
5384
6736
|
"@context": [
|
|
5385
6737
|
"https://w3id.org/security/v2",
|
|
@@ -5403,24 +6755,20 @@ var Updater = class _Updater {
|
|
|
5403
6755
|
*
|
|
5404
6756
|
* @param {BeaconService} beaconService The beacon service to broadcast through.
|
|
5405
6757
|
* @param {SignedBTCR2Update} update The signed update to announce.
|
|
5406
|
-
* @param {
|
|
6758
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
5407
6759
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
5408
6760
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
5409
6761
|
*/
|
|
5410
|
-
static async announce(beaconService, update,
|
|
6762
|
+
static async announce(beaconService, update, signer, bitcoin) {
|
|
5411
6763
|
const beacon = BeaconFactory.establish(beaconService);
|
|
5412
|
-
return beacon.broadcastSignal(update,
|
|
6764
|
+
return beacon.broadcastSignal(update, signer, bitcoin);
|
|
5413
6765
|
}
|
|
5414
|
-
//
|
|
6766
|
+
// Private instance wrappers
|
|
5415
6767
|
// Delegate to the public statics with bound instance fields for cleaner
|
|
5416
6768
|
// advance/provide code.
|
|
5417
6769
|
#construct() {
|
|
5418
6770
|
return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
|
|
5419
6771
|
}
|
|
5420
|
-
#sign(secretKey) {
|
|
5421
|
-
return _Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate, this.#verificationMethod, secretKey);
|
|
5422
|
-
}
|
|
5423
|
-
// ─── State machine ─────────────────────────────────────────────────────────
|
|
5424
6772
|
/**
|
|
5425
6773
|
* Advance the state machine. Returns either:
|
|
5426
6774
|
* - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
|
|
@@ -5428,30 +6776,30 @@ var Updater = class _Updater {
|
|
|
5428
6776
|
*/
|
|
5429
6777
|
advance() {
|
|
5430
6778
|
while (true) {
|
|
5431
|
-
switch (this.#phase) {
|
|
6779
|
+
switch (this.#state.phase) {
|
|
5432
6780
|
// Phase: Construct
|
|
5433
6781
|
// Build the unsigned update from source doc + patches. Pure, synchronous.
|
|
5434
|
-
case "Construct"
|
|
5435
|
-
|
|
5436
|
-
this.#
|
|
6782
|
+
case "Construct": {
|
|
6783
|
+
const unsignedUpdate = this.#construct();
|
|
6784
|
+
this.#state = { phase: "Sign", unsignedUpdate };
|
|
5437
6785
|
continue;
|
|
5438
6786
|
}
|
|
5439
6787
|
// Phase: Sign
|
|
5440
6788
|
// Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
|
|
5441
|
-
case "Sign"
|
|
6789
|
+
case "Sign": {
|
|
5442
6790
|
return {
|
|
5443
6791
|
status: "action-required",
|
|
5444
6792
|
needs: [{
|
|
5445
6793
|
kind: "NeedSigningKey",
|
|
5446
6794
|
verificationMethodId: this.#verificationMethod.id,
|
|
5447
|
-
unsignedUpdate: this.#unsignedUpdate
|
|
6795
|
+
unsignedUpdate: this.#state.unsignedUpdate
|
|
5448
6796
|
}]
|
|
5449
6797
|
};
|
|
5450
6798
|
}
|
|
5451
6799
|
// Phase: Fund
|
|
5452
6800
|
// Emit NeedFunding with the beacon address. The caller checks UTXOs,
|
|
5453
6801
|
// funds the address if needed, and provides to continue.
|
|
5454
|
-
case "Fund"
|
|
6802
|
+
case "Fund": {
|
|
5455
6803
|
const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
|
|
5456
6804
|
return {
|
|
5457
6805
|
status: "action-required",
|
|
@@ -5465,21 +6813,21 @@ var Updater = class _Updater {
|
|
|
5465
6813
|
// Phase: Broadcast
|
|
5466
6814
|
// Emit NeedBroadcast with the signed update + beacon service. The caller performs
|
|
5467
6815
|
// the actual on-chain announcement (or hands off to the aggregation protocol).
|
|
5468
|
-
case "Broadcast"
|
|
6816
|
+
case "Broadcast": {
|
|
5469
6817
|
return {
|
|
5470
6818
|
status: "action-required",
|
|
5471
6819
|
needs: [{
|
|
5472
6820
|
kind: "NeedBroadcast",
|
|
5473
6821
|
beaconService: this.#beaconService,
|
|
5474
|
-
signedUpdate: this.#signedUpdate
|
|
6822
|
+
signedUpdate: this.#state.signedUpdate
|
|
5475
6823
|
}]
|
|
5476
6824
|
};
|
|
5477
6825
|
}
|
|
5478
6826
|
// Phase: Complete
|
|
5479
|
-
case "Complete"
|
|
6827
|
+
case "Complete": {
|
|
5480
6828
|
return {
|
|
5481
6829
|
status: "complete",
|
|
5482
|
-
result: { signedUpdate: this.#signedUpdate }
|
|
6830
|
+
result: { signedUpdate: this.#state.signedUpdate }
|
|
5483
6831
|
};
|
|
5484
6832
|
}
|
|
5485
6833
|
}
|
|
@@ -5488,49 +6836,63 @@ var Updater = class _Updater {
|
|
|
5488
6836
|
provide(need, data) {
|
|
5489
6837
|
switch (need.kind) {
|
|
5490
6838
|
case "NeedSigningKey": {
|
|
5491
|
-
if (this.#phase !== "Sign"
|
|
5492
|
-
throw new
|
|
5493
|
-
`Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
|
|
5494
|
-
|
|
5495
|
-
{ phase: this.#phase }
|
|
6839
|
+
if (this.#state.phase !== "Sign") {
|
|
6840
|
+
throw new import_common20.UpdateError(
|
|
6841
|
+
`Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
|
|
6842
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6843
|
+
{ phase: this.#state.phase }
|
|
5496
6844
|
);
|
|
5497
6845
|
}
|
|
5498
6846
|
if (!data) {
|
|
5499
|
-
throw new
|
|
5500
|
-
"NeedSigningKey requires
|
|
5501
|
-
|
|
5502
|
-
);
|
|
5503
|
-
}
|
|
5504
|
-
if (!this.#unsignedUpdate) {
|
|
5505
|
-
throw new import_common18.UpdateError(
|
|
5506
|
-
"Internal error: unsigned update missing in Sign phase.",
|
|
5507
|
-
import_common18.INVALID_DID_UPDATE
|
|
6847
|
+
throw new import_common20.UpdateError(
|
|
6848
|
+
"NeedSigningKey requires a Signer.",
|
|
6849
|
+
import_common20.INVALID_DID_UPDATE
|
|
5508
6850
|
);
|
|
5509
6851
|
}
|
|
5510
|
-
|
|
5511
|
-
|
|
6852
|
+
const unsignedUpdate = this.#state.unsignedUpdate;
|
|
6853
|
+
const signedUpdate = _Updater.sign(
|
|
6854
|
+
this.#sourceDocument.id,
|
|
6855
|
+
unsignedUpdate,
|
|
6856
|
+
this.#verificationMethod,
|
|
6857
|
+
data
|
|
6858
|
+
);
|
|
6859
|
+
this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
|
|
5512
6860
|
break;
|
|
5513
6861
|
}
|
|
5514
6862
|
case "NeedFunding": {
|
|
5515
|
-
if (this.#phase !== "Fund"
|
|
5516
|
-
throw new
|
|
5517
|
-
`Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
|
|
5518
|
-
|
|
5519
|
-
{ phase: this.#phase }
|
|
6863
|
+
if (this.#state.phase !== "Fund") {
|
|
6864
|
+
throw new import_common20.UpdateError(
|
|
6865
|
+
`Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
|
|
6866
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6867
|
+
{ phase: this.#state.phase }
|
|
5520
6868
|
);
|
|
5521
6869
|
}
|
|
5522
|
-
|
|
6870
|
+
if (data !== void 0) {
|
|
6871
|
+
const proof = data;
|
|
6872
|
+
if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
|
|
6873
|
+
throw new import_common20.UpdateError(
|
|
6874
|
+
`NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
|
|
6875
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6876
|
+
{ utxoCount: proof.utxoCount }
|
|
6877
|
+
);
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
this.#state = {
|
|
6881
|
+
phase: "Broadcast",
|
|
6882
|
+
unsignedUpdate: this.#state.unsignedUpdate,
|
|
6883
|
+
signedUpdate: this.#state.signedUpdate
|
|
6884
|
+
};
|
|
5523
6885
|
break;
|
|
5524
6886
|
}
|
|
5525
6887
|
case "NeedBroadcast": {
|
|
5526
|
-
if (this.#phase !== "Broadcast"
|
|
5527
|
-
throw new
|
|
5528
|
-
`Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
|
|
5529
|
-
|
|
5530
|
-
{ phase: this.#phase }
|
|
6888
|
+
if (this.#state.phase !== "Broadcast") {
|
|
6889
|
+
throw new import_common20.UpdateError(
|
|
6890
|
+
`Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
|
|
6891
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6892
|
+
{ phase: this.#state.phase }
|
|
5531
6893
|
);
|
|
5532
6894
|
}
|
|
5533
|
-
this.#
|
|
6895
|
+
this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
|
|
5534
6896
|
break;
|
|
5535
6897
|
}
|
|
5536
6898
|
}
|
|
@@ -5562,9 +6924,9 @@ var DidBtcr2 = class {
|
|
|
5562
6924
|
static create(genesisBytes, options) {
|
|
5563
6925
|
const { idType, version = 1, network = "bitcoin" } = options || {};
|
|
5564
6926
|
if (!idType) {
|
|
5565
|
-
throw new
|
|
6927
|
+
throw new import_common21.MethodError(
|
|
5566
6928
|
"idType is required for creating a did:btcr2 identifier",
|
|
5567
|
-
|
|
6929
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5568
6930
|
options
|
|
5569
6931
|
);
|
|
5570
6932
|
}
|
|
@@ -5594,7 +6956,7 @@ var DidBtcr2 = class {
|
|
|
5594
6956
|
static resolve(did, resolutionOptions = {}) {
|
|
5595
6957
|
const didComponents = Identifier.decode(did);
|
|
5596
6958
|
const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
|
|
5597
|
-
const currentDocument = didComponents.hrp ===
|
|
6959
|
+
const currentDocument = didComponents.hrp === import_common21.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
|
|
5598
6960
|
return new Resolver(didComponents, sidecarData, currentDocument, {
|
|
5599
6961
|
versionId: resolutionOptions.versionId,
|
|
5600
6962
|
versionTime: resolutionOptions.versionTime,
|
|
@@ -5632,39 +6994,39 @@ var DidBtcr2 = class {
|
|
|
5632
6994
|
beaconId
|
|
5633
6995
|
}) {
|
|
5634
6996
|
if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
|
|
5635
|
-
throw new
|
|
6997
|
+
throw new import_common21.UpdateError(
|
|
5636
6998
|
"Invalid verificationMethodId: not authorized for capabilityInvocation",
|
|
5637
|
-
|
|
6999
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5638
7000
|
sourceDocument
|
|
5639
7001
|
);
|
|
5640
7002
|
}
|
|
5641
7003
|
const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
|
|
5642
7004
|
if (!verificationMethod) {
|
|
5643
|
-
throw new
|
|
7005
|
+
throw new import_common21.UpdateError(
|
|
5644
7006
|
"Invalid verificationMethod: not found in source document",
|
|
5645
|
-
|
|
7007
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5646
7008
|
{ sourceDocument, verificationMethodId }
|
|
5647
7009
|
);
|
|
5648
7010
|
}
|
|
5649
7011
|
if (verificationMethod.type !== "Multikey") {
|
|
5650
|
-
throw new
|
|
7012
|
+
throw new import_common21.UpdateError(
|
|
5651
7013
|
'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
|
|
5652
|
-
|
|
7014
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5653
7015
|
verificationMethod
|
|
5654
7016
|
);
|
|
5655
7017
|
}
|
|
5656
7018
|
if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
|
|
5657
|
-
throw new
|
|
7019
|
+
throw new import_common21.UpdateError(
|
|
5658
7020
|
'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
|
|
5659
|
-
|
|
7021
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5660
7022
|
verificationMethod
|
|
5661
7023
|
);
|
|
5662
7024
|
}
|
|
5663
7025
|
const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
|
|
5664
7026
|
if (!beaconService) {
|
|
5665
|
-
throw new
|
|
7027
|
+
throw new import_common21.UpdateError(
|
|
5666
7028
|
"No beacon service found for provided beaconId",
|
|
5667
|
-
|
|
7029
|
+
import_common21.INVALID_DID_UPDATE,
|
|
5668
7030
|
{ sourceDocument, beaconId }
|
|
5669
7031
|
);
|
|
5670
7032
|
}
|
|
@@ -5690,7 +7052,7 @@ var DidBtcr2 = class {
|
|
|
5690
7052
|
methodId ??= "#initialKey";
|
|
5691
7053
|
const parsedDid = import_dids2.Did.parse(didDocument.id);
|
|
5692
7054
|
if (parsedDid && parsedDid.method !== this.methodName) {
|
|
5693
|
-
throw new
|
|
7055
|
+
throw new import_common21.MethodError(`Method not supported: ${parsedDid.method}`, import_common21.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
|
|
5694
7056
|
}
|
|
5695
7057
|
const verificationMethod = didDocument.verificationMethod?.find(
|
|
5696
7058
|
(vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
|
|
@@ -5706,7 +7068,7 @@ var DidBtcr2 = class {
|
|
|
5706
7068
|
};
|
|
5707
7069
|
|
|
5708
7070
|
// src/core/resolver.ts
|
|
5709
|
-
var
|
|
7071
|
+
var import_utils12 = require("@noble/curves/utils.js");
|
|
5710
7072
|
var Resolver = class _Resolver {
|
|
5711
7073
|
// --- Immutable inputs ---
|
|
5712
7074
|
#didComponents;
|
|
@@ -5746,7 +7108,7 @@ var Resolver = class _Resolver {
|
|
|
5746
7108
|
static deterministic(didComponents) {
|
|
5747
7109
|
const genesisBytes = didComponents.genesisBytes;
|
|
5748
7110
|
const did = Identifier.encode(genesisBytes, didComponents);
|
|
5749
|
-
const { multibase } = new
|
|
7111
|
+
const { multibase } = new import_keypair6.CompressedSecp256k1PublicKey(genesisBytes);
|
|
5750
7112
|
const service = BeaconUtils.generateBeaconServices({
|
|
5751
7113
|
id: did,
|
|
5752
7114
|
publicKey: genesisBytes,
|
|
@@ -5772,14 +7134,14 @@ var Resolver = class _Resolver {
|
|
|
5772
7134
|
* @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
|
|
5773
7135
|
*/
|
|
5774
7136
|
static external(didComponents, genesisDocument) {
|
|
5775
|
-
const genesisDocumentHash = (0,
|
|
5776
|
-
if (!(0,
|
|
5777
|
-
throw new
|
|
7137
|
+
const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
|
|
7138
|
+
if (!(0, import_utils12.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
7139
|
+
throw new import_common22.ResolveError(
|
|
5778
7140
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
5779
|
-
|
|
7141
|
+
import_common22.INVALID_DID_DOCUMENT,
|
|
5780
7142
|
{
|
|
5781
|
-
genesisBytes: (0,
|
|
5782
|
-
genesisDocumentHash: (0,
|
|
7143
|
+
genesisBytes: (0, import_common22.encode)(didComponents.genesisBytes, "hex"),
|
|
7144
|
+
genesisDocumentHash: (0, import_common22.encode)(genesisDocumentHash, "hex")
|
|
5783
7145
|
}
|
|
5784
7146
|
);
|
|
5785
7147
|
}
|
|
@@ -5798,17 +7160,17 @@ var Resolver = class _Resolver {
|
|
|
5798
7160
|
const updateMap = /* @__PURE__ */ new Map();
|
|
5799
7161
|
if (sidecar.updates?.length)
|
|
5800
7162
|
for (const update of sidecar.updates) {
|
|
5801
|
-
updateMap.set((0,
|
|
7163
|
+
updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5802
7164
|
}
|
|
5803
7165
|
const casMap = /* @__PURE__ */ new Map();
|
|
5804
7166
|
if (sidecar.casUpdates?.length)
|
|
5805
7167
|
for (const update of sidecar.casUpdates) {
|
|
5806
|
-
casMap.set((0,
|
|
7168
|
+
casMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5807
7169
|
}
|
|
5808
7170
|
const smtMap = /* @__PURE__ */ new Map();
|
|
5809
7171
|
if (sidecar.smtProofs?.length)
|
|
5810
7172
|
for (const proof of sidecar.smtProofs) {
|
|
5811
|
-
smtMap.set(proof.id, proof);
|
|
7173
|
+
smtMap.set((0, import_common22.encode)((0, import_common22.decode)(proof.id, "base64urlnopad"), "hex"), proof);
|
|
5812
7174
|
}
|
|
5813
7175
|
return { updateMap, casMap, smtMap };
|
|
5814
7176
|
}
|
|
@@ -5836,12 +7198,12 @@ var Resolver = class _Resolver {
|
|
|
5836
7198
|
}
|
|
5837
7199
|
};
|
|
5838
7200
|
for (const [update, block] of updates) {
|
|
5839
|
-
const currentDocumentHash = (0,
|
|
5840
|
-
const blocktime =
|
|
5841
|
-
response.metadata.updated =
|
|
7201
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(response.didDocument);
|
|
7202
|
+
const blocktime = import_common22.DateUtils.blocktimeToTimestamp(block.time);
|
|
7203
|
+
response.metadata.updated = import_common22.DateUtils.toISOStringNonFractional(blocktime);
|
|
5842
7204
|
response.metadata.confirmations = block.confirmations;
|
|
5843
7205
|
if (versionTime) {
|
|
5844
|
-
if (blocktime >
|
|
7206
|
+
if (blocktime > import_common22.DateUtils.dateStringToTimestamp(versionTime)) {
|
|
5845
7207
|
return response;
|
|
5846
7208
|
}
|
|
5847
7209
|
}
|
|
@@ -5849,22 +7211,22 @@ var Resolver = class _Resolver {
|
|
|
5849
7211
|
updateHashHistory.push(currentDocumentHash);
|
|
5850
7212
|
this.confirmDuplicate(update, updateHashHistory);
|
|
5851
7213
|
} else if (update.targetVersionId === currentVersionId + 1) {
|
|
5852
|
-
const sourceHashBytes = (0,
|
|
5853
|
-
if (!(0,
|
|
5854
|
-
throw new
|
|
7214
|
+
const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
|
|
7215
|
+
if (!(0, import_utils12.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
7216
|
+
throw new import_common22.ResolveError(
|
|
5855
7217
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
5856
|
-
|
|
7218
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5857
7219
|
{
|
|
5858
7220
|
sourceHash: update.sourceHash,
|
|
5859
|
-
currentDocumentHash: (0,
|
|
7221
|
+
currentDocumentHash: (0, import_common22.encode)(currentDocumentHash, "hex")
|
|
5860
7222
|
}
|
|
5861
7223
|
);
|
|
5862
7224
|
}
|
|
5863
7225
|
response.didDocument = this.applyUpdate(response.didDocument, update);
|
|
5864
|
-
const unsignedUpdate =
|
|
5865
|
-
updateHashHistory.push((0,
|
|
7226
|
+
const unsignedUpdate = import_common22.JSONUtils.deleteKeys(update, ["proof"]);
|
|
7227
|
+
updateHashHistory.push((0, import_common22.canonicalHashBytes)(unsignedUpdate));
|
|
5866
7228
|
} else if (update.targetVersionId > currentVersionId + 1) {
|
|
5867
|
-
throw new
|
|
7229
|
+
throw new import_common22.ResolveError(
|
|
5868
7230
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
5869
7231
|
"LATE_PUBLISHING_ERROR",
|
|
5870
7232
|
{
|
|
@@ -5895,15 +7257,15 @@ var Resolver = class _Resolver {
|
|
|
5895
7257
|
*/
|
|
5896
7258
|
static confirmDuplicate(update, updateHashHistory) {
|
|
5897
7259
|
const { proof: _, ...unsignedUpdate } = update;
|
|
5898
|
-
const unsignedUpdateHash = (0,
|
|
7260
|
+
const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
|
|
5899
7261
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
5900
|
-
if (!(0,
|
|
5901
|
-
throw new
|
|
7262
|
+
if (!(0, import_utils12.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
|
|
7263
|
+
throw new import_common22.ResolveError(
|
|
5902
7264
|
`Invalid duplicate: unsigned update hash does not match historical hash`,
|
|
5903
|
-
|
|
7265
|
+
import_common22.LATE_PUBLISHING_ERROR,
|
|
5904
7266
|
{
|
|
5905
|
-
unsignedUpdateHash: (0,
|
|
5906
|
-
historicalHash: (0,
|
|
7267
|
+
unsignedUpdateHash: (0, import_common22.encode)(unsignedUpdateHash, "hex"),
|
|
7268
|
+
historicalHash: (0, import_common22.encode)(historicalUpdateHash, "hex")
|
|
5907
7269
|
}
|
|
5908
7270
|
);
|
|
5909
7271
|
}
|
|
@@ -5918,42 +7280,42 @@ var Resolver = class _Resolver {
|
|
|
5918
7280
|
static applyUpdate(currentDocument, update) {
|
|
5919
7281
|
const capabilityId = update.proof?.capability;
|
|
5920
7282
|
if (!capabilityId) {
|
|
5921
|
-
throw new
|
|
7283
|
+
throw new import_common22.ResolveError("No root capability found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5922
7284
|
}
|
|
5923
7285
|
const rootCapability = Appendix.dereferenceZcapId(capabilityId);
|
|
5924
7286
|
const { invocationTarget, controller: rootController } = rootCapability;
|
|
5925
7287
|
if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
|
|
5926
|
-
throw new
|
|
7288
|
+
throw new import_common22.ResolveError(
|
|
5927
7289
|
"Invalid root capability",
|
|
5928
|
-
|
|
7290
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5929
7291
|
{ rootCapability, currentDocument }
|
|
5930
7292
|
);
|
|
5931
7293
|
}
|
|
5932
7294
|
const verificationMethodId = update.proof?.verificationMethod;
|
|
5933
7295
|
if (!verificationMethodId) {
|
|
5934
|
-
throw new
|
|
7296
|
+
throw new import_common22.ResolveError("No verificationMethod found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5935
7297
|
}
|
|
5936
7298
|
const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
|
|
5937
7299
|
const multikey = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm);
|
|
5938
7300
|
const cryptosuite = new import_cryptosuite3.BIP340Cryptosuite(multikey);
|
|
5939
|
-
const canonicalUpdate = (0,
|
|
7301
|
+
const canonicalUpdate = (0, import_common22.canonicalize)(update);
|
|
5940
7302
|
const diProof = new import_cryptosuite3.BIP340DataIntegrityProof(cryptosuite);
|
|
5941
7303
|
const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
|
|
5942
7304
|
if (!verificationResult.verified) {
|
|
5943
|
-
throw new
|
|
7305
|
+
throw new import_common22.ResolveError(
|
|
5944
7306
|
"Invalid update: proof not verified",
|
|
5945
|
-
|
|
7307
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5946
7308
|
verificationResult
|
|
5947
7309
|
);
|
|
5948
7310
|
}
|
|
5949
|
-
const updatedDocument =
|
|
7311
|
+
const updatedDocument = import_common22.JSONPatch.apply(currentDocument, update.patch);
|
|
5950
7312
|
DidDocument.validate(updatedDocument);
|
|
5951
|
-
const currentDocumentHash = (0,
|
|
5952
|
-
const updateTargetHash = (0,
|
|
5953
|
-
if (!(0,
|
|
5954
|
-
throw new
|
|
7313
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
|
|
7314
|
+
const updateTargetHash = (0, import_common22.decode)(update.targetHash);
|
|
7315
|
+
if (!(0, import_utils12.equalBytes)(updateTargetHash, currentDocumentHash)) {
|
|
7316
|
+
throw new import_common22.ResolveError(
|
|
5955
7317
|
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
5956
|
-
|
|
7318
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5957
7319
|
{ updateTargetHash, currentDocumentHash }
|
|
5958
7320
|
);
|
|
5959
7321
|
}
|
|
@@ -5981,7 +7343,7 @@ var Resolver = class _Resolver {
|
|
|
5981
7343
|
this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
|
|
5982
7344
|
continue;
|
|
5983
7345
|
}
|
|
5984
|
-
const genesisHash = (0,
|
|
7346
|
+
const genesisHash = (0, import_common22.encode)(this.#didComponents.genesisBytes, "hex");
|
|
5985
7347
|
return {
|
|
5986
7348
|
status: "action-required",
|
|
5987
7349
|
needs: [{ kind: "NeedGenesisDocument", genesisHash }]
|
|
@@ -6085,22 +7447,23 @@ var Resolver = class _Resolver {
|
|
|
6085
7447
|
}
|
|
6086
7448
|
case "NeedCASAnnouncement": {
|
|
6087
7449
|
const announcement = data;
|
|
6088
|
-
this.#sidecarData.casMap.set((0,
|
|
7450
|
+
this.#sidecarData.casMap.set((0, import_common22.canonicalHash)(announcement, { encoding: "hex" }), announcement);
|
|
6089
7451
|
break;
|
|
6090
7452
|
}
|
|
6091
7453
|
case "NeedSignedUpdate": {
|
|
6092
7454
|
const update = data;
|
|
6093
|
-
this.#sidecarData.updateMap.set((0,
|
|
7455
|
+
this.#sidecarData.updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
6094
7456
|
break;
|
|
6095
7457
|
}
|
|
6096
7458
|
case "NeedSMTProof": {
|
|
6097
7459
|
const smtNeed = need;
|
|
6098
7460
|
const proof = data;
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
7461
|
+
const proofIdHex = (0, import_common22.encode)((0, import_common22.decode)(proof.id, "base64urlnopad"), "hex");
|
|
7462
|
+
if (proofIdHex !== smtNeed.smtRootHash) {
|
|
7463
|
+
throw new import_common22.ResolveError(
|
|
7464
|
+
`SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proofIdHex}`,
|
|
7465
|
+
import_common22.INVALID_DID_UPDATE,
|
|
7466
|
+
{ expected: smtNeed.smtRootHash, actual: proofIdHex }
|
|
6104
7467
|
);
|
|
6105
7468
|
}
|
|
6106
7469
|
this.#sidecarData.smtMap.set(smtNeed.smtRootHash, proof);
|
|
@@ -6111,12 +7474,12 @@ var Resolver = class _Resolver {
|
|
|
6111
7474
|
};
|
|
6112
7475
|
|
|
6113
7476
|
// src/utils/did-document-builder.ts
|
|
6114
|
-
var
|
|
7477
|
+
var import_common23 = require("@did-btcr2/common");
|
|
6115
7478
|
var DidDocumentBuilder = class {
|
|
6116
7479
|
document = {};
|
|
6117
7480
|
constructor(initialDocument) {
|
|
6118
7481
|
if (!initialDocument.id) {
|
|
6119
|
-
throw new
|
|
7482
|
+
throw new import_common23.DidDocumentError('Missing required "id" property', import_common23.INVALID_DID_DOCUMENT, initialDocument);
|
|
6120
7483
|
}
|
|
6121
7484
|
this.document.id = initialDocument.id;
|
|
6122
7485
|
this.document.verificationMethod = initialDocument.verificationMethod ?? [];
|
|
@@ -6198,7 +7561,9 @@ var DidDocumentBuilder = class {
|
|
|
6198
7561
|
CONSOLE_LOGGER,
|
|
6199
7562
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
6200
7563
|
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
7564
|
+
DEFAULT_CLOCK_SKEW_SEC,
|
|
6201
7565
|
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
7566
|
+
DEFAULT_NONCE_LEN_BYTES,
|
|
6202
7567
|
DEFAULT_NOSTR_RELAYS,
|
|
6203
7568
|
DID_REGEX,
|
|
6204
7569
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
@@ -6209,16 +7574,31 @@ var DidDocumentBuilder = class {
|
|
|
6209
7574
|
DidVerificationMethod,
|
|
6210
7575
|
Document,
|
|
6211
7576
|
GenesisDocument,
|
|
7577
|
+
HTTP_ENVELOPE_VERSION,
|
|
7578
|
+
HTTP_ROUTE,
|
|
7579
|
+
HttpClientTransport,
|
|
7580
|
+
HttpServerTransport,
|
|
7581
|
+
HttpTransportError,
|
|
6212
7582
|
ID_PLACEHOLDER_VALUE,
|
|
6213
7583
|
Identifier,
|
|
7584
|
+
InMemoryRateLimitStore,
|
|
7585
|
+
InboxBuffer,
|
|
6214
7586
|
NONCE_CONTRIBUTION,
|
|
7587
|
+
NonceCache,
|
|
6215
7588
|
NostrTransport,
|
|
7589
|
+
P2PKH_BEACON_TX_VSIZE,
|
|
7590
|
+
P2TR_BEACON_TX_VSIZE,
|
|
7591
|
+
P2WPKH_BEACON_TX_VSIZE,
|
|
6216
7592
|
ParticipantCohortPhase,
|
|
7593
|
+
REQUEST_AUTH_SCHEME,
|
|
7594
|
+
RateLimiter,
|
|
6217
7595
|
Resolver,
|
|
6218
7596
|
SIGNATURE_AUTHORIZATION,
|
|
6219
7597
|
SILENT_LOGGER,
|
|
7598
|
+
SINGLETON_BEACON_TX_VSIZE,
|
|
6220
7599
|
SMTBeacon,
|
|
6221
7600
|
SMTBeaconError,
|
|
7601
|
+
SSE_EVENT,
|
|
6222
7602
|
SUBMIT_UPDATE,
|
|
6223
7603
|
ServiceCohortPhase,
|
|
6224
7604
|
SigningSessionError,
|
|
@@ -6233,6 +7613,7 @@ var DidDocumentBuilder = class {
|
|
|
6233
7613
|
Updater,
|
|
6234
7614
|
VALIDATION_ACK,
|
|
6235
7615
|
buildAggregationBeaconTx,
|
|
7616
|
+
buildRequestAuth,
|
|
6236
7617
|
createAggregatedNonceMessage,
|
|
6237
7618
|
createAuthorizationRequestMessage,
|
|
6238
7619
|
createCohortAdvertMessage,
|
|
@@ -6244,6 +7625,11 @@ var DidDocumentBuilder = class {
|
|
|
6244
7625
|
createSignatureAuthorizationMessage,
|
|
6245
7626
|
createSubmitUpdateMessage,
|
|
6246
7627
|
createValidationAckMessage,
|
|
7628
|
+
defaultReconnectBackoff,
|
|
7629
|
+
deriveSingletonAddress,
|
|
7630
|
+
detectSingletonScriptKind,
|
|
7631
|
+
formatSseComment,
|
|
7632
|
+
formatSseEvent,
|
|
6247
7633
|
getBeaconStrategy,
|
|
6248
7634
|
isAggregatedNonceMessage,
|
|
6249
7635
|
isAggregationMessageType,
|
|
@@ -6260,6 +7646,13 @@ var DidDocumentBuilder = class {
|
|
|
6260
7646
|
isSubmitUpdateMessage,
|
|
6261
7647
|
isUpdateMessageType,
|
|
6262
7648
|
isValidationAckMessage,
|
|
7649
|
+
normalizeForWire,
|
|
6263
7650
|
opReturnScript,
|
|
6264
|
-
|
|
7651
|
+
parseRequestAuth,
|
|
7652
|
+
parseSseStream,
|
|
7653
|
+
registerBeaconStrategy,
|
|
7654
|
+
reviveFromWire,
|
|
7655
|
+
signEnvelope,
|
|
7656
|
+
verifyEnvelope,
|
|
7657
|
+
verifyRequestAuth
|
|
6265
7658
|
});
|