@did-btcr2/method 0.29.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +8174 -7157
- package/dist/browser.mjs +8174 -7157
- package/dist/cjs/index.js +1845 -455
- 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 +3 -3
- package/dist/esm/core/beacon/smt-beacon.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/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/updater.d.ts +27 -12
- package/dist/types/core/updater.d.ts.map +1 -1
- package/package.json +5 -5
- 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 +4 -4
- 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
|
|
|
@@ -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.
|
|
@@ -3374,20 +4896,20 @@ var SMTBeacon = class extends Beacon {
|
|
|
3374
4896
|
* signing, and broadcast are delegated to {@link Beacon.buildSignAndBroadcast}.
|
|
3375
4897
|
*
|
|
3376
4898
|
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
3377
|
-
* @param {
|
|
4899
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
3378
4900
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
3379
4901
|
* @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator).
|
|
3380
4902
|
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
3381
4903
|
* @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee.
|
|
3382
4904
|
*/
|
|
3383
|
-
async broadcastSignal(signedUpdate,
|
|
4905
|
+
async broadcastSignal(signedUpdate, signer, bitcoin, options) {
|
|
3384
4906
|
const did = this.service.id.split("#")[0];
|
|
3385
|
-
const canonicalBytes = new TextEncoder().encode((0,
|
|
3386
|
-
const nonce = (0,
|
|
4907
|
+
const canonicalBytes = new TextEncoder().encode((0, import_common15.canonicalize)(signedUpdate));
|
|
4908
|
+
const nonce = (0, import_utils8.randomBytes)(32);
|
|
3387
4909
|
const tree = new import_smt3.BTCR2MerkleTree();
|
|
3388
4910
|
tree.addEntries([{ did, nonce, signedUpdate: canonicalBytes }]);
|
|
3389
4911
|
tree.finalize();
|
|
3390
|
-
await this.buildSignAndBroadcast(tree.rootHash,
|
|
4912
|
+
await this.buildSignAndBroadcast(tree.rootHash, signer, bitcoin, options);
|
|
3391
4913
|
return signedUpdate;
|
|
3392
4914
|
}
|
|
3393
4915
|
};
|
|
@@ -3408,18 +4930,18 @@ var BeaconFactory = class {
|
|
|
3408
4930
|
case "SMTBeacon":
|
|
3409
4931
|
return new SMTBeacon(service);
|
|
3410
4932
|
default:
|
|
3411
|
-
throw new
|
|
4933
|
+
throw new import_common16.MethodError("Invalid Beacon Type", "INVALID_BEACON_ERROR", service);
|
|
3412
4934
|
}
|
|
3413
4935
|
}
|
|
3414
4936
|
};
|
|
3415
4937
|
|
|
3416
4938
|
// src/core/beacon/signal-discovery.ts
|
|
3417
4939
|
var import_bitcoin2 = require("@did-btcr2/bitcoin");
|
|
3418
|
-
var
|
|
4940
|
+
var import_common18 = require("@did-btcr2/common");
|
|
3419
4941
|
|
|
3420
4942
|
// src/core/beacon/utils.ts
|
|
3421
4943
|
var import_bitcoin = require("@did-btcr2/bitcoin");
|
|
3422
|
-
var
|
|
4944
|
+
var import_common17 = require("@did-btcr2/common");
|
|
3423
4945
|
var import_btc_signer5 = require("@scure/btc-signer");
|
|
3424
4946
|
|
|
3425
4947
|
// src/utils/appendix.ts
|
|
@@ -4460,198 +5982,6 @@ var Appendix = class _Appendix {
|
|
|
4460
5982
|
}
|
|
4461
5983
|
};
|
|
4462
5984
|
|
|
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
5985
|
// src/core/beacon/utils.ts
|
|
4656
5986
|
var BeaconUtils = class {
|
|
4657
5987
|
/**
|
|
@@ -4662,7 +5992,7 @@ var BeaconUtils = class {
|
|
|
4662
5992
|
*/
|
|
4663
5993
|
static parseBitcoinAddress(uri) {
|
|
4664
5994
|
if (!uri.startsWith("bitcoin:")) {
|
|
4665
|
-
throw new
|
|
5995
|
+
throw new import_common17.MethodError("Invalid Bitcoin URI format", "BEACON_SERVICE_ERROR", { uri });
|
|
4666
5996
|
}
|
|
4667
5997
|
return uri.replace("bitcoin:", "").split("?")[0];
|
|
4668
5998
|
}
|
|
@@ -4743,7 +6073,7 @@ var BeaconUtils = class {
|
|
|
4743
6073
|
const p2wpkhAddr = (0, import_btc_signer5.p2wpkh)(publicKey, network).address;
|
|
4744
6074
|
const p2trAddr = (0, import_btc_signer5.p2tr)(publicKey.slice(1, 33), void 0, network).address;
|
|
4745
6075
|
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
4746
|
-
throw new
|
|
6076
|
+
throw new import_common17.DidMethodError("Failed to generate bitcoin addresses");
|
|
4747
6077
|
}
|
|
4748
6078
|
return [
|
|
4749
6079
|
{
|
|
@@ -4859,7 +6189,7 @@ var BeaconSignalDiscovery = class {
|
|
|
4859
6189
|
}
|
|
4860
6190
|
const rpc = bitcoin.rpc;
|
|
4861
6191
|
if (!rpc) {
|
|
4862
|
-
throw new
|
|
6192
|
+
throw new import_common18.ResolveError("RPC connection is not available", "RPC_CONNECTION_ERROR", bitcoin);
|
|
4863
6193
|
}
|
|
4864
6194
|
const targetHeight = await rpc.getBlockCount();
|
|
4865
6195
|
const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
|
|
@@ -4930,23 +6260,23 @@ var BeaconSignalDiscovery = class {
|
|
|
4930
6260
|
|
|
4931
6261
|
// src/core/resolver.ts
|
|
4932
6262
|
var import_bitcoin4 = require("@did-btcr2/bitcoin");
|
|
4933
|
-
var
|
|
6263
|
+
var import_common22 = require("@did-btcr2/common");
|
|
4934
6264
|
var import_cryptosuite3 = require("@did-btcr2/cryptosuite");
|
|
4935
|
-
var
|
|
6265
|
+
var import_keypair6 = require("@did-btcr2/keypair");
|
|
4936
6266
|
|
|
4937
6267
|
// src/did-btcr2.ts
|
|
4938
|
-
var
|
|
6268
|
+
var import_common21 = require("@did-btcr2/common");
|
|
4939
6269
|
var import_dids2 = require("@web5/dids");
|
|
4940
6270
|
|
|
4941
6271
|
// src/core/updater.ts
|
|
4942
|
-
var
|
|
6272
|
+
var import_common20 = require("@did-btcr2/common");
|
|
4943
6273
|
var import_cryptosuite2 = require("@did-btcr2/cryptosuite");
|
|
4944
6274
|
|
|
4945
6275
|
// src/utils/did-document.ts
|
|
4946
6276
|
var import_bitcoin3 = require("@did-btcr2/bitcoin");
|
|
4947
|
-
var
|
|
4948
|
-
var
|
|
4949
|
-
var
|
|
6277
|
+
var import_common19 = require("@did-btcr2/common");
|
|
6278
|
+
var import_keypair5 = require("@did-btcr2/keypair");
|
|
6279
|
+
var import_utils10 = require("@web5/dids/utils");
|
|
4950
6280
|
var import_btc_signer6 = require("@scure/btc-signer");
|
|
4951
6281
|
var BTCR2_DID_DOCUMENT_CONTEXT = [
|
|
4952
6282
|
"https://www.w3.org/ns/did/v1.1",
|
|
@@ -4988,20 +6318,20 @@ var DidDocument = class _DidDocument {
|
|
|
4988
6318
|
deactivated;
|
|
4989
6319
|
constructor(document) {
|
|
4990
6320
|
if (!document.id) {
|
|
4991
|
-
throw new
|
|
6321
|
+
throw new import_common19.DidDocumentError("DID Document must have an id", import_common19.INVALID_DID_DOCUMENT, document);
|
|
4992
6322
|
}
|
|
4993
|
-
const idType = document.id.includes("k1") ?
|
|
6323
|
+
const idType = document.id.includes("k1") ? import_common19.IdentifierTypes.KEY : import_common19.IdentifierTypes.EXTERNAL;
|
|
4994
6324
|
const isGenesis = document.id === ID_PLACEHOLDER_VALUE;
|
|
4995
6325
|
const { id, verificationMethod: vm, service } = document;
|
|
4996
6326
|
if (!isGenesis) {
|
|
4997
6327
|
if (!_DidDocument.isValidId(id)) {
|
|
4998
|
-
throw new
|
|
6328
|
+
throw new import_common19.DidDocumentError(`Invalid id: ${id}`, import_common19.INVALID_DID_DOCUMENT, document);
|
|
4999
6329
|
}
|
|
5000
6330
|
if (!_DidDocument.isValidVerificationMethods(vm)) {
|
|
5001
|
-
throw new
|
|
6331
|
+
throw new import_common19.DidDocumentError("Invalid verificationMethod: " + vm, import_common19.INVALID_DID_DOCUMENT, document);
|
|
5002
6332
|
}
|
|
5003
6333
|
if (!_DidDocument.isValidServices(service)) {
|
|
5004
|
-
throw new
|
|
6334
|
+
throw new import_common19.DidDocumentError("Invalid service: " + service, import_common19.INVALID_DID_DOCUMENT, document);
|
|
5005
6335
|
}
|
|
5006
6336
|
}
|
|
5007
6337
|
this.id = document.id;
|
|
@@ -5011,7 +6341,7 @@ var DidDocument = class _DidDocument {
|
|
|
5011
6341
|
"https://www.w3.org/ns/did/v1.1",
|
|
5012
6342
|
"https://btcr2.dev/context/v1"
|
|
5013
6343
|
];
|
|
5014
|
-
if (idType ===
|
|
6344
|
+
if (idType === import_common19.IdentifierTypes.KEY) {
|
|
5015
6345
|
const keyRef = `${this.id}#initialKey`;
|
|
5016
6346
|
this.authentication = document.authentication || [keyRef];
|
|
5017
6347
|
this.assertionMethod = document.assertionMethod || [keyRef];
|
|
@@ -5097,19 +6427,19 @@ var DidDocument = class _DidDocument {
|
|
|
5097
6427
|
*/
|
|
5098
6428
|
static isValid(didDocument) {
|
|
5099
6429
|
if (!this.isValidContext(didDocument?.["@context"])) {
|
|
5100
|
-
throw new
|
|
6430
|
+
throw new import_common19.DidDocumentError('Invalid "@context"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5101
6431
|
}
|
|
5102
6432
|
if (!this.isValidId(didDocument?.id)) {
|
|
5103
|
-
throw new
|
|
6433
|
+
throw new import_common19.DidDocumentError('Invalid "id"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5104
6434
|
}
|
|
5105
6435
|
if (!this.isValidVerificationMethods(didDocument?.verificationMethod)) {
|
|
5106
|
-
throw new
|
|
6436
|
+
throw new import_common19.DidDocumentError('Invalid "verificationMethod"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5107
6437
|
}
|
|
5108
6438
|
if (!this.isValidServices(didDocument?.service)) {
|
|
5109
|
-
throw new
|
|
6439
|
+
throw new import_common19.DidDocumentError('Invalid "service"', import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5110
6440
|
}
|
|
5111
6441
|
if (!this.isValidVerificationRelationships(didDocument)) {
|
|
5112
|
-
throw new
|
|
6442
|
+
throw new import_common19.DidDocumentError("Invalid verification relationships", import_common19.INVALID_DID_DOCUMENT, didDocument);
|
|
5113
6443
|
}
|
|
5114
6444
|
return true;
|
|
5115
6445
|
}
|
|
@@ -5156,7 +6486,7 @@ var DidDocument = class _DidDocument {
|
|
|
5156
6486
|
* @returns {boolean} True if the services are valid.
|
|
5157
6487
|
*/
|
|
5158
6488
|
static isValidServices(service) {
|
|
5159
|
-
return Array.isArray(service) && service.every(
|
|
6489
|
+
return Array.isArray(service) && service.every(import_utils10.isDidService);
|
|
5160
6490
|
}
|
|
5161
6491
|
/**
|
|
5162
6492
|
* Validates verification relationships (authentication, assertionMethod, capabilityInvocation, capabilityDelegation).
|
|
@@ -5199,16 +6529,16 @@ var DidDocument = class _DidDocument {
|
|
|
5199
6529
|
*/
|
|
5200
6530
|
validateGenesis() {
|
|
5201
6531
|
if (this.id !== ID_PLACEHOLDER_VALUE) {
|
|
5202
|
-
throw new
|
|
6532
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument ID", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5203
6533
|
}
|
|
5204
6534
|
if (!this.verificationMethod.every((vm) => vm.id.includes(ID_PLACEHOLDER_VALUE) && vm.controller === ID_PLACEHOLDER_VALUE)) {
|
|
5205
|
-
throw new
|
|
6535
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument verificationMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5206
6536
|
}
|
|
5207
6537
|
if (!this.service.every((svc) => svc.id.includes(ID_PLACEHOLDER_VALUE))) {
|
|
5208
|
-
throw new
|
|
6538
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument service", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5209
6539
|
}
|
|
5210
6540
|
if (!_DidDocument.isValidVerificationRelationships(this)) {
|
|
5211
|
-
throw new
|
|
6541
|
+
throw new import_common19.DidDocumentError("Invalid GenesisDocument assertionMethod", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5212
6542
|
}
|
|
5213
6543
|
return true;
|
|
5214
6544
|
}
|
|
@@ -5218,7 +6548,7 @@ var DidDocument = class _DidDocument {
|
|
|
5218
6548
|
*/
|
|
5219
6549
|
toIntermediate() {
|
|
5220
6550
|
if (this.id.includes("k1")) {
|
|
5221
|
-
throw new
|
|
6551
|
+
throw new import_common19.DidDocumentError("Cannot convert a key identifier to an intermediate document", import_common19.INVALID_DID_DOCUMENT, this);
|
|
5222
6552
|
}
|
|
5223
6553
|
return new GenesisDocument(this);
|
|
5224
6554
|
}
|
|
@@ -5248,7 +6578,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5248
6578
|
* @returns {GenesisDocument} The GenesisDocument representation of the DidDocument.
|
|
5249
6579
|
*/
|
|
5250
6580
|
static fromDidDocument(didDocument) {
|
|
5251
|
-
const intermediateDocument =
|
|
6581
|
+
const intermediateDocument = import_common19.JSONUtils.cloneReplace(didDocument, DID_REGEX, ID_PLACEHOLDER_VALUE);
|
|
5252
6582
|
return new _GenesisDocument(intermediateDocument);
|
|
5253
6583
|
}
|
|
5254
6584
|
/**
|
|
@@ -5267,7 +6597,7 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5267
6597
|
* @returns {GenesisDocument} A new GenesisDocument with the placeholder ID.
|
|
5268
6598
|
*/
|
|
5269
6599
|
static fromPublicKey(publicKey, network) {
|
|
5270
|
-
const pk = new
|
|
6600
|
+
const pk = new import_keypair5.CompressedSecp256k1PublicKey(publicKey);
|
|
5271
6601
|
const id = ID_PLACEHOLDER_VALUE;
|
|
5272
6602
|
const address = (0, import_btc_signer6.p2pkh)(pk.compressed, (0, import_bitcoin3.getNetwork)(network)).address;
|
|
5273
6603
|
const services = [{
|
|
@@ -5305,20 +6635,18 @@ var GenesisDocument = class _GenesisDocument extends DidDocument {
|
|
|
5305
6635
|
* @returns {Bytes} The genesis bytes.
|
|
5306
6636
|
*/
|
|
5307
6637
|
static toGenesisBytes(genesisDocument) {
|
|
5308
|
-
return (0,
|
|
6638
|
+
return (0, import_common19.hash)((0, import_common19.canonicalize)(genesisDocument));
|
|
5309
6639
|
}
|
|
5310
6640
|
};
|
|
5311
6641
|
|
|
5312
6642
|
// src/core/updater.ts
|
|
5313
6643
|
var Updater = class _Updater {
|
|
5314
|
-
#
|
|
6644
|
+
#state = { phase: "Construct" };
|
|
5315
6645
|
#sourceDocument;
|
|
5316
6646
|
#patches;
|
|
5317
6647
|
#sourceVersionId;
|
|
5318
6648
|
#verificationMethod;
|
|
5319
6649
|
#beaconService;
|
|
5320
|
-
#unsignedUpdate = null;
|
|
5321
|
-
#signedUpdate = null;
|
|
5322
6650
|
/**
|
|
5323
6651
|
* @internal — Use {@link DidBtcr2.update} to create instances.
|
|
5324
6652
|
*/
|
|
@@ -5352,19 +6680,26 @@ var Updater = class _Updater {
|
|
|
5352
6680
|
patch: patches,
|
|
5353
6681
|
targetHash: "",
|
|
5354
6682
|
targetVersionId: sourceVersionId + 1,
|
|
5355
|
-
sourceHash: (0,
|
|
6683
|
+
sourceHash: (0, import_common20.canonicalHash)(sourceDocument)
|
|
5356
6684
|
};
|
|
5357
|
-
const targetDocument =
|
|
6685
|
+
const targetDocument = import_common20.JSONPatch.apply(sourceDocument, patches);
|
|
6686
|
+
if (targetDocument.id !== sourceDocument.id) {
|
|
6687
|
+
throw new import_common20.UpdateError(
|
|
6688
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" \u2192 target "${targetDocument.id}").`,
|
|
6689
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6690
|
+
{ sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
6691
|
+
);
|
|
6692
|
+
}
|
|
5358
6693
|
try {
|
|
5359
6694
|
DidDocument.isValid(targetDocument);
|
|
5360
6695
|
} catch (error) {
|
|
5361
|
-
throw new
|
|
6696
|
+
throw new import_common20.UpdateError(
|
|
5362
6697
|
"Error validating targetDocument: " + (error instanceof Error ? error.message : String(error)),
|
|
5363
|
-
|
|
6698
|
+
import_common20.INVALID_DID_UPDATE,
|
|
5364
6699
|
targetDocument
|
|
5365
6700
|
);
|
|
5366
6701
|
}
|
|
5367
|
-
unsignedUpdate.targetHash = (0,
|
|
6702
|
+
unsignedUpdate.targetHash = (0, import_common20.canonicalHash)(targetDocument);
|
|
5368
6703
|
return unsignedUpdate;
|
|
5369
6704
|
}
|
|
5370
6705
|
/**
|
|
@@ -5373,13 +6708,28 @@ var Updater = class _Updater {
|
|
|
5373
6708
|
* @param {string} did The did-btcr2 identifier to derive the root capability from.
|
|
5374
6709
|
* @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
|
|
5375
6710
|
* @param {DidVerificationMethod} verificationMethod The verification method for signing.
|
|
5376
|
-
* @param {
|
|
6711
|
+
* @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
|
|
5377
6712
|
* @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
|
|
5378
6713
|
*/
|
|
5379
|
-
static sign(did, unsignedUpdate, verificationMethod,
|
|
6714
|
+
static sign(did, unsignedUpdate, verificationMethod, signer) {
|
|
6715
|
+
if (!did.startsWith("did:btcr2:")) {
|
|
6716
|
+
throw new import_common20.UpdateError(
|
|
6717
|
+
`Expected a did:btcr2 identifier for the root capability; got "${did}".`,
|
|
6718
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6719
|
+
{ did }
|
|
6720
|
+
);
|
|
6721
|
+
}
|
|
5380
6722
|
const controller = verificationMethod.controller;
|
|
5381
|
-
const
|
|
5382
|
-
|
|
6723
|
+
const hashIdx = verificationMethod.id.indexOf("#");
|
|
6724
|
+
if (hashIdx < 0) {
|
|
6725
|
+
throw new import_common20.UpdateError(
|
|
6726
|
+
`Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
|
|
6727
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6728
|
+
{ verificationMethodId: verificationMethod.id }
|
|
6729
|
+
);
|
|
6730
|
+
}
|
|
6731
|
+
const id = verificationMethod.id.slice(hashIdx);
|
|
6732
|
+
const multikey = import_cryptosuite2.SchnorrMultikey.fromSigner(id, controller, signer);
|
|
5383
6733
|
const config = {
|
|
5384
6734
|
"@context": [
|
|
5385
6735
|
"https://w3id.org/security/v2",
|
|
@@ -5403,24 +6753,20 @@ var Updater = class _Updater {
|
|
|
5403
6753
|
*
|
|
5404
6754
|
* @param {BeaconService} beaconService The beacon service to broadcast through.
|
|
5405
6755
|
* @param {SignedBTCR2Update} update The signed update to announce.
|
|
5406
|
-
* @param {
|
|
6756
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
5407
6757
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
5408
6758
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
5409
6759
|
*/
|
|
5410
|
-
static async announce(beaconService, update,
|
|
6760
|
+
static async announce(beaconService, update, signer, bitcoin) {
|
|
5411
6761
|
const beacon = BeaconFactory.establish(beaconService);
|
|
5412
|
-
return beacon.broadcastSignal(update,
|
|
6762
|
+
return beacon.broadcastSignal(update, signer, bitcoin);
|
|
5413
6763
|
}
|
|
5414
|
-
//
|
|
6764
|
+
// Private instance wrappers
|
|
5415
6765
|
// Delegate to the public statics with bound instance fields for cleaner
|
|
5416
6766
|
// advance/provide code.
|
|
5417
6767
|
#construct() {
|
|
5418
6768
|
return _Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
|
|
5419
6769
|
}
|
|
5420
|
-
#sign(secretKey) {
|
|
5421
|
-
return _Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate, this.#verificationMethod, secretKey);
|
|
5422
|
-
}
|
|
5423
|
-
// ─── State machine ─────────────────────────────────────────────────────────
|
|
5424
6770
|
/**
|
|
5425
6771
|
* Advance the state machine. Returns either:
|
|
5426
6772
|
* - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
|
|
@@ -5428,30 +6774,30 @@ var Updater = class _Updater {
|
|
|
5428
6774
|
*/
|
|
5429
6775
|
advance() {
|
|
5430
6776
|
while (true) {
|
|
5431
|
-
switch (this.#phase) {
|
|
6777
|
+
switch (this.#state.phase) {
|
|
5432
6778
|
// Phase: Construct
|
|
5433
6779
|
// Build the unsigned update from source doc + patches. Pure, synchronous.
|
|
5434
|
-
case "Construct"
|
|
5435
|
-
|
|
5436
|
-
this.#
|
|
6780
|
+
case "Construct": {
|
|
6781
|
+
const unsignedUpdate = this.#construct();
|
|
6782
|
+
this.#state = { phase: "Sign", unsignedUpdate };
|
|
5437
6783
|
continue;
|
|
5438
6784
|
}
|
|
5439
6785
|
// Phase: Sign
|
|
5440
6786
|
// Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
|
|
5441
|
-
case "Sign"
|
|
6787
|
+
case "Sign": {
|
|
5442
6788
|
return {
|
|
5443
6789
|
status: "action-required",
|
|
5444
6790
|
needs: [{
|
|
5445
6791
|
kind: "NeedSigningKey",
|
|
5446
6792
|
verificationMethodId: this.#verificationMethod.id,
|
|
5447
|
-
unsignedUpdate: this.#unsignedUpdate
|
|
6793
|
+
unsignedUpdate: this.#state.unsignedUpdate
|
|
5448
6794
|
}]
|
|
5449
6795
|
};
|
|
5450
6796
|
}
|
|
5451
6797
|
// Phase: Fund
|
|
5452
6798
|
// Emit NeedFunding with the beacon address. The caller checks UTXOs,
|
|
5453
6799
|
// funds the address if needed, and provides to continue.
|
|
5454
|
-
case "Fund"
|
|
6800
|
+
case "Fund": {
|
|
5455
6801
|
const beaconAddress = this.#beaconService.serviceEndpoint.replace("bitcoin:", "");
|
|
5456
6802
|
return {
|
|
5457
6803
|
status: "action-required",
|
|
@@ -5465,21 +6811,21 @@ var Updater = class _Updater {
|
|
|
5465
6811
|
// Phase: Broadcast
|
|
5466
6812
|
// Emit NeedBroadcast with the signed update + beacon service. The caller performs
|
|
5467
6813
|
// the actual on-chain announcement (or hands off to the aggregation protocol).
|
|
5468
|
-
case "Broadcast"
|
|
6814
|
+
case "Broadcast": {
|
|
5469
6815
|
return {
|
|
5470
6816
|
status: "action-required",
|
|
5471
6817
|
needs: [{
|
|
5472
6818
|
kind: "NeedBroadcast",
|
|
5473
6819
|
beaconService: this.#beaconService,
|
|
5474
|
-
signedUpdate: this.#signedUpdate
|
|
6820
|
+
signedUpdate: this.#state.signedUpdate
|
|
5475
6821
|
}]
|
|
5476
6822
|
};
|
|
5477
6823
|
}
|
|
5478
6824
|
// Phase: Complete
|
|
5479
|
-
case "Complete"
|
|
6825
|
+
case "Complete": {
|
|
5480
6826
|
return {
|
|
5481
6827
|
status: "complete",
|
|
5482
|
-
result: { signedUpdate: this.#signedUpdate }
|
|
6828
|
+
result: { signedUpdate: this.#state.signedUpdate }
|
|
5483
6829
|
};
|
|
5484
6830
|
}
|
|
5485
6831
|
}
|
|
@@ -5488,49 +6834,63 @@ var Updater = class _Updater {
|
|
|
5488
6834
|
provide(need, data) {
|
|
5489
6835
|
switch (need.kind) {
|
|
5490
6836
|
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 }
|
|
6837
|
+
if (this.#state.phase !== "Sign") {
|
|
6838
|
+
throw new import_common20.UpdateError(
|
|
6839
|
+
`Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
|
|
6840
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6841
|
+
{ phase: this.#state.phase }
|
|
5496
6842
|
);
|
|
5497
6843
|
}
|
|
5498
6844
|
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
|
|
6845
|
+
throw new import_common20.UpdateError(
|
|
6846
|
+
"NeedSigningKey requires a Signer.",
|
|
6847
|
+
import_common20.INVALID_DID_UPDATE
|
|
5508
6848
|
);
|
|
5509
6849
|
}
|
|
5510
|
-
|
|
5511
|
-
|
|
6850
|
+
const unsignedUpdate = this.#state.unsignedUpdate;
|
|
6851
|
+
const signedUpdate = _Updater.sign(
|
|
6852
|
+
this.#sourceDocument.id,
|
|
6853
|
+
unsignedUpdate,
|
|
6854
|
+
this.#verificationMethod,
|
|
6855
|
+
data
|
|
6856
|
+
);
|
|
6857
|
+
this.#state = { phase: "Fund", unsignedUpdate, signedUpdate };
|
|
5512
6858
|
break;
|
|
5513
6859
|
}
|
|
5514
6860
|
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 }
|
|
6861
|
+
if (this.#state.phase !== "Fund") {
|
|
6862
|
+
throw new import_common20.UpdateError(
|
|
6863
|
+
`Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
|
|
6864
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6865
|
+
{ phase: this.#state.phase }
|
|
5520
6866
|
);
|
|
5521
6867
|
}
|
|
5522
|
-
|
|
6868
|
+
if (data !== void 0) {
|
|
6869
|
+
const proof = data;
|
|
6870
|
+
if (typeof proof.utxoCount !== "number" || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
|
|
6871
|
+
throw new import_common20.UpdateError(
|
|
6872
|
+
`NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
|
|
6873
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6874
|
+
{ utxoCount: proof.utxoCount }
|
|
6875
|
+
);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
this.#state = {
|
|
6879
|
+
phase: "Broadcast",
|
|
6880
|
+
unsignedUpdate: this.#state.unsignedUpdate,
|
|
6881
|
+
signedUpdate: this.#state.signedUpdate
|
|
6882
|
+
};
|
|
5523
6883
|
break;
|
|
5524
6884
|
}
|
|
5525
6885
|
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 }
|
|
6886
|
+
if (this.#state.phase !== "Broadcast") {
|
|
6887
|
+
throw new import_common20.UpdateError(
|
|
6888
|
+
`Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
|
|
6889
|
+
import_common20.INVALID_DID_UPDATE,
|
|
6890
|
+
{ phase: this.#state.phase }
|
|
5531
6891
|
);
|
|
5532
6892
|
}
|
|
5533
|
-
this.#
|
|
6893
|
+
this.#state = { phase: "Complete", signedUpdate: this.#state.signedUpdate };
|
|
5534
6894
|
break;
|
|
5535
6895
|
}
|
|
5536
6896
|
}
|
|
@@ -5562,9 +6922,9 @@ var DidBtcr2 = class {
|
|
|
5562
6922
|
static create(genesisBytes, options) {
|
|
5563
6923
|
const { idType, version = 1, network = "bitcoin" } = options || {};
|
|
5564
6924
|
if (!idType) {
|
|
5565
|
-
throw new
|
|
6925
|
+
throw new import_common21.MethodError(
|
|
5566
6926
|
"idType is required for creating a did:btcr2 identifier",
|
|
5567
|
-
|
|
6927
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5568
6928
|
options
|
|
5569
6929
|
);
|
|
5570
6930
|
}
|
|
@@ -5594,7 +6954,7 @@ var DidBtcr2 = class {
|
|
|
5594
6954
|
static resolve(did, resolutionOptions = {}) {
|
|
5595
6955
|
const didComponents = Identifier.decode(did);
|
|
5596
6956
|
const sidecarData = Resolver.sidecarData(resolutionOptions.sidecar);
|
|
5597
|
-
const currentDocument = didComponents.hrp ===
|
|
6957
|
+
const currentDocument = didComponents.hrp === import_common21.IdentifierHrp.k ? Resolver.deterministic(didComponents) : null;
|
|
5598
6958
|
return new Resolver(didComponents, sidecarData, currentDocument, {
|
|
5599
6959
|
versionId: resolutionOptions.versionId,
|
|
5600
6960
|
versionTime: resolutionOptions.versionTime,
|
|
@@ -5632,39 +6992,39 @@ var DidBtcr2 = class {
|
|
|
5632
6992
|
beaconId
|
|
5633
6993
|
}) {
|
|
5634
6994
|
if (!sourceDocument.capabilityInvocation?.some((vr) => vr === verificationMethodId)) {
|
|
5635
|
-
throw new
|
|
6995
|
+
throw new import_common21.UpdateError(
|
|
5636
6996
|
"Invalid verificationMethodId: not authorized for capabilityInvocation",
|
|
5637
|
-
|
|
6997
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5638
6998
|
sourceDocument
|
|
5639
6999
|
);
|
|
5640
7000
|
}
|
|
5641
7001
|
const verificationMethod = this.getSigningMethod(sourceDocument, verificationMethodId);
|
|
5642
7002
|
if (!verificationMethod) {
|
|
5643
|
-
throw new
|
|
7003
|
+
throw new import_common21.UpdateError(
|
|
5644
7004
|
"Invalid verificationMethod: not found in source document",
|
|
5645
|
-
|
|
7005
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5646
7006
|
{ sourceDocument, verificationMethodId }
|
|
5647
7007
|
);
|
|
5648
7008
|
}
|
|
5649
7009
|
if (verificationMethod.type !== "Multikey") {
|
|
5650
|
-
throw new
|
|
7010
|
+
throw new import_common21.UpdateError(
|
|
5651
7011
|
'Invalid verificationMethod: verificationMethod.type must be "Multikey"',
|
|
5652
|
-
|
|
7012
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5653
7013
|
verificationMethod
|
|
5654
7014
|
);
|
|
5655
7015
|
}
|
|
5656
7016
|
if (verificationMethod.publicKeyMultibase?.slice(0, 4) !== "zQ3s") {
|
|
5657
|
-
throw new
|
|
7017
|
+
throw new import_common21.UpdateError(
|
|
5658
7018
|
'Invalid verificationMethodId: publicKeyMultibase prefix must start with "zQ3s"',
|
|
5659
|
-
|
|
7019
|
+
import_common21.INVALID_DID_DOCUMENT,
|
|
5660
7020
|
verificationMethod
|
|
5661
7021
|
);
|
|
5662
7022
|
}
|
|
5663
7023
|
const beaconService = sourceDocument.service.filter((service) => service.id === beaconId).filter((service) => !!service).shift();
|
|
5664
7024
|
if (!beaconService) {
|
|
5665
|
-
throw new
|
|
7025
|
+
throw new import_common21.UpdateError(
|
|
5666
7026
|
"No beacon service found for provided beaconId",
|
|
5667
|
-
|
|
7027
|
+
import_common21.INVALID_DID_UPDATE,
|
|
5668
7028
|
{ sourceDocument, beaconId }
|
|
5669
7029
|
);
|
|
5670
7030
|
}
|
|
@@ -5690,7 +7050,7 @@ var DidBtcr2 = class {
|
|
|
5690
7050
|
methodId ??= "#initialKey";
|
|
5691
7051
|
const parsedDid = import_dids2.Did.parse(didDocument.id);
|
|
5692
7052
|
if (parsedDid && parsedDid.method !== this.methodName) {
|
|
5693
|
-
throw new
|
|
7053
|
+
throw new import_common21.MethodError(`Method not supported: ${parsedDid.method}`, import_common21.METHOD_NOT_SUPPORTED, { identifier: didDocument.id });
|
|
5694
7054
|
}
|
|
5695
7055
|
const verificationMethod = didDocument.verificationMethod?.find(
|
|
5696
7056
|
(vm) => Appendix.extractDidFragment(vm.id) === (Appendix.extractDidFragment(methodId) ?? Appendix.extractDidFragment(didDocument.assertionMethod?.[0]))
|
|
@@ -5706,7 +7066,7 @@ var DidBtcr2 = class {
|
|
|
5706
7066
|
};
|
|
5707
7067
|
|
|
5708
7068
|
// src/core/resolver.ts
|
|
5709
|
-
var
|
|
7069
|
+
var import_utils12 = require("@noble/curves/utils.js");
|
|
5710
7070
|
var Resolver = class _Resolver {
|
|
5711
7071
|
// --- Immutable inputs ---
|
|
5712
7072
|
#didComponents;
|
|
@@ -5746,7 +7106,7 @@ var Resolver = class _Resolver {
|
|
|
5746
7106
|
static deterministic(didComponents) {
|
|
5747
7107
|
const genesisBytes = didComponents.genesisBytes;
|
|
5748
7108
|
const did = Identifier.encode(genesisBytes, didComponents);
|
|
5749
|
-
const { multibase } = new
|
|
7109
|
+
const { multibase } = new import_keypair6.CompressedSecp256k1PublicKey(genesisBytes);
|
|
5750
7110
|
const service = BeaconUtils.generateBeaconServices({
|
|
5751
7111
|
id: did,
|
|
5752
7112
|
publicKey: genesisBytes,
|
|
@@ -5772,14 +7132,14 @@ var Resolver = class _Resolver {
|
|
|
5772
7132
|
* @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
|
|
5773
7133
|
*/
|
|
5774
7134
|
static external(didComponents, genesisDocument) {
|
|
5775
|
-
const genesisDocumentHash = (0,
|
|
5776
|
-
if (!(0,
|
|
5777
|
-
throw new
|
|
7135
|
+
const genesisDocumentHash = (0, import_common22.canonicalHashBytes)(genesisDocument);
|
|
7136
|
+
if (!(0, import_utils12.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
7137
|
+
throw new import_common22.ResolveError(
|
|
5778
7138
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
5779
|
-
|
|
7139
|
+
import_common22.INVALID_DID_DOCUMENT,
|
|
5780
7140
|
{
|
|
5781
|
-
genesisBytes: (0,
|
|
5782
|
-
genesisDocumentHash: (0,
|
|
7141
|
+
genesisBytes: (0, import_common22.encode)(didComponents.genesisBytes, "hex"),
|
|
7142
|
+
genesisDocumentHash: (0, import_common22.encode)(genesisDocumentHash, "hex")
|
|
5783
7143
|
}
|
|
5784
7144
|
);
|
|
5785
7145
|
}
|
|
@@ -5798,12 +7158,12 @@ var Resolver = class _Resolver {
|
|
|
5798
7158
|
const updateMap = /* @__PURE__ */ new Map();
|
|
5799
7159
|
if (sidecar.updates?.length)
|
|
5800
7160
|
for (const update of sidecar.updates) {
|
|
5801
|
-
updateMap.set((0,
|
|
7161
|
+
updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5802
7162
|
}
|
|
5803
7163
|
const casMap = /* @__PURE__ */ new Map();
|
|
5804
7164
|
if (sidecar.casUpdates?.length)
|
|
5805
7165
|
for (const update of sidecar.casUpdates) {
|
|
5806
|
-
casMap.set((0,
|
|
7166
|
+
casMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
5807
7167
|
}
|
|
5808
7168
|
const smtMap = /* @__PURE__ */ new Map();
|
|
5809
7169
|
if (sidecar.smtProofs?.length)
|
|
@@ -5836,12 +7196,12 @@ var Resolver = class _Resolver {
|
|
|
5836
7196
|
}
|
|
5837
7197
|
};
|
|
5838
7198
|
for (const [update, block] of updates) {
|
|
5839
|
-
const currentDocumentHash = (0,
|
|
5840
|
-
const blocktime =
|
|
5841
|
-
response.metadata.updated =
|
|
7199
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(response.didDocument);
|
|
7200
|
+
const blocktime = import_common22.DateUtils.blocktimeToTimestamp(block.time);
|
|
7201
|
+
response.metadata.updated = import_common22.DateUtils.toISOStringNonFractional(blocktime);
|
|
5842
7202
|
response.metadata.confirmations = block.confirmations;
|
|
5843
7203
|
if (versionTime) {
|
|
5844
|
-
if (blocktime >
|
|
7204
|
+
if (blocktime > import_common22.DateUtils.dateStringToTimestamp(versionTime)) {
|
|
5845
7205
|
return response;
|
|
5846
7206
|
}
|
|
5847
7207
|
}
|
|
@@ -5849,22 +7209,22 @@ var Resolver = class _Resolver {
|
|
|
5849
7209
|
updateHashHistory.push(currentDocumentHash);
|
|
5850
7210
|
this.confirmDuplicate(update, updateHashHistory);
|
|
5851
7211
|
} else if (update.targetVersionId === currentVersionId + 1) {
|
|
5852
|
-
const sourceHashBytes = (0,
|
|
5853
|
-
if (!(0,
|
|
5854
|
-
throw new
|
|
7212
|
+
const sourceHashBytes = (0, import_common22.decode)(update.sourceHash, "base64urlnopad");
|
|
7213
|
+
if (!(0, import_utils12.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
7214
|
+
throw new import_common22.ResolveError(
|
|
5855
7215
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
5856
|
-
|
|
7216
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5857
7217
|
{
|
|
5858
7218
|
sourceHash: update.sourceHash,
|
|
5859
|
-
currentDocumentHash: (0,
|
|
7219
|
+
currentDocumentHash: (0, import_common22.encode)(currentDocumentHash, "hex")
|
|
5860
7220
|
}
|
|
5861
7221
|
);
|
|
5862
7222
|
}
|
|
5863
7223
|
response.didDocument = this.applyUpdate(response.didDocument, update);
|
|
5864
|
-
const unsignedUpdate =
|
|
5865
|
-
updateHashHistory.push((0,
|
|
7224
|
+
const unsignedUpdate = import_common22.JSONUtils.deleteKeys(update, ["proof"]);
|
|
7225
|
+
updateHashHistory.push((0, import_common22.canonicalHashBytes)(unsignedUpdate));
|
|
5866
7226
|
} else if (update.targetVersionId > currentVersionId + 1) {
|
|
5867
|
-
throw new
|
|
7227
|
+
throw new import_common22.ResolveError(
|
|
5868
7228
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
5869
7229
|
"LATE_PUBLISHING_ERROR",
|
|
5870
7230
|
{
|
|
@@ -5895,15 +7255,15 @@ var Resolver = class _Resolver {
|
|
|
5895
7255
|
*/
|
|
5896
7256
|
static confirmDuplicate(update, updateHashHistory) {
|
|
5897
7257
|
const { proof: _, ...unsignedUpdate } = update;
|
|
5898
|
-
const unsignedUpdateHash = (0,
|
|
7258
|
+
const unsignedUpdateHash = (0, import_common22.canonicalHashBytes)(unsignedUpdate);
|
|
5899
7259
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
5900
|
-
if (!(0,
|
|
5901
|
-
throw new
|
|
7260
|
+
if (!(0, import_utils12.equalBytes)(historicalUpdateHash, unsignedUpdateHash)) {
|
|
7261
|
+
throw new import_common22.ResolveError(
|
|
5902
7262
|
`Invalid duplicate: unsigned update hash does not match historical hash`,
|
|
5903
|
-
|
|
7263
|
+
import_common22.LATE_PUBLISHING_ERROR,
|
|
5904
7264
|
{
|
|
5905
|
-
unsignedUpdateHash: (0,
|
|
5906
|
-
historicalHash: (0,
|
|
7265
|
+
unsignedUpdateHash: (0, import_common22.encode)(unsignedUpdateHash, "hex"),
|
|
7266
|
+
historicalHash: (0, import_common22.encode)(historicalUpdateHash, "hex")
|
|
5907
7267
|
}
|
|
5908
7268
|
);
|
|
5909
7269
|
}
|
|
@@ -5918,42 +7278,42 @@ var Resolver = class _Resolver {
|
|
|
5918
7278
|
static applyUpdate(currentDocument, update) {
|
|
5919
7279
|
const capabilityId = update.proof?.capability;
|
|
5920
7280
|
if (!capabilityId) {
|
|
5921
|
-
throw new
|
|
7281
|
+
throw new import_common22.ResolveError("No root capability found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5922
7282
|
}
|
|
5923
7283
|
const rootCapability = Appendix.dereferenceZcapId(capabilityId);
|
|
5924
7284
|
const { invocationTarget, controller: rootController } = rootCapability;
|
|
5925
7285
|
if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
|
|
5926
|
-
throw new
|
|
7286
|
+
throw new import_common22.ResolveError(
|
|
5927
7287
|
"Invalid root capability",
|
|
5928
|
-
|
|
7288
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5929
7289
|
{ rootCapability, currentDocument }
|
|
5930
7290
|
);
|
|
5931
7291
|
}
|
|
5932
7292
|
const verificationMethodId = update.proof?.verificationMethod;
|
|
5933
7293
|
if (!verificationMethodId) {
|
|
5934
|
-
throw new
|
|
7294
|
+
throw new import_common22.ResolveError("No verificationMethod found in update", import_common22.INVALID_DID_UPDATE, update);
|
|
5935
7295
|
}
|
|
5936
7296
|
const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
|
|
5937
7297
|
const multikey = import_cryptosuite3.SchnorrMultikey.fromVerificationMethod(vm);
|
|
5938
7298
|
const cryptosuite = new import_cryptosuite3.BIP340Cryptosuite(multikey);
|
|
5939
|
-
const canonicalUpdate = (0,
|
|
7299
|
+
const canonicalUpdate = (0, import_common22.canonicalize)(update);
|
|
5940
7300
|
const diProof = new import_cryptosuite3.BIP340DataIntegrityProof(cryptosuite);
|
|
5941
7301
|
const verificationResult = diProof.verifyProof(canonicalUpdate, "capabilityInvocation");
|
|
5942
7302
|
if (!verificationResult.verified) {
|
|
5943
|
-
throw new
|
|
7303
|
+
throw new import_common22.ResolveError(
|
|
5944
7304
|
"Invalid update: proof not verified",
|
|
5945
|
-
|
|
7305
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5946
7306
|
verificationResult
|
|
5947
7307
|
);
|
|
5948
7308
|
}
|
|
5949
|
-
const updatedDocument =
|
|
7309
|
+
const updatedDocument = import_common22.JSONPatch.apply(currentDocument, update.patch);
|
|
5950
7310
|
DidDocument.validate(updatedDocument);
|
|
5951
|
-
const currentDocumentHash = (0,
|
|
5952
|
-
const updateTargetHash = (0,
|
|
5953
|
-
if (!(0,
|
|
5954
|
-
throw new
|
|
7311
|
+
const currentDocumentHash = (0, import_common22.canonicalHashBytes)(updatedDocument);
|
|
7312
|
+
const updateTargetHash = (0, import_common22.decode)(update.targetHash);
|
|
7313
|
+
if (!(0, import_utils12.equalBytes)(updateTargetHash, currentDocumentHash)) {
|
|
7314
|
+
throw new import_common22.ResolveError(
|
|
5955
7315
|
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
5956
|
-
|
|
7316
|
+
import_common22.INVALID_DID_UPDATE,
|
|
5957
7317
|
{ updateTargetHash, currentDocumentHash }
|
|
5958
7318
|
);
|
|
5959
7319
|
}
|
|
@@ -5981,7 +7341,7 @@ var Resolver = class _Resolver {
|
|
|
5981
7341
|
this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
|
|
5982
7342
|
continue;
|
|
5983
7343
|
}
|
|
5984
|
-
const genesisHash = (0,
|
|
7344
|
+
const genesisHash = (0, import_common22.encode)(this.#didComponents.genesisBytes, "hex");
|
|
5985
7345
|
return {
|
|
5986
7346
|
status: "action-required",
|
|
5987
7347
|
needs: [{ kind: "NeedGenesisDocument", genesisHash }]
|
|
@@ -6085,21 +7445,21 @@ var Resolver = class _Resolver {
|
|
|
6085
7445
|
}
|
|
6086
7446
|
case "NeedCASAnnouncement": {
|
|
6087
7447
|
const announcement = data;
|
|
6088
|
-
this.#sidecarData.casMap.set((0,
|
|
7448
|
+
this.#sidecarData.casMap.set((0, import_common22.canonicalHash)(announcement, { encoding: "hex" }), announcement);
|
|
6089
7449
|
break;
|
|
6090
7450
|
}
|
|
6091
7451
|
case "NeedSignedUpdate": {
|
|
6092
7452
|
const update = data;
|
|
6093
|
-
this.#sidecarData.updateMap.set((0,
|
|
7453
|
+
this.#sidecarData.updateMap.set((0, import_common22.canonicalHash)(update, { encoding: "hex" }), update);
|
|
6094
7454
|
break;
|
|
6095
7455
|
}
|
|
6096
7456
|
case "NeedSMTProof": {
|
|
6097
7457
|
const smtNeed = need;
|
|
6098
7458
|
const proof = data;
|
|
6099
7459
|
if (proof.id !== smtNeed.smtRootHash) {
|
|
6100
|
-
throw new
|
|
7460
|
+
throw new import_common22.ResolveError(
|
|
6101
7461
|
`SMT proof root hash mismatch: expected ${smtNeed.smtRootHash}, got ${proof.id}`,
|
|
6102
|
-
|
|
7462
|
+
import_common22.INVALID_DID_UPDATE,
|
|
6103
7463
|
{ expected: smtNeed.smtRootHash, actual: proof.id }
|
|
6104
7464
|
);
|
|
6105
7465
|
}
|
|
@@ -6111,12 +7471,12 @@ var Resolver = class _Resolver {
|
|
|
6111
7471
|
};
|
|
6112
7472
|
|
|
6113
7473
|
// src/utils/did-document-builder.ts
|
|
6114
|
-
var
|
|
7474
|
+
var import_common23 = require("@did-btcr2/common");
|
|
6115
7475
|
var DidDocumentBuilder = class {
|
|
6116
7476
|
document = {};
|
|
6117
7477
|
constructor(initialDocument) {
|
|
6118
7478
|
if (!initialDocument.id) {
|
|
6119
|
-
throw new
|
|
7479
|
+
throw new import_common23.DidDocumentError('Missing required "id" property', import_common23.INVALID_DID_DOCUMENT, initialDocument);
|
|
6120
7480
|
}
|
|
6121
7481
|
this.document.id = initialDocument.id;
|
|
6122
7482
|
this.document.verificationMethod = initialDocument.verificationMethod ?? [];
|
|
@@ -6198,7 +7558,9 @@ var DidDocumentBuilder = class {
|
|
|
6198
7558
|
CONSOLE_LOGGER,
|
|
6199
7559
|
DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
|
|
6200
7560
|
DEFAULT_BROADCAST_LOOKBACK_MS,
|
|
7561
|
+
DEFAULT_CLOCK_SKEW_SEC,
|
|
6201
7562
|
DEFAULT_MAX_UPDATE_SIZE_BYTES,
|
|
7563
|
+
DEFAULT_NONCE_LEN_BYTES,
|
|
6202
7564
|
DEFAULT_NOSTR_RELAYS,
|
|
6203
7565
|
DID_REGEX,
|
|
6204
7566
|
DISTRIBUTE_AGGREGATED_DATA,
|
|
@@ -6209,16 +7571,31 @@ var DidDocumentBuilder = class {
|
|
|
6209
7571
|
DidVerificationMethod,
|
|
6210
7572
|
Document,
|
|
6211
7573
|
GenesisDocument,
|
|
7574
|
+
HTTP_ENVELOPE_VERSION,
|
|
7575
|
+
HTTP_ROUTE,
|
|
7576
|
+
HttpClientTransport,
|
|
7577
|
+
HttpServerTransport,
|
|
7578
|
+
HttpTransportError,
|
|
6212
7579
|
ID_PLACEHOLDER_VALUE,
|
|
6213
7580
|
Identifier,
|
|
7581
|
+
InMemoryRateLimitStore,
|
|
7582
|
+
InboxBuffer,
|
|
6214
7583
|
NONCE_CONTRIBUTION,
|
|
7584
|
+
NonceCache,
|
|
6215
7585
|
NostrTransport,
|
|
7586
|
+
P2PKH_BEACON_TX_VSIZE,
|
|
7587
|
+
P2TR_BEACON_TX_VSIZE,
|
|
7588
|
+
P2WPKH_BEACON_TX_VSIZE,
|
|
6216
7589
|
ParticipantCohortPhase,
|
|
7590
|
+
REQUEST_AUTH_SCHEME,
|
|
7591
|
+
RateLimiter,
|
|
6217
7592
|
Resolver,
|
|
6218
7593
|
SIGNATURE_AUTHORIZATION,
|
|
6219
7594
|
SILENT_LOGGER,
|
|
7595
|
+
SINGLETON_BEACON_TX_VSIZE,
|
|
6220
7596
|
SMTBeacon,
|
|
6221
7597
|
SMTBeaconError,
|
|
7598
|
+
SSE_EVENT,
|
|
6222
7599
|
SUBMIT_UPDATE,
|
|
6223
7600
|
ServiceCohortPhase,
|
|
6224
7601
|
SigningSessionError,
|
|
@@ -6233,6 +7610,7 @@ var DidDocumentBuilder = class {
|
|
|
6233
7610
|
Updater,
|
|
6234
7611
|
VALIDATION_ACK,
|
|
6235
7612
|
buildAggregationBeaconTx,
|
|
7613
|
+
buildRequestAuth,
|
|
6236
7614
|
createAggregatedNonceMessage,
|
|
6237
7615
|
createAuthorizationRequestMessage,
|
|
6238
7616
|
createCohortAdvertMessage,
|
|
@@ -6244,6 +7622,11 @@ var DidDocumentBuilder = class {
|
|
|
6244
7622
|
createSignatureAuthorizationMessage,
|
|
6245
7623
|
createSubmitUpdateMessage,
|
|
6246
7624
|
createValidationAckMessage,
|
|
7625
|
+
defaultReconnectBackoff,
|
|
7626
|
+
deriveSingletonAddress,
|
|
7627
|
+
detectSingletonScriptKind,
|
|
7628
|
+
formatSseComment,
|
|
7629
|
+
formatSseEvent,
|
|
6247
7630
|
getBeaconStrategy,
|
|
6248
7631
|
isAggregatedNonceMessage,
|
|
6249
7632
|
isAggregationMessageType,
|
|
@@ -6260,6 +7643,13 @@ var DidDocumentBuilder = class {
|
|
|
6260
7643
|
isSubmitUpdateMessage,
|
|
6261
7644
|
isUpdateMessageType,
|
|
6262
7645
|
isValidationAckMessage,
|
|
7646
|
+
normalizeForWire,
|
|
6263
7647
|
opReturnScript,
|
|
6264
|
-
|
|
7648
|
+
parseRequestAuth,
|
|
7649
|
+
parseSseStream,
|
|
7650
|
+
registerBeaconStrategy,
|
|
7651
|
+
reviveFromWire,
|
|
7652
|
+
signEnvelope,
|
|
7653
|
+
verifyEnvelope,
|
|
7654
|
+
verifyRequestAuth
|
|
6265
7655
|
});
|