@did-btcr2/method 0.19.0 → 0.20.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 +715 -1445
- package/dist/browser.mjs +715 -1445
- package/dist/cjs/core/beacon/beacon.js +25 -0
- package/dist/cjs/core/beacon/beacon.js.map +1 -0
- package/dist/cjs/core/beacon/cas-beacon.js +20 -36
- package/dist/cjs/core/beacon/cas-beacon.js.map +1 -1
- package/dist/cjs/core/beacon/error.js +4 -4
- package/dist/cjs/core/beacon/error.js.map +1 -1
- package/dist/cjs/core/beacon/factory.js +5 -7
- package/dist/cjs/core/beacon/factory.js.map +1 -1
- package/dist/cjs/core/beacon/interfaces.js +1 -31
- package/dist/cjs/core/beacon/interfaces.js.map +1 -1
- package/dist/cjs/core/beacon/signal-discovery.js +183 -0
- package/dist/cjs/core/beacon/signal-discovery.js.map +1 -0
- package/dist/cjs/core/beacon/singleton.js +54 -81
- package/dist/cjs/core/beacon/singleton.js.map +1 -1
- package/dist/cjs/core/beacon/smt-beacon.js +22 -39
- package/dist/cjs/core/beacon/smt-beacon.js.map +1 -1
- package/dist/cjs/core/beacon/utils.js +4 -9
- package/dist/cjs/core/beacon/utils.js.map +1 -1
- package/dist/cjs/core/resolve.js +87 -277
- package/dist/cjs/core/resolve.js.map +1 -1
- package/dist/cjs/core/update.js +62 -153
- package/dist/cjs/core/update.js.map +1 -1
- package/dist/cjs/did-btcr2.js +80 -75
- package/dist/cjs/did-btcr2.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/utils/appendix.js +6 -15
- package/dist/cjs/utils/appendix.js.map +1 -1
- package/dist/cjs/utils/did-document-builder.js +5 -0
- package/dist/cjs/utils/did-document-builder.js.map +1 -1
- package/dist/cjs/utils/did-document.js +11 -14
- package/dist/cjs/utils/did-document.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +25 -0
- package/dist/esm/core/beacon/beacon.js.map +1 -0
- package/dist/esm/core/beacon/cas-beacon.js +20 -36
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/error.js +4 -4
- package/dist/esm/core/beacon/error.js.map +1 -1
- package/dist/esm/core/beacon/factory.js +5 -7
- package/dist/esm/core/beacon/factory.js.map +1 -1
- package/dist/esm/core/beacon/interfaces.js +1 -31
- package/dist/esm/core/beacon/interfaces.js.map +1 -1
- package/dist/esm/core/beacon/signal-discovery.js +183 -0
- package/dist/esm/core/beacon/signal-discovery.js.map +1 -0
- package/dist/esm/core/beacon/singleton.js +54 -81
- package/dist/esm/core/beacon/singleton.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +22 -39
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +4 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/core/resolve.js +87 -277
- package/dist/esm/core/resolve.js.map +1 -1
- package/dist/esm/core/update.js +62 -153
- package/dist/esm/core/update.js.map +1 -1
- package/dist/esm/did-btcr2.js +80 -75
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/appendix.js +6 -15
- package/dist/esm/utils/appendix.js.map +1 -1
- package/dist/esm/utils/did-document-builder.js +5 -0
- package/dist/esm/utils/did-document-builder.js.map +1 -1
- package/dist/esm/utils/did-document.js +11 -14
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +44 -0
- package/dist/types/core/beacon/beacon.d.ts.map +1 -0
- package/dist/types/core/beacon/cas-beacon.d.ts +19 -30
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/error.d.ts +2 -2
- package/dist/types/core/beacon/error.d.ts.map +1 -1
- package/dist/types/core/beacon/factory.d.ts +4 -6
- package/dist/types/core/beacon/factory.d.ts.map +1 -1
- package/dist/types/core/beacon/interfaces.d.ts +7 -46
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/signal-discovery.d.ts +25 -0
- package/dist/types/core/beacon/signal-discovery.d.ts.map +1 -0
- package/dist/types/core/beacon/singleton.d.ts +16 -29
- package/dist/types/core/beacon/singleton.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +21 -33
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +1 -8
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolve.d.ts +27 -43
- package/dist/types/core/resolve.d.ts.map +1 -1
- package/dist/types/core/types.d.ts +21 -8
- package/dist/types/core/types.d.ts.map +1 -1
- package/dist/types/core/update.d.ts +30 -73
- package/dist/types/core/update.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts +37 -44
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/appendix.d.ts.map +1 -1
- package/dist/types/utils/did-document-builder.d.ts +5 -0
- package/dist/types/utils/did-document-builder.d.ts.map +1 -1
- package/dist/types/utils/did-document.d.ts +16 -14
- package/dist/types/utils/did-document.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/beacon/beacon.ts +58 -0
- package/src/core/beacon/cas-beacon.ts +30 -44
- package/src/core/beacon/error.ts +5 -6
- package/src/core/beacon/factory.ts +7 -9
- package/src/core/beacon/interfaces.ts +7 -64
- package/src/core/beacon/signal-discovery.ts +237 -0
- package/src/core/beacon/singleton.ts +74 -94
- package/src/core/beacon/smt-beacon.ts +32 -49
- package/src/core/beacon/utils.ts +16 -13
- package/src/core/interfaces.ts +1 -9
- package/src/core/resolve.ts +116 -360
- package/src/core/types.ts +25 -8
- package/src/core/update.ts +90 -235
- package/src/did-btcr2.ts +131 -95
- package/src/index.ts +8 -1
- package/src/utils/appendix.ts +8 -22
- package/src/utils/did-document-builder.ts +5 -0
- package/src/utils/did-document.ts +41 -45
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BitcoinNetworkConnection,
|
|
3
|
+
BlockV3,
|
|
4
|
+
GENESIS_TX_ID,
|
|
5
|
+
RawTransactionV2,
|
|
6
|
+
TXIN_WITNESS_COINBASE
|
|
7
|
+
} from '@did-btcr2/bitcoin';
|
|
8
|
+
import { ResolveError } from '@did-btcr2/common';
|
|
9
|
+
import { BeaconService, BeaconSignal } from './interfaces.js';
|
|
10
|
+
import { BeaconUtils } from './utils.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Static utility class for discovering Beacon Signals on the Bitcoin blockchain.
|
|
14
|
+
* Extracted from {@link Resolve} for single-responsibility and independent testability.
|
|
15
|
+
*
|
|
16
|
+
* @class BeaconSignalDiscovery
|
|
17
|
+
*/
|
|
18
|
+
export class BeaconSignalDiscovery {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves the beacon signals for the given array of BeaconService objects
|
|
22
|
+
* using an esplora/electrs REST API connection via a bitcoin I/O driver.
|
|
23
|
+
* @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
|
|
24
|
+
* @param {BitcoinNetworkConnection} bitcoin Bitcoin network connection to use for REST calls
|
|
25
|
+
* @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals
|
|
26
|
+
*/
|
|
27
|
+
static async indexer(
|
|
28
|
+
beaconServices: Array<BeaconService>,
|
|
29
|
+
bitcoin: BitcoinNetworkConnection
|
|
30
|
+
): Promise<Map<BeaconService, Array<BeaconSignal>>> {
|
|
31
|
+
const beaconServiceSignals = new Map<BeaconService, Array<BeaconSignal>>();
|
|
32
|
+
|
|
33
|
+
// Fetch the current block count once before the loop
|
|
34
|
+
const currentBlockCount = await bitcoin.network.rest.block.count();
|
|
35
|
+
|
|
36
|
+
// Iterate over each beacon
|
|
37
|
+
for (const beaconService of beaconServices) {
|
|
38
|
+
beaconServiceSignals.set(beaconService, []);
|
|
39
|
+
// Get the transactions for the beacon address via REST
|
|
40
|
+
const beaconSignals = await bitcoin.network.rest.address.getTxs(
|
|
41
|
+
beaconService.serviceEndpoint as string
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// If no signals are found, continue
|
|
45
|
+
if (!beaconSignals || !beaconSignals.length) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Iterate over each signal
|
|
50
|
+
for (const beaconSignal of beaconSignals) {
|
|
51
|
+
// Get the last vout in the transaction
|
|
52
|
+
const signalVout = beaconSignal.vout.slice(-1)[0];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Look for OP_RETURN in last vout scriptpubkey_asm
|
|
56
|
+
* Vout (rest) format:
|
|
57
|
+
* {
|
|
58
|
+
* scriptpubkey: '6a20570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
|
|
59
|
+
* scriptpubkey_asm: 'OP_RETURN OP_PUSHBYTES_32 570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
|
|
60
|
+
* scriptpubkey_type: 'op_return',
|
|
61
|
+
* value: 0
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
if(!signalVout || !signalVout.scriptpubkey_asm.includes('OP_RETURN')) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Construct output map for easier access
|
|
69
|
+
const outputMap = new Map<string, string | number>(Object.entries(signalVout));
|
|
70
|
+
|
|
71
|
+
// Grab the signal vout scriptpubkey
|
|
72
|
+
const signalVoutScriptPubkey = outputMap.get('scriptpubkey_asm') as string;
|
|
73
|
+
|
|
74
|
+
// If the signal vout scriptpubkey does not exist, continue to next signal
|
|
75
|
+
if(!signalVoutScriptPubkey){
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Extract hex string hash of the signal bytes from the scriptpubkey
|
|
80
|
+
const updateHash = signalVoutScriptPubkey.split(' ').slice(-1)[0];
|
|
81
|
+
if(!updateHash) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Use the pre-fetched block count instead of calling per-signal
|
|
86
|
+
const confirmations = currentBlockCount - beaconSignal.status.block_height + 1;
|
|
87
|
+
|
|
88
|
+
// Push the beacon signal object to the signals array for the beacon service
|
|
89
|
+
beaconServiceSignals.get(beaconService)?.push({
|
|
90
|
+
tx : beaconSignal,
|
|
91
|
+
signalBytes : updateHash,
|
|
92
|
+
blockMetadata : {
|
|
93
|
+
confirmations,
|
|
94
|
+
height : beaconSignal.status.block_height,
|
|
95
|
+
time : beaconSignal.status.block_time,
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return beaconServiceSignals;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Traverse the full blockchain from genesis to chain top looking for beacon signals.
|
|
106
|
+
* @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
|
|
107
|
+
* @param {BitcoinNetworkConnection} bitcoin Bitcoin network connection to use for RPC calls.
|
|
108
|
+
* @returns {Promise<Map<BeaconService, Array<BeaconSignal>>>} Map of beacon service to its discovered signals.
|
|
109
|
+
*/
|
|
110
|
+
static async fullnode(
|
|
111
|
+
beaconServices: Array<BeaconService>,
|
|
112
|
+
bitcoin: BitcoinNetworkConnection
|
|
113
|
+
): Promise<Map<BeaconService, Array<BeaconSignal>>> {
|
|
114
|
+
const beaconServiceSignals = new Map<BeaconService, Array<BeaconSignal>>();
|
|
115
|
+
|
|
116
|
+
for(const beaconService of beaconServices) {
|
|
117
|
+
beaconServiceSignals.set(beaconService, []);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get the RPC connection from the bitcoin network
|
|
121
|
+
const rpc = bitcoin.network.rpc;
|
|
122
|
+
|
|
123
|
+
// Ensure that the RPC connection is available
|
|
124
|
+
if(!rpc) {
|
|
125
|
+
throw new ResolveError('RPC connection is not available', 'RPC_CONNECTION_ERROR', bitcoin);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get the current block height once before the loop
|
|
129
|
+
const targetHeight = await rpc.getBlockCount();
|
|
130
|
+
|
|
131
|
+
// Hoist the beacon services map before the loop
|
|
132
|
+
const beaconServicesMap = BeaconUtils.getBeaconServicesMap(beaconServices);
|
|
133
|
+
|
|
134
|
+
// Set genesis height
|
|
135
|
+
let height = 0;
|
|
136
|
+
|
|
137
|
+
// Opt into rpc connection to get the block data at the blockhash
|
|
138
|
+
let block = await bitcoin.network.rpc!.getBlock({ height }) as BlockV3;
|
|
139
|
+
|
|
140
|
+
console.info(`Searching for beacon signals, please wait ...`);
|
|
141
|
+
while (block.height <= targetHeight) {
|
|
142
|
+
// Iterate over each transaction in the block
|
|
143
|
+
for (const tx of block.tx) {
|
|
144
|
+
// If the txid is a coinbase, continue ...
|
|
145
|
+
if (tx.txid === GENESIS_TX_ID) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Iterate over each input in the transaction
|
|
150
|
+
for (const vin of tx.vin) {
|
|
151
|
+
|
|
152
|
+
// If the vin is a coinbase transaction, continue ...
|
|
153
|
+
if (vin.coinbase) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If the vin txinwitness contains a coinbase did, continue ...
|
|
158
|
+
if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === TXIN_WITNESS_COINBASE) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// If the txid from the vin is undefined, continue ...
|
|
163
|
+
if (!vin.txid) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If the vout from the vin is undefined, continue ...
|
|
168
|
+
if (vin.vout === undefined) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Get the previous output transaction data
|
|
173
|
+
const prevout = await rpc.getRawTransaction(vin.txid, 2) as RawTransactionV2;
|
|
174
|
+
|
|
175
|
+
// If the previous output vout at the vin.vout index is undefined, continue ...
|
|
176
|
+
if (!prevout.vout[vin.vout]) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Get the address from the scriptPubKey from the prevvout
|
|
181
|
+
const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
|
|
182
|
+
|
|
183
|
+
// If the scriptPubKey.address is undefined, continue ...
|
|
184
|
+
if (!scriptPubKey.address) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Use the hoisted beaconServicesMap instead of rebuilding per-vin
|
|
189
|
+
const beaconService = beaconServicesMap.get(scriptPubKey.address);
|
|
190
|
+
if (!beaconService) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Look for 'OP_RETURN' in the scriptPubKey asm
|
|
195
|
+
const txVoutScriptPubkeyAsm = prevout.vout[vin.vout].scriptPubKey.asm;
|
|
196
|
+
if(!txVoutScriptPubkeyAsm.includes('OP_RETURN')) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Log the found txid and beacon
|
|
201
|
+
console.info(`Tx ${tx.txid} contains beacon service address ${scriptPubKey.address} and OP_RETURN!`, tx);
|
|
202
|
+
|
|
203
|
+
// Extract hex string hash of the signal bytes from the scriptpubkey
|
|
204
|
+
const updateHash = txVoutScriptPubkeyAsm.split(' ').slice(-1)[0];
|
|
205
|
+
if(!updateHash) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Push the beacon signal object to the beacon signals array for that beacon service
|
|
210
|
+
beaconServiceSignals.get(beaconService)?.push({
|
|
211
|
+
tx,
|
|
212
|
+
signalBytes : updateHash,
|
|
213
|
+
blockMetadata : {
|
|
214
|
+
height : block.height,
|
|
215
|
+
time : block.time,
|
|
216
|
+
confirmations : block.confirmations
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Increment the height
|
|
223
|
+
height += 1;
|
|
224
|
+
|
|
225
|
+
// Use pre-fetched targetHeight instead of calling rpc.getBlockCount() every iteration
|
|
226
|
+
if(height > targetHeight) {
|
|
227
|
+
console.info(`Chain tip reached ${height}, breaking ...`);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Reset the block var to the next block data
|
|
232
|
+
block = await rpc.getBlock({ height }) as BlockV3;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return beaconServiceSignals;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,74 +1,52 @@
|
|
|
1
1
|
import { AddressUtxo, BitcoinNetworkConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { INVALID_SIDECAR_DATA, KeyBytes, MISSING_UPDATE_DATA } from '@did-btcr2/common';
|
|
3
|
+
import { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
|
+
import { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
5
|
+
import { Signer } from '@did-btcr2/kms';
|
|
6
6
|
import { opcodes, Psbt, script } from 'bitcoinjs-lib';
|
|
7
7
|
import { base58btc } from 'multiformats/bases/base58';
|
|
8
8
|
import { canonicalization } from '../../did-btcr2.js';
|
|
9
|
-
import { Identifier } from '../identifier.js';
|
|
10
9
|
import { SidecarData } from '../types.js';
|
|
11
|
-
import {
|
|
10
|
+
import { Beacon } from './beacon.js';
|
|
11
|
+
import { SingletonBeaconError } from './error.js';
|
|
12
|
+
import { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#singleton-beacon | Singleton Beacon}.
|
|
15
|
-
*
|
|
16
16
|
* @class SingletonBeacon
|
|
17
17
|
* @type {SingletonBeacon}
|
|
18
18
|
* @extends {AggregateBeacon}
|
|
19
19
|
*/
|
|
20
|
-
export class SingletonBeacon extends
|
|
20
|
+
export class SingletonBeacon extends Beacon {
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Creates an instance of SingletonBeacon.
|
|
24
|
-
* @param {BeaconService} service The
|
|
25
|
-
*
|
|
26
|
-
*/
|
|
27
|
-
constructor(
|
|
28
|
-
service: BeaconService,
|
|
29
|
-
signals: Array<BeaconSignal>,
|
|
30
|
-
sidecar: SidecarData,
|
|
31
|
-
bitcoin?: BitcoinNetworkConnection
|
|
32
|
-
) {
|
|
33
|
-
super({ ...service, type: 'SingletonBeacon' }, signals, sidecar, bitcoin);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Static, convenience method for establishing a CASBeacon object.
|
|
38
|
-
* @param {string} service The Beacon service.
|
|
39
|
-
* @param {SidecarData} sidecar The sidecar data.
|
|
40
|
-
* @returns {SingletonBeacon} The Singleton Beacon.
|
|
41
|
-
*/
|
|
42
|
-
static establish(service: BeaconService, signals: Array<BeaconSignal>, sidecar: SidecarData): SingletonBeacon {
|
|
43
|
-
return new SingletonBeacon(service, signals, sidecar);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Generates a Beacon Signal for a Singleton Beacon Service.
|
|
48
|
-
* @param {HexString} updateHash The update hash to be included in the Beacon Signal.
|
|
49
|
-
* @returns {BeaconSignal} The generated signal.
|
|
50
|
-
* @throws {MethodError} if the signal is invalid.
|
|
24
|
+
* @param {BeaconService} service The BeaconService object representing the funded beacon to announce the update to.
|
|
25
|
+
*
|
|
51
26
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
constructor(service: BeaconService) {
|
|
28
|
+
super({ ...service, type: 'SingletonBeacon' });
|
|
54
29
|
}
|
|
55
30
|
|
|
56
31
|
/**
|
|
57
32
|
* Processes an array of Beacon Signals associated with a Singleton Beacon Service.
|
|
58
|
-
* @returns {Promise<
|
|
33
|
+
* @returns {Promise<SignedBTCR2Update | undefined>} The DID Update payload announced by the Beacon Signal.
|
|
59
34
|
* @throws {SingletonBeaconError} if the signalTx is invalid or the signalSidecarData is invalid.
|
|
60
35
|
*/
|
|
61
|
-
async processSignals(
|
|
36
|
+
async processSignals(
|
|
37
|
+
signals: Array<BeaconSignal>,
|
|
38
|
+
sidecar: SidecarData
|
|
39
|
+
): Promise<Array<[SignedBTCR2Update, BlockMetadata]>> {
|
|
62
40
|
// Initialize an empty array to hold the BTCR2 signed updates
|
|
63
|
-
const updates = new Array<[
|
|
41
|
+
const updates = new Array<[SignedBTCR2Update, BlockMetadata]>();
|
|
64
42
|
|
|
65
|
-
// Loop through each signal in
|
|
66
|
-
for(const signal of
|
|
43
|
+
// Loop through each signal in signals
|
|
44
|
+
for(const signal of signals) {
|
|
67
45
|
// Grab the beacon signal bytes hash from the signal
|
|
68
46
|
const updateHash = signal.signalBytes;
|
|
69
47
|
|
|
70
48
|
// Use the updateHash as the sidecar data lookup key to retrieve the btcr2 update
|
|
71
|
-
const signedUpdate =
|
|
49
|
+
const signedUpdate = sidecar.updateMap.get(updateHash);
|
|
72
50
|
|
|
73
51
|
// If no btcr2 update is found in sidecar data maps, throw missingUpdateData error.
|
|
74
52
|
if(!signedUpdate) {
|
|
@@ -100,28 +78,39 @@ export class SingletonBeacon extends AggregateBeacon {
|
|
|
100
78
|
// Return the array of signed updates
|
|
101
79
|
return updates;
|
|
102
80
|
}
|
|
103
|
-
|
|
104
81
|
/**
|
|
105
|
-
* Broadcasts a SingletonBeacon signal.
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* @
|
|
82
|
+
* Broadcasts a SingletonBeacon signal to the Bitcoin network.
|
|
83
|
+
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
84
|
+
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
85
|
+
* @param {BitcoinNetworkConnection} bitcoin The Bitcoin network connection.
|
|
86
|
+
* @returns {Promise<SignedBTCR2Update>} The signed update that was broadcast.
|
|
109
87
|
* @throws {SingletonBeaconError} if the bitcoin address is invalid or unfunded.
|
|
110
88
|
*/
|
|
111
|
-
async broadcastSignal(
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
async broadcastSignal(
|
|
90
|
+
signedUpdate: SignedBTCR2Update,
|
|
91
|
+
secretKey: KeyBytes,
|
|
92
|
+
bitcoin: BitcoinNetworkConnection
|
|
93
|
+
): Promise<SignedBTCR2Update> {
|
|
94
|
+
// Convert the serviceEndpoint to a bitcoin address by removing the 'bitcoin:' prefix
|
|
114
95
|
const bitcoinAddress = this.service.serviceEndpoint.replace('bitcoin:', '');
|
|
115
96
|
|
|
116
|
-
//
|
|
117
|
-
|
|
97
|
+
// Query the Bitcoin network for UTXOs associated with the bitcoinAddress
|
|
98
|
+
const utxos = await bitcoin.network.rest.address.getUtxos(bitcoinAddress);
|
|
118
99
|
|
|
119
|
-
|
|
100
|
+
// If no utxos are found, throw an error indicating the address is unfunded.
|
|
120
101
|
if(!utxos.length) {
|
|
121
|
-
throw new SingletonBeaconError(
|
|
102
|
+
throw new SingletonBeaconError(
|
|
103
|
+
'No UTXOs found, please fund address!',
|
|
104
|
+
'UNFUNDED_BEACON_ADDRESS', { bitcoinAddress }
|
|
105
|
+
);
|
|
122
106
|
}
|
|
123
107
|
|
|
124
|
-
|
|
108
|
+
// Sort utxos by block height and take the most recent one
|
|
109
|
+
const utxo: AddressUtxo | undefined = utxos.sort(
|
|
110
|
+
(a, b) => b.status.block_height - a.status.block_height
|
|
111
|
+
).shift();
|
|
112
|
+
|
|
113
|
+
// If no utxos are found, throw an error.
|
|
125
114
|
if(!utxo) {
|
|
126
115
|
throw new SingletonBeaconError(
|
|
127
116
|
'Beacon bitcoin address unfunded or utxos unconfirmed.',
|
|
@@ -129,55 +118,46 @@ export class SingletonBeacon extends AggregateBeacon {
|
|
|
129
118
|
);
|
|
130
119
|
}
|
|
131
120
|
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// TODO: Figure out a good way to estimate fees
|
|
149
|
-
const spendTx = new Psbt({ network: this.bitcoin.network.data })
|
|
150
|
-
.addInput(input)
|
|
121
|
+
// Get the previous tx to the utxo being spent
|
|
122
|
+
const prevTx = await bitcoin.network.rest.transaction.getHex(utxo.txid);
|
|
123
|
+
|
|
124
|
+
// Canonicalize and hash the signed update for OP_RETURN output
|
|
125
|
+
const updateHash = canonicalization.canonicalhash(signedUpdate);
|
|
126
|
+
|
|
127
|
+
// Construct a spend transaction
|
|
128
|
+
const spendTx = new Psbt({ network: bitcoin.network.data })
|
|
129
|
+
// Spend tx contains the utxo as its input
|
|
130
|
+
.addInput({
|
|
131
|
+
hash : utxo.txid,
|
|
132
|
+
index : utxo.vout,
|
|
133
|
+
nonWitnessUtxo : Buffer.from(prevTx, 'hex')
|
|
134
|
+
})
|
|
135
|
+
// Add a change output minus a fee of 500 sats
|
|
136
|
+
// TODO: calculate fee based on transaction vsize and current fee rates
|
|
151
137
|
.addOutput({ address: bitcoinAddress, value: BigInt(utxo.value) - BigInt(500) })
|
|
152
|
-
|
|
138
|
+
// Add an OP_RETURN output containing the update hash
|
|
139
|
+
.addOutput({ script: script.compile([opcodes.OP_RETURN, updateHash]), value: 0n });
|
|
153
140
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
const components = Identifier.decode(this.service.id);
|
|
157
|
-
const keyUri = new CompressedSecp256k1PublicKey(components.genesisBytes).hex;
|
|
158
|
-
const keyPair = Kms.getKey(keyUri as string);
|
|
141
|
+
// Construct a Schnorr key pair from the secret key
|
|
142
|
+
const keyPair = SchnorrKeyPair.fromSecret(secretKey);
|
|
159
143
|
if (!keyPair) {
|
|
160
|
-
throw new
|
|
144
|
+
throw new SingletonBeaconError('Key pair not found.', 'KEY_PAIR_NOT_FOUND', { secretKey });
|
|
161
145
|
}
|
|
162
146
|
|
|
163
|
-
|
|
147
|
+
// Construct a signer object from the key pair and bitcoin network
|
|
148
|
+
const signer = new Signer({ keyPair, network: bitcoin.network.name });
|
|
164
149
|
|
|
165
|
-
//
|
|
150
|
+
// Sign 0th input, finalize extract to hex in prep for broadcast
|
|
166
151
|
const signedTx = spendTx.signInput(0, signer)
|
|
167
152
|
.finalizeAllInputs()
|
|
168
153
|
.extractTransaction()
|
|
169
154
|
.toHex();
|
|
170
|
-
if(!spendTx) {
|
|
171
|
-
throw new SingletonBeaconError('Failed to sign raw transaction.', 'RAW_TX_SIGN_FAILED', { spendTx });
|
|
172
|
-
}
|
|
173
155
|
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
throw new SingletonBeaconError('Failed to send raw transaction.', 'SEND_FAILED', { spentTx });
|
|
178
|
-
}
|
|
156
|
+
// Broadcast spendTx to the Bitcoin network.
|
|
157
|
+
const txid = await bitcoin.network.rest.transaction.send(signedTx);
|
|
158
|
+
console.info(`Broadcasted Singleton Beacon signal with txid ${txid}`);
|
|
179
159
|
|
|
180
|
-
// Return the signed update
|
|
181
|
-
return
|
|
160
|
+
// Return the signed update
|
|
161
|
+
return signedUpdate;
|
|
182
162
|
}
|
|
183
163
|
}
|
|
@@ -1,70 +1,53 @@
|
|
|
1
1
|
import { BitcoinNetworkConnection } from '@did-btcr2/bitcoin';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { KeyBytes } from '@did-btcr2/common';
|
|
3
|
+
import { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
4
4
|
import { SidecarData } from '../types.js';
|
|
5
|
-
import {
|
|
5
|
+
import { Beacon } from './beacon.js';
|
|
6
|
+
import { SMTBeaconError } from './error.js';
|
|
7
|
+
import { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* TODO: Finish implementation
|
|
9
10
|
* Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#smt-beacon | SMTBeacon}.
|
|
10
11
|
* @class SMTBeacon
|
|
11
12
|
* @type {SMTBeacon}
|
|
12
|
-
* @extends {
|
|
13
|
+
* @extends {Beacon}
|
|
13
14
|
*/
|
|
14
|
-
export class SMTBeacon extends
|
|
15
|
+
export class SMTBeacon extends Beacon {
|
|
15
16
|
/**
|
|
16
|
-
* Creates an instance of
|
|
17
|
+
* Creates an instance of SMTBeacon.
|
|
17
18
|
* @param {BeaconService} service The Beacon service.
|
|
18
|
-
* @param {Array<BeaconSignal>} signals The SingletonBeacon sidecar data.
|
|
19
|
-
* @param {SidecarData} sidecar The sidecar data.
|
|
20
19
|
*/
|
|
21
|
-
constructor(
|
|
22
|
-
service:
|
|
23
|
-
signals: Array<BeaconSignal>,
|
|
24
|
-
sidecar: SidecarData,
|
|
25
|
-
bitcoin?: BitcoinNetworkConnection
|
|
26
|
-
) {
|
|
27
|
-
super({ ...service, type: 'SMTBeacon' }, signals, sidecar, bitcoin);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Static, convenience method for establishing a SMTBeacon object.
|
|
32
|
-
* @param {string} service The Beacon service.
|
|
33
|
-
* @param {SidecarData} sidecar The sidecar data.
|
|
34
|
-
* @returns {SingletonBeacon} The Singleton Beacon.
|
|
35
|
-
*/
|
|
36
|
-
static establish(service: BeaconService, signals: Array<BeaconSignal>, sidecar: SidecarData): SMTBeacon {
|
|
37
|
-
return new SMTBeacon(service, signals, sidecar);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* TODO: Figure out if this is necessary or not.
|
|
42
|
-
* @param {HexString} updateHash The hash of the BTCR2 update to generate the signal for.
|
|
43
|
-
* @returns {BeaconSignal} The generated signal.
|
|
44
|
-
* @throws {MethodError} if the signal is invalid.
|
|
45
|
-
*/
|
|
46
|
-
generateSignal(updateHash: HexString): BeaconSignal {
|
|
47
|
-
throw new MethodError('Method not implemented.', `METHOD_NOT_IMPLEMENTED`, {updateHash});
|
|
20
|
+
constructor(service: BeaconService) {
|
|
21
|
+
super({ ...service, type: 'SMTBeacon' });
|
|
48
22
|
}
|
|
49
23
|
|
|
50
24
|
/**
|
|
51
|
-
* Process
|
|
52
|
-
* @
|
|
53
|
-
* @
|
|
25
|
+
* Implements {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-smt-beacon | 7.2.e.1 Process SMT Beacon}.
|
|
26
|
+
* @param {Array<BeaconSignal>} signals The array of Beacon Signals to process.
|
|
27
|
+
* @param {SidecarData} sidecar The sidecar data associated with the SMT Beacon.
|
|
28
|
+
* @returns {Promise<Array<[SignedBTCR2Update, BlockMetadata]>>} The processed signals.
|
|
29
|
+
* @throws {SMTBeaconError} if processing fails.
|
|
54
30
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
processSignals(
|
|
32
|
+
signals: Array<BeaconSignal>,
|
|
33
|
+
sidecar: SidecarData
|
|
34
|
+
): Promise<Array<[SignedBTCR2Update, BlockMetadata]>> {
|
|
35
|
+
throw new SMTBeaconError('Method not implemented.', `METHOD_NOT_IMPLEMENTED`, {signals, sidecar});
|
|
57
36
|
}
|
|
58
37
|
|
|
59
|
-
|
|
60
38
|
/**
|
|
61
|
-
* Broadcast
|
|
62
|
-
* @param {
|
|
63
|
-
* @
|
|
64
|
-
* @
|
|
39
|
+
* Broadcast CAS Beacon signal to the Bitcoin network.
|
|
40
|
+
* @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast.
|
|
41
|
+
* @param {KeyBytes} secretKey The secret key for signing the Bitcoin transaction.
|
|
42
|
+
* @param {BitcoinNetworkConnection} bitcoin The Bitcoin network connection.
|
|
43
|
+
* @return {Promise<SignedBTCR2Update>} The signed update that was broadcasted.
|
|
44
|
+
* @throws {SMTBeaconError} if broadcasting fails.
|
|
65
45
|
*/
|
|
66
|
-
async broadcastSignal(
|
|
67
|
-
|
|
46
|
+
async broadcastSignal(
|
|
47
|
+
signedUpdate: SignedBTCR2Update,
|
|
48
|
+
secretKey: KeyBytes,
|
|
49
|
+
bitcoin: BitcoinNetworkConnection
|
|
50
|
+
): Promise<SignedBTCR2Update> {
|
|
51
|
+
throw new SMTBeaconError('Method not implemented.', `METHOD_NOT_IMPLEMENTED`, {signedUpdate, secretKey, bitcoin});
|
|
68
52
|
}
|
|
69
|
-
|
|
70
53
|
}
|
package/src/core/beacon/utils.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Appendix } from '../../utils/appendix.js';
|
|
|
5
5
|
import { DidDocument } from '../../utils/did-document.js';
|
|
6
6
|
import { Identifier } from '../identifier.js';
|
|
7
7
|
import { BeaconService } from './interfaces.js';
|
|
8
|
+
import { BeaconError } from './error.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Static class of utility functions for the Beacon Service
|
|
@@ -40,9 +41,6 @@ export class BeaconUtils {
|
|
|
40
41
|
// Return false if the serviceEndpoint is not a valid BIP21 bitcoin address.
|
|
41
42
|
if ([obj.serviceEndpoint].flat().some(ep => typeof ep === 'string' && !ep.startsWith('bitcoin:'))) return false;
|
|
42
43
|
|
|
43
|
-
// Return false if the casType exists and is not a string.
|
|
44
|
-
if(obj.casType && typeof obj.casType !== 'string') return false;
|
|
45
|
-
|
|
46
44
|
// Else return true
|
|
47
45
|
return true;
|
|
48
46
|
}
|
|
@@ -73,9 +71,11 @@ export class BeaconUtils {
|
|
|
73
71
|
return addrTypes.map(
|
|
74
72
|
(addrType) => this.createBeaconService(did, addrType, beaconType)
|
|
75
73
|
);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
} catch (error: any) {
|
|
75
|
+
throw new BeaconError(
|
|
76
|
+
'Failed to create beacon services: ' + error.message,
|
|
77
|
+
'BEACON_SERVICE_ERROR', { did, beaconType }
|
|
78
|
+
);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -101,13 +101,14 @@ export class BeaconUtils {
|
|
|
101
101
|
const serviceEndpoint = `bitcoin:${payments[addressType]({ pubkey, network }).address}`;
|
|
102
102
|
// Return the beacon serviceD
|
|
103
103
|
return { id, type: beaconType, serviceEndpoint, };
|
|
104
|
-
} catch (error) {
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
throw new BeaconError(
|
|
106
|
+
'Failed to create beacon service: ' + error.message,
|
|
107
|
+
'BEACON_SERVICE_ERROR', { did, beaconType }
|
|
108
|
+
);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
|
|
111
112
|
/**
|
|
112
113
|
* Generate three default Beacon Service Endpoints for a given `k` (public-key-based) identifier.
|
|
113
114
|
* @param {string} did The DID for which to create the beacon services.
|
|
@@ -147,9 +148,11 @@ export class BeaconUtils {
|
|
|
147
148
|
serviceEndpoint : `bitcoin:${p2tr}`
|
|
148
149
|
},
|
|
149
150
|
];
|
|
150
|
-
} catch (error) {
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
throw new BeaconError(
|
|
153
|
+
'Failed to create beacon services: ' + error.message,
|
|
154
|
+
'BEACON_SERVICE_ERROR', { id, publicKey, network, beaconType }
|
|
155
|
+
);
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
|
package/src/core/interfaces.ts
CHANGED
|
@@ -43,17 +43,9 @@ export interface ResolutionOptions extends ResolutionOptionsCore {
|
|
|
43
43
|
/**
|
|
44
44
|
* Drivers for interacting with external systems, such as the Bitcoin network.
|
|
45
45
|
*/
|
|
46
|
-
drivers
|
|
46
|
+
drivers?: {
|
|
47
47
|
bitcoin?: BitcoinNetworkConnection;
|
|
48
48
|
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Flag to signal a full blockchain search for beacon signals from genesis
|
|
52
|
-
* block to chain tip during resolution, instead of using an indexer.
|
|
53
|
-
* @type {boolean}
|
|
54
|
-
* @default false
|
|
55
|
-
*/
|
|
56
|
-
fullBlockchainTraversal?: boolean;
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
/**
|