@did-btcr2/api 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +22152 -34966
- package/dist/browser.mjs +22152 -34966
- package/dist/cjs/index.js +168 -41
- package/dist/esm/api.js +11 -11
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/cas.js +61 -4
- package/dist/esm/cas.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/{kms.js → key-manager.js} +21 -12
- package/dist/esm/key-manager.js.map +1 -0
- package/dist/esm/method.js +81 -20
- package/dist/esm/method.js.map +1 -1
- package/dist/types/api.d.ts +10 -5
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/cas.d.ts +34 -1
- package/dist/types/cas.d.ts.map +1 -1
- package/dist/types/crypto.d.ts +2 -2
- package/dist/types/crypto.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/{kms.d.ts → key-manager.d.ts} +19 -11
- package/dist/types/key-manager.d.ts.map +1 -0
- package/dist/types/method.d.ts +28 -12
- package/dist/types/method.d.ts.map +1 -1
- package/dist/types/types.d.ts +1 -8
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +9 -12
- package/src/api.ts +14 -13
- package/src/cas.ts +84 -5
- package/src/crypto.ts +2 -2
- package/src/index.ts +1 -1
- package/src/{kms.ts → key-manager.ts} +25 -16
- package/src/method.ts +98 -22
- package/src/types.ts +1 -8
- package/dist/esm/kms.js.map +0 -1
- package/dist/types/kms.d.ts.map +0 -1
package/dist/types/types.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { HttpExecutor, NetworkName, RestConfig, RpcConfig } from '@did-btcr2/bitcoin';
|
|
2
|
-
import type { KeyManager } from '@did-btcr2/
|
|
2
|
+
import type { KeyManager } from '@did-btcr2/key-manager';
|
|
3
3
|
import type { Btcr2DidDocument } from '@did-btcr2/method';
|
|
4
4
|
import type { DidResolutionResult } from '@web5/dids';
|
|
5
5
|
import type { CasConfig } from './cas.js';
|
|
6
6
|
/**
|
|
7
7
|
* Pluggable logger interface. All methods are optional-call; the default
|
|
8
8
|
* implementation is a silent no-op.
|
|
9
|
-
* @public
|
|
10
9
|
*/
|
|
11
10
|
export type Logger = {
|
|
12
11
|
debug(message: string, ...args: unknown[]): void;
|
|
@@ -21,7 +20,6 @@ export type Logger = {
|
|
|
21
20
|
* than a union. This local alias provides compile-time safety at the API
|
|
22
21
|
* facade level. Upstream runtime validation in `Identifier.encode()` still
|
|
23
22
|
* catches invalid values.
|
|
24
|
-
* @public
|
|
25
23
|
*/
|
|
26
24
|
export type IdType = 'KEY' | 'EXTERNAL';
|
|
27
25
|
/**
|
|
@@ -35,14 +33,12 @@ export type IdType = 'KEY' | 'EXTERNAL';
|
|
|
35
33
|
* api.resolveDid(did); // OK
|
|
36
34
|
* api.btc.getTransaction(did); // Type error — DidString is not TxId
|
|
37
35
|
* ```
|
|
38
|
-
* @public
|
|
39
36
|
*/
|
|
40
37
|
export type DidString = string & {
|
|
41
38
|
readonly __brand: 'DidString';
|
|
42
39
|
};
|
|
43
40
|
/**
|
|
44
41
|
* A branded string representing a Bitcoin transaction ID (64-char hex).
|
|
45
|
-
* @public
|
|
46
42
|
*/
|
|
47
43
|
export type TxId = string & {
|
|
48
44
|
readonly __brand: 'TxId';
|
|
@@ -61,7 +57,6 @@ export type TxId = string & {
|
|
|
61
57
|
* console.log(result.error, result.errorMessage);
|
|
62
58
|
* }
|
|
63
59
|
* ```
|
|
64
|
-
* @public
|
|
65
60
|
*/
|
|
66
61
|
export type ResolutionResult = {
|
|
67
62
|
ok: true;
|
|
@@ -91,7 +86,6 @@ export type ResolutionResult = {
|
|
|
91
86
|
* // Use regtest with custom RPC credentials, default REST
|
|
92
87
|
* { network: 'regtest', rpc: { host: 'http://mynode:18443', username: 'u', password: 'p' } }
|
|
93
88
|
* ```
|
|
94
|
-
* @public
|
|
95
89
|
*/
|
|
96
90
|
export type BitcoinApiConfig = {
|
|
97
91
|
/** Bitcoin network name (e.g., 'regtest', 'testnet4', 'bitcoin'). */
|
|
@@ -116,7 +110,6 @@ export type BitcoinApiConfig = {
|
|
|
116
110
|
};
|
|
117
111
|
/**
|
|
118
112
|
* Top-level API configuration options.
|
|
119
|
-
* @public
|
|
120
113
|
*/
|
|
121
114
|
export type ApiConfig = {
|
|
122
115
|
btc?: BitcoinApiConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAExC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;IAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,GACzH;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAElF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,OAAO,EAAE,WAAW,CAAC;IACrB,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,+DAA+D;IAC/D,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@did-btcr2/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SDK for accessing the did:btcr2 method functionality.",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -65,10 +65,9 @@
|
|
|
65
65
|
"api"
|
|
66
66
|
],
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@bitcoinerlab/secp256k1": "^1.2.0",
|
|
69
68
|
"@helia/strings": "^4.0.2",
|
|
70
|
-
"@noble/curves": "^
|
|
71
|
-
"@noble/hashes": "^
|
|
69
|
+
"@noble/curves": "^2.0.1",
|
|
70
|
+
"@noble/hashes": "^2.0.1",
|
|
72
71
|
"@noble/secp256k1": "^2.1.0",
|
|
73
72
|
"@scure/base": "^1.1.9",
|
|
74
73
|
"@scure/bip32": "^1.5.0",
|
|
@@ -77,19 +76,17 @@
|
|
|
77
76
|
"@web5/common": "^1.1.0",
|
|
78
77
|
"@web5/crypto": "^1.0.6",
|
|
79
78
|
"@web5/dids": "^1.2.0",
|
|
80
|
-
"bitcoinjs-lib": "7.0.0-rc.0",
|
|
81
79
|
"canonicalize": "^2.1.0",
|
|
82
80
|
"dotenv": "^16.5.0",
|
|
83
81
|
"helia": "^5.2.1",
|
|
84
82
|
"multiformats": "^13.3.1",
|
|
85
83
|
"nostr-tools": "^2.15.0",
|
|
86
|
-
"
|
|
87
|
-
"@did-btcr2/
|
|
88
|
-
"@did-btcr2/
|
|
89
|
-
"@did-btcr2/cryptosuite": "^
|
|
90
|
-
"@did-btcr2/
|
|
91
|
-
"@did-btcr2/
|
|
92
|
-
"@did-btcr2/method": "^0.26.0"
|
|
84
|
+
"@did-btcr2/common": "^9.1.0",
|
|
85
|
+
"@did-btcr2/keypair": "^0.13.0",
|
|
86
|
+
"@did-btcr2/method": "^0.32.0",
|
|
87
|
+
"@did-btcr2/cryptosuite": "^8.0.0",
|
|
88
|
+
"@did-btcr2/bitcoin": "^0.6.0",
|
|
89
|
+
"@did-btcr2/key-manager": "^0.6.0"
|
|
93
90
|
},
|
|
94
91
|
"devDependencies": {
|
|
95
92
|
"@eslint/js": "^9.22.0",
|
package/src/api.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import type { NetworkName } from '@did-btcr2/bitcoin';
|
|
2
2
|
import type { DocumentBytes, KeyBytes, PatchOperation } from '@did-btcr2/common';
|
|
3
3
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
4
5
|
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
5
|
-
import type { KeyIdentifier } from '@did-btcr2/
|
|
6
|
+
import type { KeyIdentifier } from '@did-btcr2/key-manager';
|
|
6
7
|
import type { Btcr2DidDocument, DidCreateOptions, ResolutionOptions } from '@did-btcr2/method';
|
|
7
8
|
import type { DidResolutionResult } from '@web5/dids';
|
|
8
9
|
import { BitcoinApi } from './bitcoin.js';
|
|
9
|
-
import { CasApi, type CasConfig } from './cas.js';
|
|
10
|
+
import { CasApi, DEFAULT_CAS_GATEWAY, type CasConfig } from './cas.js';
|
|
10
11
|
import { CryptoApi } from './crypto.js';
|
|
11
12
|
import { DidApi } from './did.js';
|
|
12
13
|
import { assertString, NOOP_LOGGER } from './helpers.js';
|
|
13
|
-
import { KeyManagerApi } from './
|
|
14
|
+
import { KeyManagerApi } from './key-manager.js';
|
|
14
15
|
import { DidMethodApi } from './method.js';
|
|
15
16
|
import type { ApiConfig, BitcoinApiConfig, Logger, ResolutionResult } from './types.js';
|
|
16
17
|
|
|
@@ -67,19 +68,16 @@ export class DidBtcr2Api {
|
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* CAS API sub-facade (lazily initialized).
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
*
|
|
72
|
+
* When no `cas` config was provided to the constructor, defaults to a
|
|
73
|
+
* read-only {@link HttpGatewayCasExecutor} backed by the public IPFS
|
|
74
|
+
* gateway (`https://ipfs.io`). Override via `createApi({ cas: { ... } })`.
|
|
75
|
+
* @throws {Error} If the instance has been disposed.
|
|
72
76
|
*/
|
|
73
77
|
get cas(): CasApi {
|
|
74
78
|
this.#assertNotDisposed();
|
|
75
79
|
if (!this.#cas) {
|
|
76
|
-
|
|
77
|
-
throw new Error(
|
|
78
|
-
'CAS not configured. Pass a cas config to createApi(), e.g.: '
|
|
79
|
-
+ 'createApi({ cas: { helia: await createHelia() } })'
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
this.#cas = new CasApi(this.#casConfig);
|
|
80
|
+
this.#cas = new CasApi(this.#casConfig ?? { gateway: DEFAULT_CAS_GATEWAY });
|
|
83
81
|
}
|
|
84
82
|
return this.#cas;
|
|
85
83
|
}
|
|
@@ -93,7 +91,7 @@ export class DidBtcr2Api {
|
|
|
93
91
|
if (!this.#btcr2) {
|
|
94
92
|
this.#btcr2 = new DidMethodApi(
|
|
95
93
|
this.#btcConfig ? this.btc : undefined,
|
|
96
|
-
this
|
|
94
|
+
this.cas,
|
|
97
95
|
this.#log
|
|
98
96
|
);
|
|
99
97
|
}
|
|
@@ -207,6 +205,7 @@ export class DidBtcr2Api {
|
|
|
207
205
|
patches,
|
|
208
206
|
verificationMethodId,
|
|
209
207
|
beaconId,
|
|
208
|
+
signer,
|
|
210
209
|
sourceDocument,
|
|
211
210
|
sourceVersionId,
|
|
212
211
|
}: {
|
|
@@ -214,6 +213,7 @@ export class DidBtcr2Api {
|
|
|
214
213
|
patches: PatchOperation[];
|
|
215
214
|
verificationMethodId: string;
|
|
216
215
|
beaconId: string;
|
|
216
|
+
signer: Signer;
|
|
217
217
|
sourceDocument?: Btcr2DidDocument;
|
|
218
218
|
sourceVersionId?: number;
|
|
219
219
|
}): Promise<SignedBTCR2Update> {
|
|
@@ -260,6 +260,7 @@ export class DidBtcr2Api {
|
|
|
260
260
|
sourceVersionId : versionId,
|
|
261
261
|
verificationMethodId,
|
|
262
262
|
beaconId,
|
|
263
|
+
signer,
|
|
263
264
|
});
|
|
264
265
|
}
|
|
265
266
|
|
package/src/cas.ts
CHANGED
|
@@ -6,6 +6,9 @@ import * as raw from 'multiformats/codecs/raw';
|
|
|
6
6
|
import { create as createDigest } from 'multiformats/hashes/digest';
|
|
7
7
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
8
8
|
|
|
9
|
+
/** Default IPFS HTTP gateway used for CAS reads when no CAS config is provided. */
|
|
10
|
+
export const DEFAULT_CAS_GATEWAY = 'https://ipfs.io';
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Executor interface for content-addressed storage.
|
|
11
14
|
*
|
|
@@ -55,15 +58,70 @@ export class IpfsCasExecutor implements CasExecutor {
|
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Read-only {@link CasExecutor} backed by an IPFS HTTP gateway.
|
|
63
|
+
*
|
|
64
|
+
* Converts the base64url SHA-256 hash to a CIDv1 (raw codec) and fetches
|
|
65
|
+
* the raw block via the
|
|
66
|
+
* {@link https://specs.ipfs.tech/http-gateways/trustless-gateway/ | Trustless Gateway}
|
|
67
|
+
* protocol.
|
|
68
|
+
*
|
|
69
|
+
* Publishing is not supported — use {@link IpfsCasExecutor} with a Helia
|
|
70
|
+
* instance for writes.
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
export class HttpGatewayCasExecutor implements CasExecutor {
|
|
74
|
+
readonly #gatewayUrl: string;
|
|
75
|
+
|
|
76
|
+
constructor(gatewayUrl: string) {
|
|
77
|
+
this.#gatewayUrl = gatewayUrl.replace(/\/+$/, '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async retrieve(hash: string): Promise<Uint8Array | null> {
|
|
81
|
+
const hashBytes = decodeHash(hash, 'base64urlnopad');
|
|
82
|
+
const cid = CID.create(1, raw.code, createDigest(sha256.code, hashBytes));
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(`${this.#gatewayUrl}/ipfs/${cid.toString()}?format=raw`, {
|
|
85
|
+
headers : { Accept: 'application/vnd.ipld.raw' },
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) return null;
|
|
88
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async publish(): Promise<string> {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'HttpGatewayCasExecutor is read-only. '
|
|
97
|
+
+ 'Publishing requires a full IPFS node (use IpfsCasExecutor with Helia).'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Default timeout (ms) for CAS operations. */
|
|
103
|
+
export const DEFAULT_CAS_TIMEOUT_MS = 30_000;
|
|
104
|
+
|
|
58
105
|
/**
|
|
59
106
|
* Configuration for the CAS (Content-Addressed Storage) driver.
|
|
107
|
+
*
|
|
108
|
+
* Provide exactly one of `executor`, `helia`, or `gateway`.
|
|
109
|
+
* Priority if multiple are set: `executor` > `helia` > `gateway`.
|
|
60
110
|
* @public
|
|
61
111
|
*/
|
|
62
112
|
export type CasConfig = {
|
|
63
|
-
/** Custom executor implementation (overrides
|
|
113
|
+
/** Custom executor implementation (overrides all other options). */
|
|
64
114
|
executor?: CasExecutor;
|
|
65
115
|
/** Pre-existing Helia instance for the default IPFS executor. */
|
|
66
116
|
helia?: Helia;
|
|
117
|
+
/** IPFS HTTP gateway URL for read-only CAS access (e.g. `'https://ipfs.io'`). */
|
|
118
|
+
gateway?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Timeout in milliseconds for CAS operations. Prevents indefinite hangs
|
|
121
|
+
* when a Helia DHT lookup or gateway request stalls. Default: 30 000 ms.
|
|
122
|
+
* Set to `0` to disable.
|
|
123
|
+
*/
|
|
124
|
+
timeoutMs?: number;
|
|
67
125
|
};
|
|
68
126
|
|
|
69
127
|
/**
|
|
@@ -81,18 +139,22 @@ export type CasConfig = {
|
|
|
81
139
|
*/
|
|
82
140
|
export class CasApi {
|
|
83
141
|
readonly #executor: CasExecutor;
|
|
142
|
+
readonly #timeoutMs: number;
|
|
84
143
|
|
|
85
144
|
constructor(config: CasConfig) {
|
|
86
145
|
if (config.executor) {
|
|
87
146
|
this.#executor = config.executor;
|
|
88
147
|
} else if (config.helia) {
|
|
89
148
|
this.#executor = new IpfsCasExecutor(config.helia);
|
|
149
|
+
} else if (config.gateway) {
|
|
150
|
+
this.#executor = new HttpGatewayCasExecutor(config.gateway);
|
|
90
151
|
} else {
|
|
91
152
|
throw new Error(
|
|
92
|
-
'CAS configuration requires
|
|
93
|
-
+ 'Example: createApi({ cas: {
|
|
153
|
+
'CAS configuration requires an executor, Helia instance, or gateway URL. '
|
|
154
|
+
+ 'Example: createApi({ cas: { gateway: \'https://ipfs.io\' } })'
|
|
94
155
|
);
|
|
95
156
|
}
|
|
157
|
+
this.#timeoutMs = config.timeoutMs ?? DEFAULT_CAS_TIMEOUT_MS;
|
|
96
158
|
}
|
|
97
159
|
|
|
98
160
|
/**
|
|
@@ -102,7 +164,7 @@ export class CasApi {
|
|
|
102
164
|
*/
|
|
103
165
|
async retrieve(hashBytes: HashBytes): Promise<object | null> {
|
|
104
166
|
const hash = encodeHash(hashBytes, 'base64urlnopad');
|
|
105
|
-
const bytes = await this.#executor.retrieve(hash);
|
|
167
|
+
const bytes = await this.#withTimeout(this.#executor.retrieve(hash));
|
|
106
168
|
if (!bytes) return null;
|
|
107
169
|
return JSON.parse(new TextDecoder().decode(bytes)) as object;
|
|
108
170
|
}
|
|
@@ -116,6 +178,23 @@ export class CasApi {
|
|
|
116
178
|
*/
|
|
117
179
|
async publish(object: object): Promise<string> {
|
|
118
180
|
const bytes = new TextEncoder().encode(canonicalize(object as Record<string, any>));
|
|
119
|
-
return await this.#executor.publish(bytes);
|
|
181
|
+
return await this.#withTimeout(this.#executor.publish(bytes));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Wraps a promise with a timeout. If `#timeoutMs` is 0, no timeout is applied.
|
|
186
|
+
*/
|
|
187
|
+
#withTimeout<T>(promise: Promise<T>): Promise<T> {
|
|
188
|
+
if (!this.#timeoutMs) return promise;
|
|
189
|
+
return new Promise<T>((resolve, reject) => {
|
|
190
|
+
const timer = setTimeout(
|
|
191
|
+
() => reject(new Error(`CAS operation timed out after ${this.#timeoutMs}ms`)),
|
|
192
|
+
this.#timeoutMs
|
|
193
|
+
);
|
|
194
|
+
promise.then(
|
|
195
|
+
(val) => { clearTimeout(timer); resolve(val); },
|
|
196
|
+
(err) => { clearTimeout(timer); reject(err); },
|
|
197
|
+
);
|
|
198
|
+
});
|
|
120
199
|
}
|
|
121
200
|
}
|
package/src/crypto.ts
CHANGED
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
SchnorrMultikey
|
|
16
16
|
} from '@did-btcr2/cryptosuite';
|
|
17
17
|
import { CompressedSecp256k1PublicKey, SchnorrKeyPair, Secp256k1SecretKey } from '@did-btcr2/keypair';
|
|
18
|
-
import type { KeyIdentifier } from '@did-btcr2/
|
|
18
|
+
import type { KeyIdentifier } from '@did-btcr2/key-manager';
|
|
19
19
|
import type { DidVerificationMethod } from '@web5/dids';
|
|
20
|
-
import type { KeyManagerApi } from './
|
|
20
|
+
import type { KeyManagerApi } from './key-manager.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Schnorr keypair operations.
|
package/src/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export * from './types.js';
|
|
|
30
30
|
export * from './helpers.js';
|
|
31
31
|
export * from './bitcoin.js';
|
|
32
32
|
export * from './cas.js';
|
|
33
|
-
export * from './
|
|
33
|
+
export * from './key-manager.js';
|
|
34
34
|
export * from './crypto.js';
|
|
35
35
|
export * from './did.js';
|
|
36
36
|
export * from './method.js';
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import type { Bytes, HashBytes, SignatureBytes } from '@did-btcr2/common';
|
|
2
2
|
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
3
|
-
import type {
|
|
4
|
-
KeyIdentifier,
|
|
5
|
-
KeyManager} from '@did-btcr2/kms';
|
|
6
3
|
import {
|
|
4
|
+
type KeyIdentifier,
|
|
5
|
+
type KeyManager,
|
|
7
6
|
type GenerateKeyOptions,
|
|
8
7
|
type ImportKeyOptions,
|
|
9
|
-
|
|
8
|
+
LocalKeyManager,
|
|
10
9
|
type SignOptions,
|
|
11
|
-
|
|
10
|
+
type VerifyOptions,
|
|
11
|
+
} from '@did-btcr2/key-manager';
|
|
12
12
|
import { assertBytes } from './helpers.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Key management operations sub-facade.
|
|
16
16
|
*
|
|
17
|
-
* Wraps
|
|
18
|
-
* {@link
|
|
19
|
-
*
|
|
17
|
+
* Wraps any {@link KeyManager} interface implementation. By default uses the
|
|
18
|
+
* bundled {@link LocalKeyManager} (in-process reference implementation); a
|
|
19
|
+
* custom implementation (AWS KMS, GCP KMS, HashiCorp Vault, HSM, etc.) can
|
|
20
|
+
* be injected via {@link ApiConfig}.
|
|
21
|
+
*
|
|
22
|
+
* The field is named `kms` because that's the category label callers use
|
|
23
|
+
* conversationally ("plug in your KMS"); the actual contract is the
|
|
24
|
+
* {@link KeyManager} interface.
|
|
20
25
|
* @public
|
|
21
26
|
*/
|
|
22
27
|
export class KeyManagerApi {
|
|
@@ -25,7 +30,7 @@ export class KeyManagerApi {
|
|
|
25
30
|
|
|
26
31
|
/** Create a new KeyManagerApi, optionally backed by a custom KeyManager. */
|
|
27
32
|
constructor(kms?: KeyManager) {
|
|
28
|
-
this.kms = kms ?? new
|
|
33
|
+
this.kms = kms ?? new LocalKeyManager();
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/** Generate a new key directly in the KMS. */
|
|
@@ -50,14 +55,18 @@ export class KeyManagerApi {
|
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
57
|
* Export a Schnorr keypair from the KMS.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
58
|
+
* Routes through the KeyManager's declared capability (`canExport`) rather
|
|
59
|
+
* than an `instanceof LocalKeyManager` check, so third-party adapters can
|
|
60
|
+
* opt in to export support without coupling to a specific implementation.
|
|
61
|
+
* External adapters (AWS, Vault, HSM) typically advertise `canExport: false`.
|
|
62
|
+
* @throws {Error} If the backing KeyManager does not advertise canExport=true,
|
|
63
|
+
* or omits the optional `exportKey` method.
|
|
55
64
|
*/
|
|
56
65
|
export(id: KeyIdentifier): SchnorrKeyPair {
|
|
57
|
-
if (!
|
|
66
|
+
if (!this.kms.canExport || !this.kms.exportKey) {
|
|
58
67
|
throw new Error(
|
|
59
68
|
'Key export is not supported by the current KeyManager implementation. '
|
|
60
|
-
+ '
|
|
69
|
+
+ 'The adapter must advertise `canExport: true` and provide an `exportKey` method.'
|
|
61
70
|
);
|
|
62
71
|
}
|
|
63
72
|
return this.kms.exportKey(id);
|
|
@@ -77,15 +86,15 @@ export class KeyManagerApi {
|
|
|
77
86
|
* Sign data via the KMS.
|
|
78
87
|
* @param data The data to sign (must be non-empty).
|
|
79
88
|
* @param id Optional key identifier; uses the active key if omitted.
|
|
80
|
-
* @param options Signing options
|
|
89
|
+
* @param options Signing options. Defaults: `scheme: 'bip340'`.
|
|
81
90
|
*/
|
|
82
91
|
sign(data: Bytes, id?: KeyIdentifier, options?: SignOptions): SignatureBytes {
|
|
83
92
|
assertBytes(data, 'data');
|
|
84
93
|
return this.kms.sign(data, id, options);
|
|
85
94
|
}
|
|
86
95
|
|
|
87
|
-
/** Verify a signature via the KMS. */
|
|
88
|
-
verify(signature: SignatureBytes, data: Bytes, id?: KeyIdentifier, options?:
|
|
96
|
+
/** Verify a signature via the KMS. Defaults: `scheme: 'bip340'`. */
|
|
97
|
+
verify(signature: SignatureBytes, data: Bytes, id?: KeyIdentifier, options?: VerifyOptions): boolean {
|
|
89
98
|
return this.kms.verify(signature, data, id, options);
|
|
90
99
|
}
|
|
91
100
|
|
package/src/method.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import type { DocumentBytes,
|
|
3
|
-
import { decode as decodeHash, IdentifierTypes, NotImplementedError } from '@did-btcr2/common';
|
|
2
|
+
import type { DocumentBytes, KeyBytes, PatchOperation } from '@did-btcr2/common';
|
|
3
|
+
import { decode as decodeHash, IdentifierTypes, INVALID_DID_UPDATE, NotImplementedError, UpdateError } from '@did-btcr2/common';
|
|
4
4
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
5
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
6
|
import type { Btcr2DidDocument, CASAnnouncement, DidCreateOptions, NeedCASAnnouncement, NeedGenesisDocument, NeedSignedUpdate, ResolutionOptions } from '@did-btcr2/method';
|
|
6
|
-
import { BeaconSignalDiscovery, DidBtcr2 } from '@did-btcr2/method';
|
|
7
|
+
import { BeaconFactory, BeaconSignalDiscovery, DidBtcr2 } from '@did-btcr2/method';
|
|
7
8
|
import type { DidResolutionResult, DidVerificationMethod } from '@web5/dids';
|
|
8
9
|
import type { BitcoinApi } from './bitcoin.js';
|
|
9
10
|
import type { CasApi } from './cas.js';
|
|
@@ -162,8 +163,16 @@ export class DidMethodApi {
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
/**
|
|
165
|
-
* Update an existing DID document
|
|
166
|
-
*
|
|
166
|
+
* Update an existing DID document by driving the sans-I/O {@link Updater} state
|
|
167
|
+
* machine (from @did-btcr2/method). This method handles the I/O side:
|
|
168
|
+
* - Signing: supplies the {@link Signer} to `NeedSigningKey`.
|
|
169
|
+
* - Broadcast: establishes a beacon via {@link BeaconFactory} and calls
|
|
170
|
+
* `broadcastSignal()` with the bitcoin connection configured on the API.
|
|
171
|
+
*
|
|
172
|
+
* For multi-party aggregation of SMT/CAS beacons, the caller should drive the
|
|
173
|
+
* Updater directly and delegate `NeedBroadcast` to the aggregation runner
|
|
174
|
+
* rather than using this high-level method.
|
|
175
|
+
*
|
|
167
176
|
* @param params The update parameters.
|
|
168
177
|
* @returns The signed update.
|
|
169
178
|
*/
|
|
@@ -173,7 +182,7 @@ export class DidMethodApi {
|
|
|
173
182
|
sourceVersionId,
|
|
174
183
|
verificationMethodId,
|
|
175
184
|
beaconId,
|
|
176
|
-
|
|
185
|
+
signer,
|
|
177
186
|
bitcoin,
|
|
178
187
|
}: {
|
|
179
188
|
sourceDocument: Btcr2DidDocument;
|
|
@@ -181,19 +190,73 @@ export class DidMethodApi {
|
|
|
181
190
|
sourceVersionId: number;
|
|
182
191
|
verificationMethodId: string;
|
|
183
192
|
beaconId: string;
|
|
184
|
-
|
|
193
|
+
signer: Signer;
|
|
185
194
|
bitcoin?: BitcoinConnection;
|
|
186
195
|
}): Promise<SignedBTCR2Update> {
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
// Bitcoin connection resolution order: per-call `bitcoin` param wins over the
|
|
197
|
+
// BitcoinApi injected at DidBtcr2Api construction time. One of the two must
|
|
198
|
+
// be present; this can't be encoded in the type system, so it's a runtime check.
|
|
199
|
+
const btcConnection = bitcoin ?? this.#btc?.connection;
|
|
200
|
+
if(!btcConnection) {
|
|
201
|
+
throw new UpdateError(
|
|
202
|
+
'Bitcoin connection required for update. Pass a configured `bitcoin` parameter '
|
|
203
|
+
+ 'or configure a BitcoinApi on the DidBtcr2Api instance.',
|
|
204
|
+
INVALID_DID_UPDATE, { beaconId }
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.#log.debug('Updating DID', sourceDocument.id, { beaconId, verificationMethodId });
|
|
209
|
+
|
|
210
|
+
// Factory validates and returns a sans-I/O state machine
|
|
211
|
+
const updater = DidBtcr2.update({
|
|
189
212
|
sourceDocument,
|
|
190
213
|
patches,
|
|
191
214
|
sourceVersionId,
|
|
192
215
|
verificationMethodId,
|
|
193
216
|
beaconId,
|
|
194
|
-
signingMaterial,
|
|
195
|
-
bitcoin : btcConnection,
|
|
196
217
|
});
|
|
218
|
+
|
|
219
|
+
// Drive the state machine. All I/O (signing delegation, Bitcoin broadcast)
|
|
220
|
+
// happens inside the need-handlers below — the Updater itself is pure.
|
|
221
|
+
let state = updater.advance();
|
|
222
|
+
while(state.status === 'action-required') {
|
|
223
|
+
for(const need of state.needs) {
|
|
224
|
+
switch(need.kind) {
|
|
225
|
+
case 'NeedSigningKey': {
|
|
226
|
+
this.#log.debug('Providing signer for', need.verificationMethodId);
|
|
227
|
+
updater.provide(need, signer);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'NeedFunding': {
|
|
231
|
+
this.#log.debug('Checking funding for beacon address %s', need.beaconAddress);
|
|
232
|
+
const utxos = await btcConnection.rest.address.getUtxos(need.beaconAddress);
|
|
233
|
+
if(!utxos.length) {
|
|
234
|
+
throw new UpdateError(
|
|
235
|
+
`Beacon address ${need.beaconAddress} is unfunded. `
|
|
236
|
+
+ 'Send BTC to this address before broadcasting the update.',
|
|
237
|
+
INVALID_DID_UPDATE, { beaconAddress: need.beaconAddress }
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
this.#log.debug('Beacon address funded (%d UTXOs)', utxos.length);
|
|
241
|
+
updater.provide(need);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case 'NeedBroadcast': {
|
|
245
|
+
this.#log.debug(
|
|
246
|
+
'Broadcasting signed update via %s beacon', need.beaconService.type
|
|
247
|
+
);
|
|
248
|
+
const beacon = BeaconFactory.establish(need.beaconService);
|
|
249
|
+
await beacon.broadcastSignal(need.signedUpdate, signer, btcConnection);
|
|
250
|
+
updater.provide(need);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
state = updater.advance();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.#log.debug('DID update complete', sourceDocument.id);
|
|
259
|
+
return state.result.signedUpdate;
|
|
197
260
|
}
|
|
198
261
|
|
|
199
262
|
/**
|
|
@@ -217,8 +280,9 @@ export class DidMethodApi {
|
|
|
217
280
|
* .buildUpdate(currentDoc)
|
|
218
281
|
* .patch({ op: 'add', path: '/service/1', value: newService })
|
|
219
282
|
* .version(2)
|
|
220
|
-
* .
|
|
283
|
+
* .verificationMethodId('#initialKey')
|
|
221
284
|
* .beacon('#beacon-0')
|
|
285
|
+
* .signer(new LocalSigner(secretKey))
|
|
222
286
|
* .execute();
|
|
223
287
|
* ```
|
|
224
288
|
*/
|
|
@@ -252,7 +316,7 @@ export class UpdateBuilder {
|
|
|
252
316
|
#sourceVersionId?: number;
|
|
253
317
|
#verificationMethodId?: string;
|
|
254
318
|
#beaconId?: string;
|
|
255
|
-
#
|
|
319
|
+
#signer?: Signer;
|
|
256
320
|
#bitcoin?: BitcoinConnection;
|
|
257
321
|
|
|
258
322
|
/** @internal */
|
|
@@ -279,8 +343,8 @@ export class UpdateBuilder {
|
|
|
279
343
|
return this;
|
|
280
344
|
}
|
|
281
345
|
|
|
282
|
-
/** Set the verification method ID used for signing. */
|
|
283
|
-
|
|
346
|
+
/** Set the verification method ID used for signing the update. */
|
|
347
|
+
verificationMethodId(methodId: string): this {
|
|
284
348
|
this.#verificationMethodId = methodId;
|
|
285
349
|
return this;
|
|
286
350
|
}
|
|
@@ -291,32 +355,44 @@ export class UpdateBuilder {
|
|
|
291
355
|
return this;
|
|
292
356
|
}
|
|
293
357
|
|
|
294
|
-
/**
|
|
295
|
-
|
|
296
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Set the {@link Signer} that produces the update's BIP-340 Schnorr proof
|
|
360
|
+
* and the beacon transaction's ECDSA input signature. Use `LocalSigner`
|
|
361
|
+
* for in-process secret keys, `KeyManagerSigner` for KMS-managed keys
|
|
362
|
+
* (AWS, Vault, HSM, etc.), or any custom adapter implementing the `Signer`
|
|
363
|
+
* interface.
|
|
364
|
+
*/
|
|
365
|
+
signer(s: Signer): this {
|
|
366
|
+
this.#signer = s;
|
|
297
367
|
return this;
|
|
298
368
|
}
|
|
299
369
|
|
|
300
370
|
/** Override the Bitcoin connection for this update. */
|
|
301
|
-
|
|
371
|
+
bitcoin(connection: BitcoinConnection): this {
|
|
302
372
|
this.#bitcoin = connection;
|
|
303
373
|
return this;
|
|
304
374
|
}
|
|
305
375
|
|
|
306
376
|
/**
|
|
307
377
|
* Execute the update.
|
|
308
|
-
* @throws {Error} If required fields (version,
|
|
378
|
+
* @throws {Error} If required fields (version, verificationMethodId, beacon, signer) are missing.
|
|
309
379
|
*/
|
|
310
380
|
async execute(): Promise<SignedBTCR2Update> {
|
|
311
381
|
if (this.#sourceVersionId === undefined) {
|
|
312
382
|
throw new Error('UpdateBuilder: sourceVersionId is required. Call .version(id) before .execute().');
|
|
313
383
|
}
|
|
314
384
|
if (!this.#verificationMethodId) {
|
|
315
|
-
throw new Error(
|
|
385
|
+
throw new Error(
|
|
386
|
+
'UpdateBuilder: verificationMethodId is required. '
|
|
387
|
+
+ 'Call .verificationMethodId(id) before .execute().'
|
|
388
|
+
);
|
|
316
389
|
}
|
|
317
390
|
if (!this.#beaconId) {
|
|
318
391
|
throw new Error('UpdateBuilder: beaconId is required. Call .beacon(id) before .execute().');
|
|
319
392
|
}
|
|
393
|
+
if (!this.#signer) {
|
|
394
|
+
throw new Error('UpdateBuilder: signer is required. Call .signer(s) before .execute().');
|
|
395
|
+
}
|
|
320
396
|
|
|
321
397
|
return this.#methodApi.update({
|
|
322
398
|
sourceDocument : this.#sourceDocument,
|
|
@@ -324,7 +400,7 @@ export class UpdateBuilder {
|
|
|
324
400
|
sourceVersionId : this.#sourceVersionId,
|
|
325
401
|
verificationMethodId : this.#verificationMethodId,
|
|
326
402
|
beaconId : this.#beaconId,
|
|
327
|
-
|
|
403
|
+
signer : this.#signer,
|
|
328
404
|
bitcoin : this.#bitcoin,
|
|
329
405
|
});
|
|
330
406
|
}
|