@did-btcr2/api 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +57253 -55798
- package/dist/browser.mjs +57252 -55797
- package/dist/cjs/api.js +31 -934
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/bitcoin.js +110 -0
- package/dist/cjs/bitcoin.js.map +1 -0
- package/dist/cjs/cas.js +90 -0
- package/dist/cjs/cas.js.map +1 -0
- package/dist/cjs/crypto.js +425 -0
- package/dist/cjs/crypto.js.map +1 -0
- package/dist/cjs/did.js +70 -0
- package/dist/cjs/did.js.map +1 -0
- package/dist/cjs/helpers.js +28 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/kms.js +73 -0
- package/dist/cjs/kms.js.map +1 -0
- package/dist/cjs/method.js +262 -0
- package/dist/cjs/method.js.map +1 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/api.js +31 -934
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/bitcoin.js +110 -0
- package/dist/esm/bitcoin.js.map +1 -0
- package/dist/esm/cas.js +90 -0
- package/dist/esm/cas.js.map +1 -0
- package/dist/esm/crypto.js +425 -0
- package/dist/esm/crypto.js.map +1 -0
- package/dist/esm/did.js +70 -0
- package/dist/esm/did.js.map +1 -0
- package/dist/esm/helpers.js +28 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/kms.js +73 -0
- package/dist/esm/kms.js.map +1 -0
- package/dist/esm/method.js +262 -0
- package/dist/esm/method.js.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/types/api.d.ts +19 -693
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/bitcoin.d.ts +64 -0
- package/dist/types/bitcoin.d.ts.map +1 -0
- package/dist/types/cas.d.ts +70 -0
- package/dist/types/cas.d.ts.map +1 -0
- package/dist/types/crypto.d.ts +310 -0
- package/dist/types/crypto.d.ts.map +1 -0
- package/dist/types/did.d.ts +51 -0
- package/dist/types/did.d.ts.map +1 -0
- package/dist/types/helpers.d.ts +10 -0
- package/dist/types/helpers.d.ts.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/kms.d.ts +49 -0
- package/dist/types/kms.d.ts.map +1 -0
- package/dist/types/method.d.ts +117 -0
- package/dist/types/method.d.ts.map +1 -0
- package/dist/types/types.d.ts +128 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/api.ts +40 -1317
- package/src/bitcoin.ts +129 -0
- package/src/cas.ts +121 -0
- package/src/crypto.ts +525 -0
- package/src/did.ts +75 -0
- package/src/helpers.ts +35 -0
- package/src/index.ts +37 -1
- package/src/kms.ts +95 -0
- package/src/method.ts +331 -0
- package/src/types.ts +122 -0
package/src/kms.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Bytes, HashBytes, SignatureBytes } from '@did-btcr2/common';
|
|
2
|
+
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
3
|
+
import {
|
|
4
|
+
type GenerateKeyOptions,
|
|
5
|
+
type ImportKeyOptions,
|
|
6
|
+
KeyIdentifier,
|
|
7
|
+
KeyManager,
|
|
8
|
+
Kms,
|
|
9
|
+
type SignOptions,
|
|
10
|
+
} from '@did-btcr2/kms';
|
|
11
|
+
import { assertBytes } from './helpers.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Key management operations sub-facade.
|
|
15
|
+
*
|
|
16
|
+
* Wraps a {@link KeyManager} interface. By default uses the built-in
|
|
17
|
+
* {@link Kms} implementation; a custom implementation can be injected
|
|
18
|
+
* via {@link ApiConfig}.
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export class KeyManagerApi {
|
|
22
|
+
/** The backing KeyManager instance. */
|
|
23
|
+
readonly kms: KeyManager;
|
|
24
|
+
|
|
25
|
+
/** Create a new KeyManagerApi, optionally backed by a custom KeyManager. */
|
|
26
|
+
constructor(kms?: KeyManager) {
|
|
27
|
+
this.kms = kms ?? new Kms();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Generate a new key directly in the KMS. */
|
|
31
|
+
generateKey(options?: GenerateKeyOptions): KeyIdentifier {
|
|
32
|
+
return this.kms.generateKey(options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Set the active key by its identifier. */
|
|
36
|
+
setActive(id: KeyIdentifier): void {
|
|
37
|
+
this.kms.setActiveKey(id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Get the public key bytes for a key identifier. */
|
|
41
|
+
getPublicKey(id?: KeyIdentifier): Bytes {
|
|
42
|
+
return this.kms.getPublicKey(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Import a Schnorr keypair into the KMS. */
|
|
46
|
+
import(kp: SchnorrKeyPair, options?: ImportKeyOptions): KeyIdentifier {
|
|
47
|
+
return this.kms.importKey(kp, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Export a Schnorr keypair from the KMS.
|
|
52
|
+
* Only supported when the backing KMS is the built-in {@link Kms} class.
|
|
53
|
+
* @throws {Error} If the backing KMS does not support key export.
|
|
54
|
+
*/
|
|
55
|
+
export(id: KeyIdentifier): SchnorrKeyPair {
|
|
56
|
+
if (!(this.kms instanceof Kms)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'Key export is not supported by the current KeyManager implementation. '
|
|
59
|
+
+ 'Export is only available with the built-in Kms class.'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return this.kms.exportKey(id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** List all managed key identifiers. */
|
|
66
|
+
listKeys(): KeyIdentifier[] {
|
|
67
|
+
return this.kms.listKeys();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Remove a key from the KMS. */
|
|
71
|
+
removeKey(id: KeyIdentifier, options: { force?: boolean } = {}): void {
|
|
72
|
+
return this.kms.removeKey(id, options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sign data via the KMS.
|
|
77
|
+
* @param data The data to sign (must be non-empty).
|
|
78
|
+
* @param id Optional key identifier; uses the active key if omitted.
|
|
79
|
+
* @param options Signing options (scheme defaults to 'schnorr').
|
|
80
|
+
*/
|
|
81
|
+
sign(data: Bytes, id?: KeyIdentifier, options?: SignOptions): SignatureBytes {
|
|
82
|
+
assertBytes(data, 'data');
|
|
83
|
+
return this.kms.sign(data, id, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Verify a signature via the KMS. */
|
|
87
|
+
verify(signature: SignatureBytes, data: Bytes, id?: KeyIdentifier, options?: SignOptions): boolean {
|
|
88
|
+
return this.kms.verify(signature, data, id, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Compute a SHA-256 digest. */
|
|
92
|
+
digest(data: Uint8Array): HashBytes {
|
|
93
|
+
return this.kms.digest(data);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/method.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
+
import type { DocumentBytes, HexString, KeyBytes, PatchOperation } from '@did-btcr2/common';
|
|
3
|
+
import { IdentifierTypes, NotImplementedError } from '@did-btcr2/common';
|
|
4
|
+
import { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
5
|
+
import type { Btcr2DidDocument, CASAnnouncement, DidCreateOptions, NeedCASAnnouncement, NeedGenesisDocument, NeedSignedUpdate, ResolutionOptions } from '@did-btcr2/method';
|
|
6
|
+
import { BeaconSignalDiscovery, DidBtcr2 } from '@did-btcr2/method';
|
|
7
|
+
import type { DidResolutionResult, DidVerificationMethod } from '@web5/dids';
|
|
8
|
+
import { BitcoinApi } from './bitcoin.js';
|
|
9
|
+
import { CasApi } from './cas.js';
|
|
10
|
+
import { assertBytes, assertCompressedPubkey, assertString, NOOP_LOGGER } from './helpers.js';
|
|
11
|
+
import type { Logger } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* DID method operations sub-facade: create, resolve, update, deactivate.
|
|
15
|
+
*
|
|
16
|
+
* Lazily initialized by {@link DidBtcr2Api} because it depends on
|
|
17
|
+
* {@link BitcoinApi} which requires network configuration.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export class DidMethodApi {
|
|
21
|
+
#btc?: BitcoinApi;
|
|
22
|
+
#cas?: CasApi;
|
|
23
|
+
#log: Logger;
|
|
24
|
+
|
|
25
|
+
constructor(btc?: BitcoinApi, cas?: CasApi, logger?: Logger) {
|
|
26
|
+
this.#btc = btc;
|
|
27
|
+
this.#cas = cas;
|
|
28
|
+
this.#log = logger ?? NOOP_LOGGER;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a deterministic (k1) DID from a public key.
|
|
33
|
+
* Sets idType to KEY automatically.
|
|
34
|
+
* @param genesisBytes The compressed public key bytes (33 bytes).
|
|
35
|
+
* @param options Creation options (idType is set for you).
|
|
36
|
+
* @returns The created DID identifier string.
|
|
37
|
+
*/
|
|
38
|
+
createDeterministic(genesisBytes: KeyBytes, options: Omit<DidCreateOptions, 'idType'> = {}): string {
|
|
39
|
+
assertCompressedPubkey(genesisBytes, 'genesisBytes');
|
|
40
|
+
return DidBtcr2.create(genesisBytes, { ...options, idType: IdentifierTypes.KEY });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a non-deterministic (x1) DID from external genesis document bytes.
|
|
45
|
+
* Sets idType to EXTERNAL automatically.
|
|
46
|
+
* @param genesisBytes The genesis document bytes.
|
|
47
|
+
* @param options Creation options (idType is set for you).
|
|
48
|
+
* @returns The created DID identifier string.
|
|
49
|
+
*/
|
|
50
|
+
createExternal(genesisBytes: DocumentBytes, options: Omit<DidCreateOptions, 'idType'> = {}): string {
|
|
51
|
+
assertBytes(genesisBytes, 'genesisBytes');
|
|
52
|
+
return DidBtcr2.create(genesisBytes, { ...options, idType: IdentifierTypes.EXTERNAL });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a DID by driving the sans-I/O {@link Resolver} state machine.
|
|
57
|
+
* If a Bitcoin connection is configured on the API, it is used automatically
|
|
58
|
+
* to fetch beacon signals. Sidecar data flows through `options.sidecar`.
|
|
59
|
+
* @param did The DID to resolve.
|
|
60
|
+
* @param options Resolution options.
|
|
61
|
+
* @returns The resolution result.
|
|
62
|
+
*/
|
|
63
|
+
async resolve(did: string, options?: ResolutionOptions): Promise<DidResolutionResult> {
|
|
64
|
+
assertString(did, 'did');
|
|
65
|
+
this.#log.debug('Resolving DID', did);
|
|
66
|
+
try {
|
|
67
|
+
const resolver = DidBtcr2.resolve(did, options);
|
|
68
|
+
let state = resolver.resolve();
|
|
69
|
+
|
|
70
|
+
while(state.status === 'action-required') {
|
|
71
|
+
for(const need of state.needs) {
|
|
72
|
+
switch(need.kind) {
|
|
73
|
+
case 'NeedBeaconSignals': {
|
|
74
|
+
if(!this.#btc) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Bitcoin connection required to fetch beacon signals. '
|
|
77
|
+
+ 'Configure a BitcoinApi on the DidBtcr2Api instance.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
this.#log.debug(
|
|
81
|
+
'Fetching beacon signals for %d service(s)',
|
|
82
|
+
need.beaconServices.length
|
|
83
|
+
);
|
|
84
|
+
const signals = await BeaconSignalDiscovery.indexer(
|
|
85
|
+
[...need.beaconServices], this.#btc.connection
|
|
86
|
+
);
|
|
87
|
+
resolver.provide(need, signals);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'NeedGenesisDocument': {
|
|
91
|
+
if(!this.#cas) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Genesis document required but not in sidecar (hash: ${need.genesisHash}), `
|
|
94
|
+
+ 'and no CAS driver configured. Either provide the genesis document via '
|
|
95
|
+
+ 'options.sidecar.genesisDocument or configure a CAS driver.'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
this.#log.debug('Fetching genesis document from CAS: %s', need.genesisHash);
|
|
99
|
+
const doc = await this.#cas.retrieve(need.genesisHash);
|
|
100
|
+
if(!doc) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Genesis document not found in CAS (hash: ${need.genesisHash}).`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
resolver.provide(need as NeedGenesisDocument, doc);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'NeedCASAnnouncement': {
|
|
109
|
+
if(!this.#cas) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`CAS announcement required but not in sidecar (hash: ${need.announcementHash}), `
|
|
112
|
+
+ 'and no CAS driver configured. Either provide it via '
|
|
113
|
+
+ 'options.sidecar.casUpdates or configure a CAS driver.'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
this.#log.debug('Fetching CAS announcement from CAS: %s', need.announcementHash);
|
|
117
|
+
const announcement = await this.#cas.retrieve(need.announcementHash);
|
|
118
|
+
if(!announcement) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`CAS announcement not found in CAS (hash: ${need.announcementHash}).`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
resolver.provide(need as NeedCASAnnouncement, announcement as CASAnnouncement);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'NeedSignedUpdate': {
|
|
127
|
+
if(!this.#cas) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Signed update required but not in sidecar (hash: ${need.updateHash}), `
|
|
130
|
+
+ 'and no CAS driver configured. Either provide it via '
|
|
131
|
+
+ 'options.sidecar.updates or configure a CAS driver.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
this.#log.debug('Fetching signed update from CAS: %s', need.updateHash);
|
|
135
|
+
const update = await this.#cas.retrieve(need.updateHash);
|
|
136
|
+
if(!update) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Signed update not found in CAS (hash: ${need.updateHash}).`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
resolver.provide(need as NeedSignedUpdate, update as SignedBTCR2Update);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
state = resolver.resolve();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.#log.debug('DID resolved successfully', did, state.result.metadata);
|
|
150
|
+
return {
|
|
151
|
+
didResolutionMetadata : {},
|
|
152
|
+
didDocument : state.result.didDocument as unknown as DidResolutionResult['didDocument'],
|
|
153
|
+
didDocumentMetadata : state.result.metadata,
|
|
154
|
+
};
|
|
155
|
+
} catch (err) {
|
|
156
|
+
this.#log.error('DID resolution failed', did, err);
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Failed to resolve DID: ${did}`,
|
|
159
|
+
{ cause: err }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Update an existing DID document. If a Bitcoin connection is configured on
|
|
166
|
+
* the API, it is injected automatically.
|
|
167
|
+
* @param params The update parameters.
|
|
168
|
+
* @returns The signed update.
|
|
169
|
+
*/
|
|
170
|
+
async update({
|
|
171
|
+
sourceDocument,
|
|
172
|
+
patches,
|
|
173
|
+
sourceVersionId,
|
|
174
|
+
verificationMethodId,
|
|
175
|
+
beaconId,
|
|
176
|
+
signingMaterial,
|
|
177
|
+
bitcoin,
|
|
178
|
+
}: {
|
|
179
|
+
sourceDocument: Btcr2DidDocument;
|
|
180
|
+
patches: PatchOperation[];
|
|
181
|
+
sourceVersionId: number;
|
|
182
|
+
verificationMethodId: string;
|
|
183
|
+
beaconId: string;
|
|
184
|
+
signingMaterial?: KeyBytes | HexString;
|
|
185
|
+
bitcoin?: BitcoinConnection;
|
|
186
|
+
}): Promise<SignedBTCR2Update> {
|
|
187
|
+
const btcConnection = bitcoin ?? this.#btc?.connection ?? undefined;
|
|
188
|
+
return await DidBtcr2.update({
|
|
189
|
+
sourceDocument,
|
|
190
|
+
patches,
|
|
191
|
+
sourceVersionId,
|
|
192
|
+
verificationMethodId,
|
|
193
|
+
beaconId,
|
|
194
|
+
signingMaterial,
|
|
195
|
+
bitcoin : btcConnection,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get the signing method from a DID document by method ID.
|
|
201
|
+
* @param didDocument The DID document.
|
|
202
|
+
* @param methodId The method ID (if omitted, the first signing method is returned).
|
|
203
|
+
* @returns The found signing method.
|
|
204
|
+
*/
|
|
205
|
+
getSigningMethod(didDocument: Btcr2DidDocument, methodId?: string): DidVerificationMethod {
|
|
206
|
+
return DidBtcr2.getSigningMethod(didDocument, methodId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Create a fluent builder for a DID update operation.
|
|
211
|
+
* @param sourceDocument The current DID document to update.
|
|
212
|
+
* @returns An {@link UpdateBuilder} for chaining update parameters.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const signed = await api.btcr2
|
|
217
|
+
* .buildUpdate(currentDoc)
|
|
218
|
+
* .patch({ op: 'add', path: '/service/1', value: newService })
|
|
219
|
+
* .version(2)
|
|
220
|
+
* .signer('#initialKey')
|
|
221
|
+
* .beacon('#beacon-0')
|
|
222
|
+
* .execute();
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
buildUpdate(sourceDocument: Btcr2DidDocument): UpdateBuilder {
|
|
226
|
+
return new UpdateBuilder(this, sourceDocument);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Deactivate a DID (not yet implemented in the core method). */
|
|
230
|
+
async deactivate(): Promise<SignedBTCR2Update> {
|
|
231
|
+
throw new NotImplementedError(
|
|
232
|
+
'DidMethodApi.deactivate is not implemented yet.',
|
|
233
|
+
{
|
|
234
|
+
type : 'DID_API_METHOD_NOT_IMPLEMENTED',
|
|
235
|
+
name : 'NOT_IMPLEMENTED_ERROR'
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fluent builder for DID update operations. Reduces the cognitive load of
|
|
243
|
+
* the 7-parameter `update()` call by letting callers chain named steps.
|
|
244
|
+
*
|
|
245
|
+
* Created via {@link DidMethodApi.buildUpdate}.
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
export class UpdateBuilder {
|
|
249
|
+
#methodApi: DidMethodApi;
|
|
250
|
+
#sourceDocument: Btcr2DidDocument;
|
|
251
|
+
#patches: PatchOperation[] = [];
|
|
252
|
+
#sourceVersionId?: number;
|
|
253
|
+
#verificationMethodId?: string;
|
|
254
|
+
#beaconId?: string;
|
|
255
|
+
#signingMaterial?: KeyBytes | HexString;
|
|
256
|
+
#bitcoin?: BitcoinConnection;
|
|
257
|
+
|
|
258
|
+
/** @internal */
|
|
259
|
+
constructor(methodApi: DidMethodApi, sourceDocument: Btcr2DidDocument) {
|
|
260
|
+
this.#methodApi = methodApi;
|
|
261
|
+
this.#sourceDocument = sourceDocument;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Add a single JSON Patch operation. Can be called multiple times. */
|
|
265
|
+
patch(op: PatchOperation): this {
|
|
266
|
+
this.#patches.push(op);
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Set all patches at once (replaces any previously added). */
|
|
271
|
+
patches(ops: PatchOperation[]): this {
|
|
272
|
+
this.#patches = [...ops];
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Set the source version ID. */
|
|
277
|
+
version(id: number): this {
|
|
278
|
+
this.#sourceVersionId = id;
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Set the verification method ID used for signing. */
|
|
283
|
+
signer(methodId: string): this {
|
|
284
|
+
this.#verificationMethodId = methodId;
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Set the beacon ID for the update announcement. */
|
|
289
|
+
beacon(beaconId: string): this {
|
|
290
|
+
this.#beaconId = beaconId;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Set the signing material (secret key bytes or hex). */
|
|
295
|
+
signingMaterial(material: KeyBytes | HexString): this {
|
|
296
|
+
this.#signingMaterial = material;
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Override the Bitcoin connection for this update. */
|
|
301
|
+
withBitcoin(connection: BitcoinConnection): this {
|
|
302
|
+
this.#bitcoin = connection;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Execute the update.
|
|
308
|
+
* @throws {Error} If required fields (version, signer, beacon) are missing.
|
|
309
|
+
*/
|
|
310
|
+
async execute(): Promise<SignedBTCR2Update> {
|
|
311
|
+
if (this.#sourceVersionId === undefined) {
|
|
312
|
+
throw new Error('UpdateBuilder: sourceVersionId is required. Call .version(id) before .execute().');
|
|
313
|
+
}
|
|
314
|
+
if (!this.#verificationMethodId) {
|
|
315
|
+
throw new Error('UpdateBuilder: verificationMethodId is required. Call .signer(id) before .execute().');
|
|
316
|
+
}
|
|
317
|
+
if (!this.#beaconId) {
|
|
318
|
+
throw new Error('UpdateBuilder: beaconId is required. Call .beacon(id) before .execute().');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return this.#methodApi.update({
|
|
322
|
+
sourceDocument : this.#sourceDocument,
|
|
323
|
+
patches : this.#patches,
|
|
324
|
+
sourceVersionId : this.#sourceVersionId,
|
|
325
|
+
verificationMethodId : this.#verificationMethodId,
|
|
326
|
+
beaconId : this.#beaconId,
|
|
327
|
+
signingMaterial : this.#signingMaterial,
|
|
328
|
+
bitcoin : this.#bitcoin,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { HttpExecutor, NetworkName, RestConfig, RpcConfig } from '@did-btcr2/bitcoin';
|
|
2
|
+
import type { KeyManager } from '@did-btcr2/kms';
|
|
3
|
+
import type { Btcr2DidDocument } from '@did-btcr2/method';
|
|
4
|
+
import type { DidResolutionResult } from '@web5/dids';
|
|
5
|
+
import type { CasConfig } from './cas.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pluggable logger interface. All methods are optional-call; the default
|
|
9
|
+
* implementation is a silent no-op.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export type Logger = {
|
|
13
|
+
debug(message: string, ...args: unknown[]): void;
|
|
14
|
+
info(message: string, ...args: unknown[]): void;
|
|
15
|
+
warn(message: string, ...args: unknown[]): void;
|
|
16
|
+
error(message: string, ...args: unknown[]): void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The two supported DID identifier types.
|
|
21
|
+
*
|
|
22
|
+
* Note: the upstream `DidCreateOptions.idType` is typed as `string` rather
|
|
23
|
+
* than a union. This local alias provides compile-time safety at the API
|
|
24
|
+
* facade level. Upstream runtime validation in `Identifier.encode()` still
|
|
25
|
+
* catches invalid values.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export type IdType = 'KEY' | 'EXTERNAL';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A branded string representing a DID identifier (e.g. `did:btcr2:k1q...`).
|
|
32
|
+
* Use branded types to prevent accidentally passing a txid where a DID is
|
|
33
|
+
* expected, or vice versa, at compile time.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const did = api.generateDid().did as DidString;
|
|
38
|
+
* api.resolveDid(did); // OK
|
|
39
|
+
* api.btc.getTransaction(did); // Type error — DidString is not TxId
|
|
40
|
+
* ```
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export type DidString = string & { readonly __brand: 'DidString' };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A branded string representing a Bitcoin transaction ID (64-char hex).
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export type TxId = string & { readonly __brand: 'TxId' };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Result of a DID resolution attempt. Wraps the standard
|
|
53
|
+
* {@link DidResolutionResult} with a discriminated `ok` flag for ergonomic
|
|
54
|
+
* pattern matching without exception handling.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const result = await api.tryResolveDid(did);
|
|
59
|
+
* if (result.ok) {
|
|
60
|
+
* console.log(result.document);
|
|
61
|
+
* } else {
|
|
62
|
+
* console.log(result.error, result.errorMessage);
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
export type ResolutionResult =
|
|
68
|
+
| { ok: true; document: Btcr2DidDocument; metadata: DidResolutionResult['didDocumentMetadata']; raw: DidResolutionResult }
|
|
69
|
+
| { ok: false; error: string; errorMessage?: string; raw: DidResolutionResult };
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Bitcoin API configuration options.
|
|
73
|
+
* The `network` field is required and determines default REST/RPC endpoints.
|
|
74
|
+
* Optional `rest` and `rpc` fields override individual endpoints on top of
|
|
75
|
+
* the network defaults.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Use regtest defaults (localhost Polar + Esplora)
|
|
80
|
+
* { network: 'regtest' }
|
|
81
|
+
*
|
|
82
|
+
* // Use testnet4 with a custom REST endpoint
|
|
83
|
+
* { network: 'testnet4', rest: { host: 'https://my-mempool.example/api' } }
|
|
84
|
+
*
|
|
85
|
+
* // Use regtest with custom RPC credentials, default REST
|
|
86
|
+
* { network: 'regtest', rpc: { host: 'http://mynode:18443', username: 'u', password: 'p' } }
|
|
87
|
+
* ```
|
|
88
|
+
* @public
|
|
89
|
+
*/
|
|
90
|
+
export type BitcoinApiConfig = {
|
|
91
|
+
/** Bitcoin network name (e.g., 'regtest', 'testnet4', 'bitcoin'). */
|
|
92
|
+
network: NetworkName;
|
|
93
|
+
/** Override REST client settings on top of network defaults. */
|
|
94
|
+
rest?: Partial<RestConfig>;
|
|
95
|
+
/** Override RPC client settings on top of network defaults. */
|
|
96
|
+
rpc?: RpcConfig;
|
|
97
|
+
/**
|
|
98
|
+
* Optional HTTP executor for sans-I/O usage. Defaults to global `fetch`.
|
|
99
|
+
* Inject a custom executor to intercept requests in tests or route through
|
|
100
|
+
* a proxy without monkey-patching globals.
|
|
101
|
+
*/
|
|
102
|
+
executor?: HttpExecutor;
|
|
103
|
+
/**
|
|
104
|
+
* Optional request timeout in milliseconds for REST calls.
|
|
105
|
+
* When set, wraps the HTTP executor with an `AbortSignal.timeout()`.
|
|
106
|
+
* Has no effect when a custom `executor` is provided (the custom
|
|
107
|
+
* executor is responsible for its own timeouts).
|
|
108
|
+
*/
|
|
109
|
+
timeoutMs?: number;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Top-level API configuration options.
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
export type ApiConfig = {
|
|
117
|
+
btc?: BitcoinApiConfig;
|
|
118
|
+
cas?: CasConfig;
|
|
119
|
+
kms?: KeyManager;
|
|
120
|
+
/** Optional logger. Defaults to a silent no-op logger. */
|
|
121
|
+
logger?: Logger;
|
|
122
|
+
};
|