@did-btcr2/method 0.28.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +34125 -44647
- package/dist/browser.mjs +26409 -36931
- package/dist/cjs/index.js +2869 -679
- package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
- package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
- package/dist/esm/core/aggregation/cohort.js +31 -8
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/logger.js +15 -0
- package/dist/esm/core/aggregation/logger.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js +12 -1
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +90 -0
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/messages/index.js +1 -0
- package/dist/esm/core/aggregation/messages/index.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +39 -46
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +143 -15
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/core/aggregation/signing-session.js +44 -5
- package/dist/esm/core/aggregation/signing-session.js.map +1 -1
- package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
- package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
- package/dist/esm/core/aggregation/transport/factory.js +15 -6
- package/dist/esm/core/aggregation/transport/factory.js.map +1 -1
- package/dist/esm/core/aggregation/transport/http/client.js +350 -0
- package/dist/esm/core/aggregation/transport/http/client.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js +126 -0
- package/dist/esm/core/aggregation/transport/http/envelope.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/errors.js +11 -0
- package/dist/esm/core/aggregation/transport/http/errors.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/index.js +12 -0
- package/dist/esm/core/aggregation/transport/http/index.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js +38 -0
- package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js +28 -0
- package/dist/esm/core/aggregation/transport/http/protocol.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js +45 -0
- package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js +100 -0
- package/dist/esm/core/aggregation/transport/http/request-auth.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/server.js +481 -0
- package/dist/esm/core/aggregation/transport/http/server.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js +110 -0
- package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js +25 -0
- package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/core/aggregation/transport/index.js +1 -0
- package/dist/esm/core/aggregation/transport/index.js.map +1 -1
- package/dist/esm/core/aggregation/transport/nostr.js +245 -16
- package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +295 -63
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +3 -3
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/singleton-beacon.js +3 -3
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +3 -3
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +14 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/core/updater.js +63 -55
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +0 -4
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/did-document.js +2 -2
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
- package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
- package/dist/types/core/aggregation/cohort.d.ts +20 -3
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/logger.d.ts +22 -0
- package/dist/types/core/aggregation/logger.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/base.d.ts +13 -1
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/index.d.ts +1 -0
- package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +2 -0
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +32 -6
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +33 -2
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/core/aggregation/signing-session.d.ts +5 -1
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/factory.d.ts +22 -7
- package/dist/types/core/aggregation/transport/factory.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/http/client.d.ts +48 -0
- package/dist/types/core/aggregation/transport/http/client.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts +64 -0
- package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts +9 -0
- package/dist/types/core/aggregation/transport/http/errors.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/index.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts +53 -0
- package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts +50 -0
- package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts +110 -0
- package/dist/types/core/aggregation/transport/http/server.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts +1 -0
- package/dist/types/core/aggregation/transport/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/transport.d.ts +26 -1
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +149 -22
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +3 -3
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +3 -3
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts +2 -2
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts +27 -12
- package/dist/types/core/updater.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -7
- package/src/core/aggregation/beacon-strategy.ts +123 -0
- package/src/core/aggregation/cohort.ts +34 -8
- package/src/core/aggregation/logger.ts +33 -0
- package/src/core/aggregation/messages/base.ts +20 -5
- package/src/core/aggregation/messages/bodies.ts +223 -0
- package/src/core/aggregation/messages/factories.ts +1 -0
- package/src/core/aggregation/messages/index.ts +1 -0
- package/src/core/aggregation/participant.ts +40 -46
- package/src/core/aggregation/runner/events.ts +27 -3
- package/src/core/aggregation/runner/participant-runner.ts +41 -7
- package/src/core/aggregation/runner/service-runner.ts +227 -19
- package/src/core/aggregation/service.ts +189 -20
- package/src/core/aggregation/signing-session.ts +65 -7
- package/src/core/aggregation/transport/didcomm.ts +17 -0
- package/src/core/aggregation/transport/factory.ts +48 -12
- package/src/core/aggregation/transport/http/client.ts +409 -0
- package/src/core/aggregation/transport/http/envelope.ts +204 -0
- package/src/core/aggregation/transport/http/errors.ts +11 -0
- package/src/core/aggregation/transport/http/inbox-buffer.ts +53 -0
- package/src/core/aggregation/transport/http/index.ts +11 -0
- package/src/core/aggregation/transport/http/nonce-cache.ts +43 -0
- package/src/core/aggregation/transport/http/protocol.ts +57 -0
- package/src/core/aggregation/transport/http/rate-limiter.ts +75 -0
- package/src/core/aggregation/transport/http/request-auth.ts +164 -0
- package/src/core/aggregation/transport/http/server.ts +615 -0
- package/src/core/aggregation/transport/http/sse-stream.ts +121 -0
- package/src/core/aggregation/transport/http/sse-writer.ts +23 -0
- package/src/core/aggregation/transport/index.ts +1 -0
- package/src/core/aggregation/transport/nostr.ts +266 -23
- package/src/core/aggregation/transport/transport.ts +34 -1
- package/src/core/beacon/beacon.ts +411 -79
- package/src/core/beacon/cas-beacon.ts +4 -4
- package/src/core/beacon/singleton-beacon.ts +4 -4
- package/src/core/beacon/smt-beacon.ts +4 -4
- package/src/core/beacon/utils.ts +16 -11
- package/src/core/updater.ts +113 -67
- package/src/did-btcr2.ts +0 -5
- package/src/index.ts +2 -0
- package/src/utils/did-document.ts +2 -2
package/src/core/beacon/utils.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { BTCNetwork } from '@did-btcr2/bitcoin';
|
|
1
2
|
import { getNetwork } from '@did-btcr2/bitcoin';
|
|
2
3
|
import type { KeyBytes, Maybe} from '@did-btcr2/common';
|
|
3
4
|
import { DidMethodError, MethodError } from '@did-btcr2/common';
|
|
4
|
-
import
|
|
5
|
-
import { payments } from 'bitcoinjs-lib';
|
|
5
|
+
import { p2pkh, p2tr, p2wpkh } from '@scure/btc-signer';
|
|
6
6
|
import { Appendix } from '../../utils/appendix.js';
|
|
7
7
|
import type { DidDocument } from '../../utils/did-document.js';
|
|
8
8
|
import { Identifier } from '../identifier.js';
|
|
@@ -100,7 +100,12 @@ export class BeaconUtils {
|
|
|
100
100
|
// Build the id
|
|
101
101
|
const id = `${did}#initial${addressType.toUpperCase()}`;
|
|
102
102
|
// Generate the bitcoin address
|
|
103
|
-
const
|
|
103
|
+
const address = addressType === 'p2tr'
|
|
104
|
+
? p2tr(pubkey.slice(1, 33), undefined, network).address
|
|
105
|
+
: addressType === 'p2wpkh'
|
|
106
|
+
? p2wpkh(pubkey, network).address
|
|
107
|
+
: p2pkh(pubkey, network).address;
|
|
108
|
+
const serviceEndpoint = `bitcoin:${address}`;
|
|
104
109
|
// Return the beacon serviceD
|
|
105
110
|
return { id, type: beaconType, serviceEndpoint, };
|
|
106
111
|
} catch (error: any) {
|
|
@@ -120,16 +125,16 @@ export class BeaconUtils {
|
|
|
120
125
|
static generateBeaconServices({ id, publicKey, network, beaconType }: {
|
|
121
126
|
id: string;
|
|
122
127
|
publicKey: KeyBytes;
|
|
123
|
-
network:
|
|
128
|
+
network: BTCNetwork;
|
|
124
129
|
beaconType: string;
|
|
125
130
|
}): Array<BeaconService> {
|
|
126
131
|
try {
|
|
127
132
|
// Generate the bitcoin addresses for the given public key and network
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
133
|
+
const p2pkhAddr = p2pkh(publicKey, network).address;
|
|
134
|
+
const p2wpkhAddr = p2wpkh(publicKey, network).address;
|
|
135
|
+
const p2trAddr = p2tr(publicKey.slice(1, 33), undefined, network).address;
|
|
131
136
|
// Check that all addresses were generated successfully
|
|
132
|
-
if (!
|
|
137
|
+
if (!p2pkhAddr || !p2wpkhAddr || !p2trAddr) {
|
|
133
138
|
throw new DidMethodError('Failed to generate bitcoin addresses');
|
|
134
139
|
}
|
|
135
140
|
// Return the beacon services with the generated addresses as service endpoints
|
|
@@ -137,17 +142,17 @@ export class BeaconUtils {
|
|
|
137
142
|
{
|
|
138
143
|
id : `${id}#initialP2PKH`,
|
|
139
144
|
type : beaconType,
|
|
140
|
-
serviceEndpoint : `bitcoin:${
|
|
145
|
+
serviceEndpoint : `bitcoin:${p2pkhAddr}`
|
|
141
146
|
},
|
|
142
147
|
{
|
|
143
148
|
id : `${id}#initialP2WPKH`,
|
|
144
149
|
type : beaconType,
|
|
145
|
-
serviceEndpoint : `bitcoin:${
|
|
150
|
+
serviceEndpoint : `bitcoin:${p2wpkhAddr}`
|
|
146
151
|
},
|
|
147
152
|
{
|
|
148
153
|
id : `${id}#initialP2TR`,
|
|
149
154
|
type : beaconType,
|
|
150
|
-
serviceEndpoint : `bitcoin:${
|
|
155
|
+
serviceEndpoint : `bitcoin:${p2trAddr}`
|
|
151
156
|
},
|
|
152
157
|
];
|
|
153
158
|
} catch (error: any) {
|
package/src/core/updater.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PatchOperation } from '@did-btcr2/common';
|
|
3
3
|
import { canonicalHash, INVALID_DID_UPDATE, JSONPatch, UpdateError } from '@did-btcr2/common';
|
|
4
4
|
import { SchnorrMultikey, type DataIntegrityConfig, type SignedBTCR2Update, type UnsignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
5
|
+
import type { Signer } from '@did-btcr2/keypair';
|
|
5
6
|
import { DidDocument, type Btcr2DidDocument, type DidVerificationMethod } from '../utils/did-document.js';
|
|
6
7
|
import { BeaconFactory } from './beacon/factory.js';
|
|
7
8
|
import type { BeaconService } from './beacon/interfaces.js';
|
|
@@ -9,9 +10,10 @@ import type { BeaconService } from './beacon/interfaces.js';
|
|
|
9
10
|
// ─── DataNeed types ──────────────────────────────────────────────────────────
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* The updater needs the caller to supply a
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* The updater needs the caller to supply a {@link Signer} for the given
|
|
14
|
+
* verification method. The unsigned update is attached so the caller can
|
|
15
|
+
* inspect it before producing a signature. The signer can wrap a local secret
|
|
16
|
+
* key (`LocalSigner`), a KMS-managed key (`KeyManagerSigner`), or any custom backend.
|
|
15
17
|
*/
|
|
16
18
|
export interface NeedSigningKey {
|
|
17
19
|
readonly kind: 'NeedSigningKey';
|
|
@@ -36,6 +38,19 @@ export interface NeedFunding {
|
|
|
36
38
|
readonly beaconService: BeaconService;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Optional proof the caller passes when fulfilling {@link NeedFunding}. The
|
|
43
|
+
* state machine asserts the proof before transitioning to Broadcast. Sans-I/O
|
|
44
|
+
* is preserved: the caller still performs the UTXO lookup; this is just a
|
|
45
|
+
* contract-level handshake.
|
|
46
|
+
*/
|
|
47
|
+
export interface FundingProof {
|
|
48
|
+
/** Number of spendable UTXOs the caller observed at the beacon address. Must be >= 1. */
|
|
49
|
+
utxoCount: number;
|
|
50
|
+
/** Optional txid the caller funded with, for diagnostics. */
|
|
51
|
+
txid?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
/**
|
|
40
55
|
* The updater needs the caller to broadcast the signed update via the beacon.
|
|
41
56
|
*
|
|
@@ -75,16 +90,18 @@ export type UpdaterState =
|
|
|
75
90
|
| { status: 'complete'; result: UpdaterResult };
|
|
76
91
|
|
|
77
92
|
/**
|
|
78
|
-
*
|
|
93
|
+
* Discriminated union of the updater's internal state. Each phase tag pins the
|
|
94
|
+
* exact set of values the state machine has computed so far, so consumers of
|
|
95
|
+
* `#state` narrow correctly under `switch (this.#state.phase)`. No nullable
|
|
96
|
+
* scratch slots, no `!`-asserts.
|
|
79
97
|
* @internal
|
|
80
98
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
99
|
+
type InternalState =
|
|
100
|
+
| { phase: 'Construct' }
|
|
101
|
+
| { phase: 'Sign'; unsignedUpdate: UnsignedBTCR2Update }
|
|
102
|
+
| { phase: 'Fund'; unsignedUpdate: UnsignedBTCR2Update; signedUpdate: SignedBTCR2Update }
|
|
103
|
+
| { phase: 'Broadcast'; unsignedUpdate: UnsignedBTCR2Update; signedUpdate: SignedBTCR2Update }
|
|
104
|
+
| { phase: 'Complete'; signedUpdate: SignedBTCR2Update };
|
|
88
105
|
|
|
89
106
|
/**
|
|
90
107
|
* Parameters for constructing an {@link Updater}. Built by
|
|
@@ -106,20 +123,21 @@ export interface UpdaterParams {
|
|
|
106
123
|
*
|
|
107
124
|
* ```typescript
|
|
108
125
|
* const updater = DidBtcr2.update({ sourceDocument, patches, ... });
|
|
126
|
+
* const signer = new LocalSigner(secretKeyBytes); // or KeyManagerSigner / custom
|
|
109
127
|
* let state = updater.advance();
|
|
110
128
|
*
|
|
111
129
|
* while(state.status === 'action-required') {
|
|
112
130
|
* for(const need of state.needs) {
|
|
113
131
|
* switch(need.kind) {
|
|
114
132
|
* case 'NeedSigningKey':
|
|
115
|
-
* updater.provide(need,
|
|
133
|
+
* updater.provide(need, signer);
|
|
116
134
|
* break;
|
|
117
135
|
* case 'NeedFunding':
|
|
118
136
|
* // Check UTXOs at need.beaconAddress, fund if needed
|
|
119
137
|
* updater.provide(need);
|
|
120
138
|
* break;
|
|
121
139
|
* case 'NeedBroadcast':
|
|
122
|
-
* await Updater.announce(need.beaconService, need.signedUpdate,
|
|
140
|
+
* await Updater.announce(need.beaconService, need.signedUpdate, signer, bitcoin);
|
|
123
141
|
* updater.provide(need);
|
|
124
142
|
* break;
|
|
125
143
|
* }
|
|
@@ -142,16 +160,13 @@ export interface UpdaterParams {
|
|
|
142
160
|
* @class Updater
|
|
143
161
|
*/
|
|
144
162
|
export class Updater {
|
|
145
|
-
#
|
|
163
|
+
#state: InternalState = { phase: 'Construct' };
|
|
146
164
|
readonly #sourceDocument: Btcr2DidDocument;
|
|
147
165
|
readonly #patches: PatchOperation[];
|
|
148
166
|
readonly #sourceVersionId: number;
|
|
149
167
|
readonly #verificationMethod: DidVerificationMethod;
|
|
150
168
|
readonly #beaconService: BeaconService;
|
|
151
169
|
|
|
152
|
-
#unsignedUpdate: UnsignedBTCR2Update | null = null;
|
|
153
|
-
#signedUpdate: SignedBTCR2Update | null = null;
|
|
154
|
-
|
|
155
170
|
/**
|
|
156
171
|
* @internal — Use {@link DidBtcr2.update} to create instances.
|
|
157
172
|
*/
|
|
@@ -196,6 +211,16 @@ export class Updater {
|
|
|
196
211
|
|
|
197
212
|
const targetDocument = JSONPatch.apply(sourceDocument, patches);
|
|
198
213
|
|
|
214
|
+
// Spec (operations/update.md): "An INVALID_DID_UPDATE error MUST be raised if
|
|
215
|
+
// didTargetDocument.id is not equal to didSourceDocument.id." `DidDocument.isValid`
|
|
216
|
+
// checks W3C conformance but not this equality, so it's enforced explicitly here.
|
|
217
|
+
if(targetDocument.id !== sourceDocument.id) {
|
|
218
|
+
throw new UpdateError(
|
|
219
|
+
`Patches must not change the DID document id (source "${sourceDocument.id}" → target "${targetDocument.id}").`,
|
|
220
|
+
INVALID_DID_UPDATE, { sourceId: sourceDocument.id, targetId: targetDocument.id }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
199
224
|
try {
|
|
200
225
|
DidDocument.isValid(targetDocument);
|
|
201
226
|
} catch (error) {
|
|
@@ -215,18 +240,31 @@ export class Updater {
|
|
|
215
240
|
* @param {string} did The did-btcr2 identifier to derive the root capability from.
|
|
216
241
|
* @param {UnsignedBTCR2Update} unsignedUpdate The unsigned update to sign.
|
|
217
242
|
* @param {DidVerificationMethod} verificationMethod The verification method for signing.
|
|
218
|
-
* @param {
|
|
243
|
+
* @param {Signer} signer Signer that produces the BIP-340 Schnorr signature.
|
|
219
244
|
* @returns {SignedBTCR2Update} The signed update with a Data Integrity proof.
|
|
220
245
|
*/
|
|
221
246
|
static sign(
|
|
222
247
|
did: string,
|
|
223
248
|
unsignedUpdate: UnsignedBTCR2Update,
|
|
224
249
|
verificationMethod: DidVerificationMethod,
|
|
225
|
-
|
|
250
|
+
signer: Signer,
|
|
226
251
|
): SignedBTCR2Update {
|
|
252
|
+
if(!did.startsWith('did:btcr2:')) {
|
|
253
|
+
throw new UpdateError(
|
|
254
|
+
`Expected a did:btcr2 identifier for the root capability; got "${did}".`,
|
|
255
|
+
INVALID_DID_UPDATE, { did }
|
|
256
|
+
);
|
|
257
|
+
}
|
|
227
258
|
const controller = verificationMethod.controller;
|
|
228
|
-
const
|
|
229
|
-
|
|
259
|
+
const hashIdx = verificationMethod.id.indexOf('#');
|
|
260
|
+
if(hashIdx < 0) {
|
|
261
|
+
throw new UpdateError(
|
|
262
|
+
`Verification method id must contain a fragment (e.g. "${verificationMethod.id}#initialKey"); got "${verificationMethod.id}".`,
|
|
263
|
+
INVALID_DID_UPDATE, { verificationMethodId: verificationMethod.id }
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const id = verificationMethod.id.slice(hashIdx);
|
|
267
|
+
const multikey = SchnorrMultikey.fromSigner(id, controller, signer);
|
|
230
268
|
|
|
231
269
|
const config: DataIntegrityConfig = {
|
|
232
270
|
'@context' : [
|
|
@@ -253,21 +291,21 @@ export class Updater {
|
|
|
253
291
|
*
|
|
254
292
|
* @param {BeaconService} beaconService The beacon service to broadcast through.
|
|
255
293
|
* @param {SignedBTCR2Update} update The signed update to announce.
|
|
256
|
-
* @param {
|
|
294
|
+
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
257
295
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
258
296
|
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
259
297
|
*/
|
|
260
298
|
static async announce(
|
|
261
299
|
beaconService: BeaconService,
|
|
262
300
|
update: SignedBTCR2Update,
|
|
263
|
-
|
|
301
|
+
signer: Signer,
|
|
264
302
|
bitcoin: BitcoinConnection
|
|
265
303
|
): Promise<SignedBTCR2Update> {
|
|
266
304
|
const beacon = BeaconFactory.establish(beaconService);
|
|
267
|
-
return beacon.broadcastSignal(update,
|
|
305
|
+
return beacon.broadcastSignal(update, signer, bitcoin);
|
|
268
306
|
}
|
|
269
307
|
|
|
270
|
-
//
|
|
308
|
+
// Private instance wrappers
|
|
271
309
|
// Delegate to the public statics with bound instance fields for cleaner
|
|
272
310
|
// advance/provide code.
|
|
273
311
|
|
|
@@ -275,12 +313,6 @@ export class Updater {
|
|
|
275
313
|
return Updater.construct(this.#sourceDocument, this.#patches, this.#sourceVersionId);
|
|
276
314
|
}
|
|
277
315
|
|
|
278
|
-
#sign(secretKey: KeyBytes): SignedBTCR2Update {
|
|
279
|
-
return Updater.sign(this.#sourceDocument.id, this.#unsignedUpdate!, this.#verificationMethod, secretKey);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// ─── State machine ─────────────────────────────────────────────────────────
|
|
283
|
-
|
|
284
316
|
/**
|
|
285
317
|
* Advance the state machine. Returns either:
|
|
286
318
|
* - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
|
|
@@ -288,25 +320,25 @@ export class Updater {
|
|
|
288
320
|
*/
|
|
289
321
|
advance(): UpdaterState {
|
|
290
322
|
while(true) {
|
|
291
|
-
switch(this.#phase) {
|
|
323
|
+
switch(this.#state.phase) {
|
|
292
324
|
|
|
293
325
|
// Phase: Construct
|
|
294
326
|
// Build the unsigned update from source doc + patches. Pure, synchronous.
|
|
295
|
-
case
|
|
296
|
-
|
|
297
|
-
this.#
|
|
327
|
+
case 'Construct': {
|
|
328
|
+
const unsignedUpdate = this.#construct();
|
|
329
|
+
this.#state = { phase: 'Sign', unsignedUpdate };
|
|
298
330
|
continue;
|
|
299
331
|
}
|
|
300
332
|
|
|
301
333
|
// Phase: Sign
|
|
302
334
|
// Emit NeedSigningKey — the caller supplies the secret key (or a KMS signature).
|
|
303
|
-
case
|
|
335
|
+
case 'Sign': {
|
|
304
336
|
return {
|
|
305
337
|
status : 'action-required',
|
|
306
338
|
needs : [{
|
|
307
339
|
kind : 'NeedSigningKey',
|
|
308
340
|
verificationMethodId : this.#verificationMethod.id,
|
|
309
|
-
unsignedUpdate : this.#unsignedUpdate
|
|
341
|
+
unsignedUpdate : this.#state.unsignedUpdate,
|
|
310
342
|
}],
|
|
311
343
|
};
|
|
312
344
|
}
|
|
@@ -314,7 +346,7 @@ export class Updater {
|
|
|
314
346
|
// Phase: Fund
|
|
315
347
|
// Emit NeedFunding with the beacon address. The caller checks UTXOs,
|
|
316
348
|
// funds the address if needed, and provides to continue.
|
|
317
|
-
case
|
|
349
|
+
case 'Fund': {
|
|
318
350
|
const beaconAddress = this.#beaconService.serviceEndpoint.replace('bitcoin:', '');
|
|
319
351
|
return {
|
|
320
352
|
status : 'action-required',
|
|
@@ -329,22 +361,22 @@ export class Updater {
|
|
|
329
361
|
// Phase: Broadcast
|
|
330
362
|
// Emit NeedBroadcast with the signed update + beacon service. The caller performs
|
|
331
363
|
// the actual on-chain announcement (or hands off to the aggregation protocol).
|
|
332
|
-
case
|
|
364
|
+
case 'Broadcast': {
|
|
333
365
|
return {
|
|
334
366
|
status : 'action-required',
|
|
335
367
|
needs : [{
|
|
336
368
|
kind : 'NeedBroadcast',
|
|
337
369
|
beaconService : this.#beaconService,
|
|
338
|
-
signedUpdate : this.#signedUpdate
|
|
370
|
+
signedUpdate : this.#state.signedUpdate,
|
|
339
371
|
}],
|
|
340
372
|
};
|
|
341
373
|
}
|
|
342
374
|
|
|
343
375
|
// Phase: Complete
|
|
344
|
-
case
|
|
376
|
+
case 'Complete': {
|
|
345
377
|
return {
|
|
346
378
|
status : 'complete',
|
|
347
|
-
result : { signedUpdate: this.#signedUpdate
|
|
379
|
+
result : { signedUpdate: this.#state.signedUpdate },
|
|
348
380
|
};
|
|
349
381
|
}
|
|
350
382
|
}
|
|
@@ -358,56 +390,70 @@ export class Updater {
|
|
|
358
390
|
* @param need The DataNeed being fulfilled (from the `needs` array).
|
|
359
391
|
* @param data The data payload corresponding to the need kind (omit for NeedFunding/NeedBroadcast).
|
|
360
392
|
*/
|
|
361
|
-
provide(need: NeedSigningKey, data:
|
|
362
|
-
provide(need: NeedFunding): void;
|
|
393
|
+
provide(need: NeedSigningKey, data: Signer): void;
|
|
394
|
+
provide(need: NeedFunding, proof?: FundingProof): void;
|
|
363
395
|
provide(need: NeedBroadcast): void;
|
|
364
|
-
provide(need: UpdaterDataNeed, data?:
|
|
396
|
+
provide(need: UpdaterDataNeed, data?: Signer | FundingProof): void {
|
|
365
397
|
switch(need.kind) {
|
|
366
398
|
case 'NeedSigningKey': {
|
|
367
|
-
if(this.#phase !==
|
|
399
|
+
if(this.#state.phase !== 'Sign') {
|
|
368
400
|
throw new UpdateError(
|
|
369
|
-
`Cannot provide NeedSigningKey: updater phase is ${this.#phase}, expected Sign.`,
|
|
370
|
-
INVALID_DID_UPDATE, { phase: this.#phase }
|
|
401
|
+
`Cannot provide NeedSigningKey: updater phase is ${this.#state.phase}, expected Sign.`,
|
|
402
|
+
INVALID_DID_UPDATE, { phase: this.#state.phase }
|
|
371
403
|
);
|
|
372
404
|
}
|
|
373
405
|
if(!data) {
|
|
374
406
|
throw new UpdateError(
|
|
375
|
-
'NeedSigningKey requires
|
|
376
|
-
INVALID_DID_UPDATE
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
if(!this.#unsignedUpdate) {
|
|
380
|
-
throw new UpdateError(
|
|
381
|
-
'Internal error: unsigned update missing in Sign phase.',
|
|
407
|
+
'NeedSigningKey requires a Signer.',
|
|
382
408
|
INVALID_DID_UPDATE
|
|
383
409
|
);
|
|
384
410
|
}
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
const unsignedUpdate = this.#state.unsignedUpdate;
|
|
412
|
+
const signedUpdate = Updater.sign(
|
|
413
|
+
this.#sourceDocument.id, unsignedUpdate, this.#verificationMethod, data as Signer,
|
|
414
|
+
);
|
|
415
|
+
this.#state = { phase: 'Fund', unsignedUpdate, signedUpdate };
|
|
387
416
|
break;
|
|
388
417
|
}
|
|
389
418
|
|
|
390
419
|
case 'NeedFunding': {
|
|
391
|
-
if(this.#phase !==
|
|
420
|
+
if(this.#state.phase !== 'Fund') {
|
|
392
421
|
throw new UpdateError(
|
|
393
|
-
`Cannot provide NeedFunding: updater phase is ${this.#phase}, expected Fund.`,
|
|
394
|
-
INVALID_DID_UPDATE, { phase: this.#phase }
|
|
422
|
+
`Cannot provide NeedFunding: updater phase is ${this.#state.phase}, expected Fund.`,
|
|
423
|
+
INVALID_DID_UPDATE, { phase: this.#state.phase }
|
|
395
424
|
);
|
|
396
425
|
}
|
|
397
|
-
//
|
|
398
|
-
|
|
426
|
+
// If the caller supplies a FundingProof, assert it before transitioning.
|
|
427
|
+
// Optional payload preserves the sans-I/O contract: the caller still does
|
|
428
|
+
// the actual UTXO lookup; this is a contract-level handshake that catches
|
|
429
|
+
// a class of caller bugs (forgot to fund, race with mempool, etc.) at the
|
|
430
|
+
// state-machine boundary rather than at broadcast time.
|
|
431
|
+
if(data !== undefined) {
|
|
432
|
+
const proof = data as FundingProof;
|
|
433
|
+
if(typeof proof.utxoCount !== 'number' || !Number.isFinite(proof.utxoCount) || proof.utxoCount < 1) {
|
|
434
|
+
throw new UpdateError(
|
|
435
|
+
`NeedFunding proof must have utxoCount >= 1; got ${String(proof.utxoCount)}.`,
|
|
436
|
+
INVALID_DID_UPDATE, { utxoCount: proof.utxoCount }
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
this.#state = {
|
|
441
|
+
phase : 'Broadcast',
|
|
442
|
+
unsignedUpdate : this.#state.unsignedUpdate,
|
|
443
|
+
signedUpdate : this.#state.signedUpdate,
|
|
444
|
+
};
|
|
399
445
|
break;
|
|
400
446
|
}
|
|
401
447
|
|
|
402
448
|
case 'NeedBroadcast': {
|
|
403
|
-
if(this.#phase !==
|
|
449
|
+
if(this.#state.phase !== 'Broadcast') {
|
|
404
450
|
throw new UpdateError(
|
|
405
|
-
`Cannot provide NeedBroadcast: updater phase is ${this.#phase}, expected Broadcast.`,
|
|
406
|
-
INVALID_DID_UPDATE, { phase: this.#phase }
|
|
451
|
+
`Cannot provide NeedBroadcast: updater phase is ${this.#state.phase}, expected Broadcast.`,
|
|
452
|
+
INVALID_DID_UPDATE, { phase: this.#state.phase }
|
|
407
453
|
);
|
|
408
454
|
}
|
|
409
455
|
// Caller has broadcast externally. Transition to Complete.
|
|
410
|
-
this.#
|
|
456
|
+
this.#state = { phase: 'Complete', signedUpdate: this.#state.signedUpdate };
|
|
411
457
|
break;
|
|
412
458
|
}
|
|
413
459
|
}
|
package/src/did-btcr2.ts
CHANGED
|
@@ -17,8 +17,6 @@ import {
|
|
|
17
17
|
DidError,
|
|
18
18
|
DidErrorCode
|
|
19
19
|
} from '@web5/dids';
|
|
20
|
-
import * as ecc from '@bitcoinerlab/secp256k1';
|
|
21
|
-
import { initEccLib } from 'bitcoinjs-lib';
|
|
22
20
|
import type { BeaconService } from './core/beacon/interfaces.js';
|
|
23
21
|
import { Identifier } from './core/identifier.js';
|
|
24
22
|
import type { ResolutionOptions } from './core/interfaces.js';
|
|
@@ -36,9 +34,6 @@ export interface DidCreateOptions {
|
|
|
36
34
|
network?: string;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
/** Initialize secp256k1 ECC library */
|
|
40
|
-
initEccLib(ecc);
|
|
41
|
-
|
|
42
37
|
/**
|
|
43
38
|
* Implements {@link https://dcdpr.github.io/did-btcr2 | did:btcr2 DID Method Specification}.
|
|
44
39
|
* did:btcr2 is a censorship-resistant Decentralized Identifier (DID) method using
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,8 @@ export * from './core/aggregation/cohort.js';
|
|
|
5
5
|
export * from './core/aggregation/signing-session.js';
|
|
6
6
|
export * from './core/aggregation/phases.js';
|
|
7
7
|
export * from './core/aggregation/errors.js';
|
|
8
|
+
export * from './core/aggregation/beacon-strategy.js';
|
|
9
|
+
export * from './core/aggregation/logger.js';
|
|
8
10
|
export * from './core/aggregation/messages/index.js';
|
|
9
11
|
export * from './core/aggregation/transport/index.js';
|
|
10
12
|
export * from './core/aggregation/runner/index.js';
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
|
|
16
16
|
import type { DidDocument as W3CDidDocument, DidVerificationMethod as W3CDidVerificationMethod } from '@web5/dids';
|
|
17
17
|
import { isDidService } from '@web5/dids/utils';
|
|
18
|
-
import {
|
|
18
|
+
import { p2pkh } from '@scure/btc-signer';
|
|
19
19
|
import type { BeaconService } from '../core/beacon/interfaces.js';
|
|
20
20
|
import { Identifier } from '../core/identifier.js';
|
|
21
21
|
import { Appendix } from './appendix.js';
|
|
@@ -491,7 +491,7 @@ export class GenesisDocument extends DidDocument {
|
|
|
491
491
|
public static fromPublicKey(publicKey: KeyBytes, network: string): GenesisDocument {
|
|
492
492
|
const pk = new CompressedSecp256k1PublicKey(publicKey);
|
|
493
493
|
const id = ID_PLACEHOLDER_VALUE;
|
|
494
|
-
const address =
|
|
494
|
+
const address = p2pkh(pk.compressed, getNetwork(network)).address;
|
|
495
495
|
const services = [{
|
|
496
496
|
id : `${id}#service-0`,
|
|
497
497
|
serviceEndpoint : `bitcoin:${address}`,
|