@btc-vision/transaction 1.7.18 → 1.7.22
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/LICENSE +190 -21
- package/README.md +1 -1
- package/browser/_version.d.ts +1 -1
- package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
- package/browser/index.js +1 -1
- package/browser/keypair/Address.d.ts +3 -1
- package/browser/opnet.d.ts +6 -1
- package/browser/signer/AddressRotation.d.ts +12 -0
- package/browser/transaction/TransactionFactory.d.ts +14 -0
- package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
- package/browser/transaction/enums/TransactionType.d.ts +3 -1
- package/browser/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
- package/browser/transaction/interfaces/ITransactionParameters.d.ts +2 -0
- package/browser/transaction/offline/OfflineTransactionManager.d.ts +69 -0
- package/browser/transaction/offline/TransactionReconstructor.d.ts +28 -0
- package/browser/transaction/offline/TransactionSerializer.d.ts +50 -0
- package/browser/transaction/offline/TransactionStateCapture.d.ts +52 -0
- package/browser/transaction/offline/index.d.ts +5 -0
- package/browser/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
- package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
- package/browser/transaction/offline/interfaces/index.d.ts +2 -0
- package/browser/transaction/shared/TweakedTransaction.d.ts +12 -1
- package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
- package/build/_version.d.ts +1 -1
- package/build/_version.js +1 -1
- package/build/generators/builders/HashCommitmentGenerator.d.ts +49 -0
- package/build/generators/builders/HashCommitmentGenerator.js +229 -0
- package/build/keypair/Address.d.ts +3 -1
- package/build/keypair/Address.js +87 -54
- package/build/opnet.d.ts +6 -1
- package/build/opnet.js +6 -1
- package/build/signer/AddressRotation.d.ts +12 -0
- package/build/signer/AddressRotation.js +16 -0
- package/build/transaction/TransactionFactory.d.ts +14 -0
- package/build/transaction/TransactionFactory.js +36 -0
- package/build/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
- package/build/transaction/builders/ConsolidatedInteractionTransaction.js +259 -0
- package/build/transaction/builders/TransactionBuilder.js +2 -0
- package/build/transaction/enums/TransactionType.d.ts +3 -1
- package/build/transaction/enums/TransactionType.js +2 -0
- package/build/transaction/interfaces/IConsolidatedTransactionParameters.d.ts +31 -0
- package/build/transaction/interfaces/IConsolidatedTransactionParameters.js +1 -0
- package/build/transaction/interfaces/ITransactionParameters.d.ts +2 -0
- package/build/transaction/offline/OfflineTransactionManager.d.ts +69 -0
- package/build/transaction/offline/OfflineTransactionManager.js +255 -0
- package/build/transaction/offline/TransactionReconstructor.d.ts +28 -0
- package/build/transaction/offline/TransactionReconstructor.js +243 -0
- package/build/transaction/offline/TransactionSerializer.d.ts +50 -0
- package/build/transaction/offline/TransactionSerializer.js +700 -0
- package/build/transaction/offline/TransactionStateCapture.d.ts +52 -0
- package/build/transaction/offline/TransactionStateCapture.js +275 -0
- package/build/transaction/offline/index.d.ts +5 -0
- package/build/transaction/offline/index.js +5 -0
- package/build/transaction/offline/interfaces/ISerializableState.d.ts +62 -0
- package/build/transaction/offline/interfaces/ISerializableState.js +2 -0
- package/build/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
- package/build/transaction/offline/interfaces/ITypeSpecificData.js +19 -0
- package/build/transaction/offline/interfaces/index.d.ts +2 -0
- package/build/transaction/offline/interfaces/index.js +2 -0
- package/build/transaction/shared/TweakedTransaction.d.ts +12 -1
- package/build/transaction/shared/TweakedTransaction.js +75 -8
- package/build/utxo/interfaces/IUTXO.d.ts +2 -0
- package/documentation/README.md +5 -0
- package/documentation/offline-transaction-signing.md +650 -0
- package/documentation/transaction-building.md +603 -0
- package/package.json +2 -2
- package/src/_version.ts +1 -1
- package/src/generators/builders/HashCommitmentGenerator.ts +495 -0
- package/src/keypair/Address.ts +123 -70
- package/src/opnet.ts +8 -1
- package/src/signer/AddressRotation.ts +72 -0
- package/src/transaction/TransactionFactory.ts +94 -1
- package/src/transaction/builders/CancelTransaction.ts +4 -2
- package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +568 -0
- package/src/transaction/builders/CustomScriptTransaction.ts +4 -2
- package/src/transaction/builders/MultiSignTransaction.ts +4 -2
- package/src/transaction/builders/TransactionBuilder.ts +8 -2
- package/src/transaction/enums/TransactionType.ts +2 -0
- package/src/transaction/interfaces/IConsolidatedTransactionParameters.ts +78 -0
- package/src/transaction/interfaces/ITransactionParameters.ts +8 -0
- package/src/transaction/offline/OfflineTransactionManager.ts +630 -0
- package/src/transaction/offline/TransactionReconstructor.ts +402 -0
- package/src/transaction/offline/TransactionSerializer.ts +920 -0
- package/src/transaction/offline/TransactionStateCapture.ts +469 -0
- package/src/transaction/offline/index.ts +8 -0
- package/src/transaction/offline/interfaces/ISerializableState.ts +141 -0
- package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -0
- package/src/transaction/offline/interfaces/index.ts +2 -0
- package/src/transaction/shared/TweakedTransaction.ts +156 -9
- package/src/utxo/interfaces/IUTXO.ts +8 -0
- package/test/address-rotation.test.ts +553 -0
- package/test/offline-transaction.test.ts +2065 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
# Offline Transaction Signing
|
|
2
|
+
|
|
3
|
+
This module enables secure offline transaction signing workflows. Transactions can be built in an online environment, exported to an air-gapped offline server for signing, then broadcast from the online environment.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The offline signing workflow separates transaction construction from signing:
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
subgraph Online["Phase 1 (Online)"]
|
|
12
|
+
A[Build Transaction] --> B[Export State]
|
|
13
|
+
B --> C["No Private Keys"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph Offline["Phase 2 (Offline)"]
|
|
17
|
+
D[Import State] --> E[Provide Signers]
|
|
18
|
+
E --> F[Sign Transaction]
|
|
19
|
+
F --> G[Export Signed TX]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
C -->|base64| D
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import {
|
|
31
|
+
OfflineTransactionManager,
|
|
32
|
+
IFundingTransactionParameters
|
|
33
|
+
} from '@btc-vision/transaction';
|
|
34
|
+
import { networks } from '@btc-vision/bitcoin';
|
|
35
|
+
|
|
36
|
+
// Phase 1: Online - Export transaction state
|
|
37
|
+
const params: IFundingTransactionParameters = {
|
|
38
|
+
signer: onlineSigner,
|
|
39
|
+
mldsaSigner: null,
|
|
40
|
+
network: networks.bitcoin,
|
|
41
|
+
utxos: [...],
|
|
42
|
+
from: 'bc1p...',
|
|
43
|
+
to: 'bc1p...',
|
|
44
|
+
feeRate: 10,
|
|
45
|
+
priorityFee: 1000n,
|
|
46
|
+
gasSatFee: 500n,
|
|
47
|
+
amount: 50000n,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const exportedState = OfflineTransactionManager.exportFunding(params);
|
|
51
|
+
// Send exportedState (base64 string) to offline environment
|
|
52
|
+
|
|
53
|
+
// Phase 2: Offline - Sign and export
|
|
54
|
+
const signedTxHex = await OfflineTransactionManager.importSignAndExport(
|
|
55
|
+
exportedState,
|
|
56
|
+
{ signer: offlineSigner }
|
|
57
|
+
);
|
|
58
|
+
// Send signedTxHex back to online environment for broadcast
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Fee Bumping (RBF)
|
|
62
|
+
|
|
63
|
+
```mermaid
|
|
64
|
+
flowchart LR
|
|
65
|
+
A["Original State<br/>(10 sat/vB)"] --> B[rebuildWithNewFees]
|
|
66
|
+
B --> C["New State<br/>(50 sat/vB)"]
|
|
67
|
+
C --> D[Sign & Export]
|
|
68
|
+
D --> E[Signed TX Hex]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Rebuild with higher fee rate (returns new state, not signed)
|
|
73
|
+
const bumpedState = OfflineTransactionManager.rebuildWithNewFees(
|
|
74
|
+
originalState,
|
|
75
|
+
50, // New fee rate in sat/vB
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Or rebuild and sign in one step
|
|
79
|
+
const signedBumpedTx = await OfflineTransactionManager.rebuildSignAndExport(
|
|
80
|
+
originalState,
|
|
81
|
+
50,
|
|
82
|
+
{ signer: offlineSigner }
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### OfflineTransactionManager
|
|
89
|
+
|
|
90
|
+
The main entry point for offline transaction workflows.
|
|
91
|
+
|
|
92
|
+
#### Export Methods
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Export a FundingTransaction
|
|
96
|
+
static exportFunding(
|
|
97
|
+
params: IFundingTransactionParameters,
|
|
98
|
+
precomputed?: Partial<PrecomputedData>
|
|
99
|
+
): string;
|
|
100
|
+
|
|
101
|
+
// Export a DeploymentTransaction
|
|
102
|
+
static exportDeployment(
|
|
103
|
+
params: IDeploymentParameters,
|
|
104
|
+
precomputed: { compiledTargetScript: string; randomBytes: string; }
|
|
105
|
+
): string;
|
|
106
|
+
|
|
107
|
+
// Export an InteractionTransaction
|
|
108
|
+
static exportInteraction(
|
|
109
|
+
params: IInteractionParameters,
|
|
110
|
+
precomputed: { compiledTargetScript: string; randomBytes: string; }
|
|
111
|
+
): string;
|
|
112
|
+
|
|
113
|
+
// Export a MultiSignTransaction
|
|
114
|
+
static exportMultiSig(
|
|
115
|
+
params: MultiSigParams,
|
|
116
|
+
precomputed?: Partial<PrecomputedData>
|
|
117
|
+
): string;
|
|
118
|
+
|
|
119
|
+
// Export a CustomScriptTransaction
|
|
120
|
+
static exportCustomScript(
|
|
121
|
+
params: CustomScriptParams,
|
|
122
|
+
precomputed?: Partial<PrecomputedData>
|
|
123
|
+
): string;
|
|
124
|
+
|
|
125
|
+
// Export a CancelTransaction
|
|
126
|
+
static exportCancel(
|
|
127
|
+
params: CancelParams,
|
|
128
|
+
precomputed?: Partial<PrecomputedData>
|
|
129
|
+
): string;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Import and Sign Methods
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Import state and prepare for signing
|
|
136
|
+
static importForSigning(
|
|
137
|
+
serializedState: string,
|
|
138
|
+
options: ReconstructionOptions
|
|
139
|
+
): TransactionBuilder<TransactionType>;
|
|
140
|
+
|
|
141
|
+
// Sign a reconstructed builder
|
|
142
|
+
static async signAndExport(
|
|
143
|
+
builder: TransactionBuilder<TransactionType>
|
|
144
|
+
): Promise<string>;
|
|
145
|
+
|
|
146
|
+
// Convenience: Import, sign, and export in one call
|
|
147
|
+
static async importSignAndExport(
|
|
148
|
+
serializedState: string,
|
|
149
|
+
options: ReconstructionOptions
|
|
150
|
+
): Promise<string>;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Fee Bumping Methods
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Rebuild state with new fee rate (returns new state, not signed)
|
|
157
|
+
static rebuildWithNewFees(
|
|
158
|
+
serializedState: string,
|
|
159
|
+
newFeeRate: number
|
|
160
|
+
): string;
|
|
161
|
+
|
|
162
|
+
// Rebuild, sign, and export with new fee rate
|
|
163
|
+
static async rebuildSignAndExport(
|
|
164
|
+
serializedState: string,
|
|
165
|
+
newFeeRate: number,
|
|
166
|
+
options: ReconstructionOptions
|
|
167
|
+
): Promise<string>;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Utility Methods
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Inspect serialized state without signing
|
|
174
|
+
static inspect(serializedState: string): ISerializableTransactionState;
|
|
175
|
+
|
|
176
|
+
// Validate state integrity (checksum verification)
|
|
177
|
+
static validate(serializedState: string): boolean;
|
|
178
|
+
|
|
179
|
+
// Get transaction type from state
|
|
180
|
+
static getType(serializedState: string): TransactionType;
|
|
181
|
+
|
|
182
|
+
// Parse/serialize state objects
|
|
183
|
+
static fromBase64(base64State: string): ISerializableTransactionState;
|
|
184
|
+
static toBase64(state: ISerializableTransactionState): string;
|
|
185
|
+
|
|
186
|
+
// Format conversion (base64 <-> hex)
|
|
187
|
+
static toHex(serializedState: string): string;
|
|
188
|
+
static fromHex(hexState: string): string;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### ReconstructionOptions
|
|
192
|
+
|
|
193
|
+
Options for reconstructing a transaction from serialized state:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
interface ReconstructionOptions {
|
|
197
|
+
// Primary signer (required)
|
|
198
|
+
signer: Signer | ECPairInterface;
|
|
199
|
+
|
|
200
|
+
// Optional: Override fee rate for fee bumping
|
|
201
|
+
newFeeRate?: number;
|
|
202
|
+
|
|
203
|
+
// Optional: Override priority fee
|
|
204
|
+
newPriorityFee?: bigint;
|
|
205
|
+
|
|
206
|
+
// Optional: Override gas sat fee
|
|
207
|
+
newGasSatFee?: bigint;
|
|
208
|
+
|
|
209
|
+
// Signer map for address rotation mode
|
|
210
|
+
signerMap?: SignerMap;
|
|
211
|
+
|
|
212
|
+
// MLDSA signer for quantum-resistant features
|
|
213
|
+
mldsaSigner?: QuantumBIP32Interface | null;
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Address Rotation Mode
|
|
218
|
+
|
|
219
|
+
For wallets with multiple addresses (different signers per UTXO):
|
|
220
|
+
|
|
221
|
+
```mermaid
|
|
222
|
+
flowchart TB
|
|
223
|
+
subgraph UTXOs["Input UTXOs"]
|
|
224
|
+
U1["UTXO 1<br/>address1"]
|
|
225
|
+
U2["UTXO 2<br/>address2"]
|
|
226
|
+
U3["UTXO 3<br/>address1"]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
subgraph SignerMap["Signer Map"]
|
|
230
|
+
S1["address1 → signer1"]
|
|
231
|
+
S2["address2 → signer2"]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
U1 --> S1
|
|
235
|
+
U2 --> S2
|
|
236
|
+
U3 --> S1
|
|
237
|
+
|
|
238
|
+
S1 --> TX[Signed Transaction]
|
|
239
|
+
S2 --> TX
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import {
|
|
244
|
+
OfflineTransactionManager,
|
|
245
|
+
createSignerMap,
|
|
246
|
+
createAddressRotation
|
|
247
|
+
} from '@btc-vision/transaction';
|
|
248
|
+
|
|
249
|
+
// Phase 1: Export with address rotation enabled
|
|
250
|
+
// createSignerMap takes an array of [address, signer] tuples
|
|
251
|
+
const signerMap = createSignerMap([
|
|
252
|
+
['bc1p...address1', signer1],
|
|
253
|
+
['bc1p...address2', signer2],
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
const params: IFundingTransactionParameters = {
|
|
257
|
+
signer: signer1,
|
|
258
|
+
mldsaSigner: null,
|
|
259
|
+
network,
|
|
260
|
+
utxos: [
|
|
261
|
+
{ /* UTXO from address1 */ },
|
|
262
|
+
{ /* UTXO from address2 */ },
|
|
263
|
+
],
|
|
264
|
+
from: 'bc1p...address1',
|
|
265
|
+
to: 'bc1p...recipient',
|
|
266
|
+
feeRate: 10,
|
|
267
|
+
priorityFee: 1000n,
|
|
268
|
+
gasSatFee: 500n,
|
|
269
|
+
amount: 50000n,
|
|
270
|
+
addressRotation: createAddressRotation(signerMap),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const exported = OfflineTransactionManager.exportFunding(params);
|
|
274
|
+
|
|
275
|
+
// Phase 2: Provide signers for each address
|
|
276
|
+
const offlineSignerMap = createSignerMap([
|
|
277
|
+
{ address: 'bc1p...address1', signer: offlineSigner1 },
|
|
278
|
+
{ address: 'bc1p...address2', signer: offlineSigner2 },
|
|
279
|
+
], network);
|
|
280
|
+
|
|
281
|
+
const signedTx = await OfflineTransactionManager.importSignAndExport(
|
|
282
|
+
exported,
|
|
283
|
+
{
|
|
284
|
+
signer: offlineSigner1,
|
|
285
|
+
signerMap: offlineSignerMap,
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Serialization Format
|
|
291
|
+
|
|
292
|
+
The serialized state uses a compact binary format with the following structure:
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
296
|
+
│ HEADER (16 bytes) │
|
|
297
|
+
├────────┬────────┬───────────┬────────┬───────────┬─────────────┤
|
|
298
|
+
│ 0x42 │ Format │ Consensus │ Type │ Chain ID │ Timestamp │
|
|
299
|
+
│ 1B │ 1B │ 1B │ 1B │ 4B │ 8B │
|
|
300
|
+
├────────┴────────┴───────────┴────────┴───────────┴─────────────┤
|
|
301
|
+
│ BODY (variable) │
|
|
302
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
303
|
+
│ Base Params (from, to, feeRate, priorityFee, gasSatFee) │
|
|
304
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
305
|
+
│ UTXOs Array │
|
|
306
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
307
|
+
│ Optional Inputs │
|
|
308
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
309
|
+
│ Optional Outputs │
|
|
310
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
311
|
+
│ Signer Mappings (address → input indices) │
|
|
312
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
313
|
+
│ Type-Specific Data │
|
|
314
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
315
|
+
│ Precomputed Data │
|
|
316
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
317
|
+
│ CHECKSUM (32 bytes) │
|
|
318
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
319
|
+
│ Double SHA256 Hash │
|
|
320
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Header Fields
|
|
324
|
+
|
|
325
|
+
| Offset | Size | Field |
|
|
326
|
+
|--------|------|-------|
|
|
327
|
+
| 0 | 1 | Magic byte (0x42) |
|
|
328
|
+
| 1 | 1 | Format version |
|
|
329
|
+
| 2 | 1 | Consensus version |
|
|
330
|
+
| 3 | 1 | Transaction type |
|
|
331
|
+
| 4-7 | 4 | Chain ID |
|
|
332
|
+
| 8-15 | 8 | Timestamp |
|
|
333
|
+
|
|
334
|
+
## Supported Transaction Types
|
|
335
|
+
|
|
336
|
+
| Type | Export Method | Type-Specific Data |
|
|
337
|
+
|------|---------------|-------------------|
|
|
338
|
+
| Funding | `exportFunding()` | amount, splitInputsInto |
|
|
339
|
+
| Deployment | `exportDeployment()` | bytecode, calldata, challenge |
|
|
340
|
+
| Interaction | `exportInteraction()` | calldata, contract, challenge, loadedStorage |
|
|
341
|
+
| MultiSig | `exportMultiSig()` | pubkeys, minimumSignatures, receiver, existingPsbtBase64 |
|
|
342
|
+
| CustomScript | `exportCustomScript()` | scriptElements, witnesses, annex |
|
|
343
|
+
| Cancel | `exportCancel()` | compiledTargetScript |
|
|
344
|
+
|
|
345
|
+
## MultiSig Transactions
|
|
346
|
+
|
|
347
|
+
MultiSig transactions require collecting signatures from multiple parties before broadcasting. The offline signing module provides specialized methods for this workflow.
|
|
348
|
+
|
|
349
|
+
```mermaid
|
|
350
|
+
flowchart LR
|
|
351
|
+
subgraph Create["1. Create"]
|
|
352
|
+
A[Export MultiSig State]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
subgraph Sign["2. Collect Signatures"]
|
|
356
|
+
B[Signer 1] --> C[Updated State]
|
|
357
|
+
C --> D[Signer 2]
|
|
358
|
+
D --> E[Updated State]
|
|
359
|
+
E --> F[Signer N]
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
subgraph Finalize["3. Finalize"]
|
|
363
|
+
G[Check Complete] --> H[Extract TX Hex]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
A --> B
|
|
367
|
+
F --> G
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### MultiSig API Methods
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// Add a signature from one signer
|
|
374
|
+
static async multiSigAddSignature(
|
|
375
|
+
serializedState: string,
|
|
376
|
+
signer: Signer | ECPairInterface
|
|
377
|
+
): Promise<{
|
|
378
|
+
state: string; // Updated state with new signature
|
|
379
|
+
signed: boolean; // Whether signing succeeded
|
|
380
|
+
final: boolean; // Whether all signatures collected
|
|
381
|
+
psbtBase64: string; // Current PSBT state
|
|
382
|
+
}>;
|
|
383
|
+
|
|
384
|
+
// Check if a public key has already signed
|
|
385
|
+
static multiSigHasSigned(
|
|
386
|
+
serializedState: string,
|
|
387
|
+
signerPubKey: Buffer | string
|
|
388
|
+
): boolean;
|
|
389
|
+
|
|
390
|
+
// Get current signature status
|
|
391
|
+
static multiSigGetSignatureStatus(serializedState: string): {
|
|
392
|
+
required: number; // Minimum signatures needed
|
|
393
|
+
collected: number; // Current signature count
|
|
394
|
+
isComplete: boolean;
|
|
395
|
+
signers: string[]; // Public keys that have signed
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Finalize and extract transaction hex
|
|
399
|
+
static multiSigFinalize(serializedState: string): string;
|
|
400
|
+
|
|
401
|
+
// Get PSBT for external signing tools
|
|
402
|
+
static multiSigGetPsbt(serializedState: string): string | null;
|
|
403
|
+
|
|
404
|
+
// Update PSBT after external signing
|
|
405
|
+
static multiSigUpdatePsbt(
|
|
406
|
+
serializedState: string,
|
|
407
|
+
psbtBase64: string
|
|
408
|
+
): string;
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### MultiSig Example
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import {
|
|
415
|
+
OfflineTransactionManager,
|
|
416
|
+
EcKeyPair,
|
|
417
|
+
} from '@btc-vision/transaction';
|
|
418
|
+
import { networks } from '@btc-vision/bitcoin';
|
|
419
|
+
|
|
420
|
+
const network = networks.regtest;
|
|
421
|
+
|
|
422
|
+
// Create 3 signers for a 2-of-3 multisig
|
|
423
|
+
const signer1 = EcKeyPair.generateRandomKeyPair(network);
|
|
424
|
+
const signer2 = EcKeyPair.generateRandomKeyPair(network);
|
|
425
|
+
const signer3 = EcKeyPair.generateRandomKeyPair(network);
|
|
426
|
+
|
|
427
|
+
const pubkeys = [
|
|
428
|
+
signer1.publicKey,
|
|
429
|
+
signer2.publicKey,
|
|
430
|
+
signer3.publicKey,
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
// Export initial multisig state
|
|
434
|
+
const params = {
|
|
435
|
+
network,
|
|
436
|
+
mldsaSigner: null,
|
|
437
|
+
utxos: [/* vault UTXOs */],
|
|
438
|
+
feeRate: 10,
|
|
439
|
+
pubkeys,
|
|
440
|
+
minimumSignatures: 2,
|
|
441
|
+
receiver: 'bcrt1q...recipient',
|
|
442
|
+
requestedAmount: 50000n,
|
|
443
|
+
refundVault: 'bcrt1q...vault',
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
let state = OfflineTransactionManager.exportMultiSig(params);
|
|
447
|
+
|
|
448
|
+
// Signer 1 adds their signature
|
|
449
|
+
const result1 = await OfflineTransactionManager.multiSigAddSignature(state, signer1);
|
|
450
|
+
console.log('Signer 1 signed:', result1.signed);
|
|
451
|
+
console.log('Complete:', result1.final);
|
|
452
|
+
state = result1.state;
|
|
453
|
+
|
|
454
|
+
// Check status
|
|
455
|
+
const status = OfflineTransactionManager.multiSigGetSignatureStatus(state);
|
|
456
|
+
console.log(`Signatures: ${status.collected}/${status.required}`);
|
|
457
|
+
|
|
458
|
+
// Signer 2 adds their signature
|
|
459
|
+
const result2 = await OfflineTransactionManager.multiSigAddSignature(state, signer2);
|
|
460
|
+
console.log('Signer 2 signed:', result2.signed);
|
|
461
|
+
console.log('Complete:', result2.final); // true - we have 2 of 3
|
|
462
|
+
state = result2.state;
|
|
463
|
+
|
|
464
|
+
// Finalize and get transaction hex
|
|
465
|
+
if (OfflineTransactionManager.multiSigGetSignatureStatus(state).isComplete) {
|
|
466
|
+
const txHex = OfflineTransactionManager.multiSigFinalize(state);
|
|
467
|
+
console.log('Ready to broadcast:', txHex);
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### External Signing Tools
|
|
472
|
+
|
|
473
|
+
For hardware wallets or external signing tools, you can extract and update the PSBT directly:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
// Get PSBT for external tool
|
|
477
|
+
const psbtBase64 = OfflineTransactionManager.multiSigGetPsbt(state);
|
|
478
|
+
|
|
479
|
+
// Send psbtBase64 to hardware wallet / external tool
|
|
480
|
+
// ... external signing happens ...
|
|
481
|
+
|
|
482
|
+
// Update state with signed PSBT
|
|
483
|
+
const signedPsbtBase64 = '...'; // From external tool
|
|
484
|
+
state = OfflineTransactionManager.multiSigUpdatePsbt(state, signedPsbtBase64);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Precomputed Data
|
|
488
|
+
|
|
489
|
+
Some transaction types require precomputed data that must be preserved for deterministic rebuilds:
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
interface PrecomputedData {
|
|
493
|
+
// Script signer seed - changing this changes signatures
|
|
494
|
+
randomBytes?: string;
|
|
495
|
+
|
|
496
|
+
// Complex scripts that are expensive to recompute
|
|
497
|
+
compiledTargetScript?: string;
|
|
498
|
+
|
|
499
|
+
// For deployment transactions
|
|
500
|
+
contractSeed?: string;
|
|
501
|
+
contractAddress?: string;
|
|
502
|
+
|
|
503
|
+
// Estimated fees
|
|
504
|
+
estimatedFees?: string;
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
For Deployment and Interaction transactions, `randomBytes` and `compiledTargetScript` are required:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const exported = OfflineTransactionManager.exportDeployment(params, {
|
|
512
|
+
randomBytes: builder.getRandomBytes().toString('hex'),
|
|
513
|
+
compiledTargetScript: builder.getCompiledScript().toString('hex'),
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## State Manipulation
|
|
518
|
+
|
|
519
|
+
You can parse, modify, and re-serialize state objects:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Parse state from base64
|
|
523
|
+
const state = OfflineTransactionManager.fromBase64(exportedState);
|
|
524
|
+
|
|
525
|
+
// Inspect or modify the state
|
|
526
|
+
console.log('Original fee rate:', state.baseParams.feeRate);
|
|
527
|
+
console.log('Transaction type:', state.header.transactionType);
|
|
528
|
+
|
|
529
|
+
// Modify state (e.g., update fee rate manually)
|
|
530
|
+
state.baseParams.feeRate = 30;
|
|
531
|
+
|
|
532
|
+
// Re-serialize to base64
|
|
533
|
+
const modifiedState = OfflineTransactionManager.toBase64(state);
|
|
534
|
+
|
|
535
|
+
// Sign the modified state
|
|
536
|
+
const signedTx = await OfflineTransactionManager.importSignAndExport(
|
|
537
|
+
modifiedState,
|
|
538
|
+
{ signer: offlineSigner }
|
|
539
|
+
);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Security Considerations
|
|
543
|
+
|
|
544
|
+
1. **Private keys are never serialized** - Only public keys and addresses are stored
|
|
545
|
+
2. **Signers must be provided at reconstruction time** by the offline device
|
|
546
|
+
3. **Checksum verification** prevents tampering with serialized state
|
|
547
|
+
4. **Format versioning** allows future security improvements
|
|
548
|
+
|
|
549
|
+
## Error Handling
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
try {
|
|
553
|
+
const state = OfflineTransactionManager.inspect(serializedState);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (error.message.includes('Invalid magic byte')) {
|
|
556
|
+
// Not a valid offline transaction state
|
|
557
|
+
} else if (error.message.includes('Invalid checksum')) {
|
|
558
|
+
// Data corrupted or tampered with
|
|
559
|
+
} else if (error.message.includes('Unsupported format version')) {
|
|
560
|
+
// State created with newer version
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Validate before processing
|
|
565
|
+
if (!OfflineTransactionManager.validate(serializedState)) {
|
|
566
|
+
throw new Error('Invalid transaction state');
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Complete Example
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
import {
|
|
574
|
+
OfflineTransactionManager,
|
|
575
|
+
IFundingTransactionParameters,
|
|
576
|
+
TransactionType,
|
|
577
|
+
EcKeyPair,
|
|
578
|
+
} from '@btc-vision/transaction';
|
|
579
|
+
import { networks } from '@btc-vision/bitcoin';
|
|
580
|
+
|
|
581
|
+
// === ONLINE ENVIRONMENT ===
|
|
582
|
+
|
|
583
|
+
const network = networks.bitcoin;
|
|
584
|
+
const signer = EcKeyPair.generateRandomKeyPair(network);
|
|
585
|
+
const address = EcKeyPair.getTaprootAddress(signer, network);
|
|
586
|
+
|
|
587
|
+
// Create transaction parameters
|
|
588
|
+
const params: IFundingTransactionParameters = {
|
|
589
|
+
signer,
|
|
590
|
+
mldsaSigner: null,
|
|
591
|
+
network,
|
|
592
|
+
utxos: [{
|
|
593
|
+
transactionId: 'abc123...'.padEnd(64, '0'),
|
|
594
|
+
outputIndex: 0,
|
|
595
|
+
value: 100000n,
|
|
596
|
+
scriptPubKey: {
|
|
597
|
+
hex: '5120...',
|
|
598
|
+
address,
|
|
599
|
+
},
|
|
600
|
+
}],
|
|
601
|
+
from: address,
|
|
602
|
+
to: 'bc1p...recipient',
|
|
603
|
+
feeRate: 10,
|
|
604
|
+
priorityFee: 1000n,
|
|
605
|
+
gasSatFee: 500n,
|
|
606
|
+
amount: 50000n,
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// Export state (send this to offline environment)
|
|
610
|
+
const exportedState = OfflineTransactionManager.exportFunding(params);
|
|
611
|
+
console.log('Exported state length:', exportedState.length);
|
|
612
|
+
|
|
613
|
+
// Validate the export
|
|
614
|
+
console.log('Valid:', OfflineTransactionManager.validate(exportedState));
|
|
615
|
+
|
|
616
|
+
// Inspect metadata
|
|
617
|
+
const metadata = OfflineTransactionManager.inspect(exportedState);
|
|
618
|
+
console.log('Transaction type:', TransactionType[metadata.header.transactionType]);
|
|
619
|
+
console.log('Fee rate:', metadata.baseParams.feeRate);
|
|
620
|
+
|
|
621
|
+
// === OFFLINE ENVIRONMENT ===
|
|
622
|
+
|
|
623
|
+
// Import and sign (using the same signer for this example)
|
|
624
|
+
const signedTxHex = await OfflineTransactionManager.importSignAndExport(
|
|
625
|
+
exportedState,
|
|
626
|
+
{ signer }
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
console.log('Signed transaction:', signedTxHex);
|
|
630
|
+
|
|
631
|
+
// === FEE BUMPING (if needed) ===
|
|
632
|
+
|
|
633
|
+
// Create new state with higher fee
|
|
634
|
+
const bumpedState = OfflineTransactionManager.rebuildWithNewFees(
|
|
635
|
+
exportedState,
|
|
636
|
+
50, // 5x higher fee rate
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
// Verify the new fee rate
|
|
640
|
+
const bumpedMetadata = OfflineTransactionManager.inspect(bumpedState);
|
|
641
|
+
console.log('New fee rate:', bumpedMetadata.baseParams.feeRate); // 50
|
|
642
|
+
|
|
643
|
+
// Sign the bumped transaction
|
|
644
|
+
const signedBumpedTx = await OfflineTransactionManager.importSignAndExport(
|
|
645
|
+
bumpedState,
|
|
646
|
+
{ signer }
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
console.log('Signed bumped transaction:', signedBumpedTx);
|
|
650
|
+
```
|