@btc-vision/transaction 1.7.19 → 1.7.23
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/.babelrc +10 -1
- package/LICENSE +190 -21
- package/README.md +1 -1
- package/browser/_version.d.ts +1 -1
- package/browser/bip39.js +204 -0
- package/browser/bitcoin-utils.js +3172 -0
- package/browser/btc-vision-bip32.js +805 -0
- package/browser/btc-vision-bitcoin.js +4179 -0
- package/browser/btc-vision-logger.js +273 -0
- package/browser/btc-vision-post-quantum.js +542 -0
- package/browser/chain/ChainData.d.ts +1 -1
- package/browser/crypto/crypto.d.ts +1 -1
- package/browser/generators/AddressGenerator.d.ts +1 -1
- package/browser/generators/Generator.d.ts +1 -1
- package/browser/generators/MLDSAData.d.ts +1 -1
- package/browser/generators/builders/CalldataGenerator.d.ts +1 -1
- package/browser/generators/builders/CustomGenerator.d.ts +1 -1
- package/browser/generators/builders/DeploymentGenerator.d.ts +1 -1
- package/browser/generators/builders/HashCommitmentGenerator.d.ts +49 -0
- package/browser/generators/builders/LegacyCalldataGenerator.d.ts +1 -1
- package/browser/generators/builders/P2WDAGenerator.d.ts +1 -1
- package/browser/index.js +10775 -2
- package/browser/keypair/Address.d.ts +5 -3
- package/browser/keypair/AddressVerificator.d.ts +2 -2
- package/browser/keypair/EcKeyPair.d.ts +2 -2
- package/browser/keypair/MessageSigner.d.ts +2 -2
- package/browser/keypair/Wallet.d.ts +2 -2
- package/browser/metadata/ContractBaseMetadata.d.ts +1 -1
- package/browser/mnemonic/Mnemonic.d.ts +2 -2
- package/browser/noble-curves.js +3316 -0
- package/browser/noble-hashes.js +1608 -0
- package/browser/opnet.d.ts +15 -2
- package/browser/p2wda/P2WDADetector.d.ts +2 -2
- package/browser/polyfills.js +4590 -0
- package/browser/scure-base.js +410 -0
- package/browser/signer/AddressRotation.d.ts +12 -0
- package/browser/signer/SignerUtils.d.ts +1 -1
- package/browser/signer/TweakedSigner.d.ts +1 -1
- package/browser/transaction/TransactionFactory.d.ts +15 -1
- package/browser/transaction/browser/BrowserSignerBase.d.ts +1 -1
- package/browser/transaction/browser/Web3Provider.d.ts +1 -1
- package/browser/transaction/browser/extensions/UnisatSigner.d.ts +1 -1
- package/browser/transaction/browser/extensions/XverseSigner.d.ts +1 -1
- package/browser/transaction/builders/CancelTransaction.d.ts +1 -1
- package/browser/transaction/builders/ConsolidatedInteractionTransaction.d.ts +44 -0
- package/browser/transaction/builders/CustomScriptTransaction.d.ts +1 -1
- package/browser/transaction/builders/DeploymentTransaction.d.ts +1 -1
- package/browser/transaction/builders/FundingTransaction.d.ts +1 -1
- package/browser/transaction/builders/InteractionTransaction.d.ts +1 -1
- package/browser/transaction/builders/InteractionTransactionP2WDA.d.ts +2 -2
- package/browser/transaction/builders/MultiSignTransaction.d.ts +1 -1
- package/browser/transaction/builders/SharedInteractionTransaction.d.ts +1 -1
- package/browser/transaction/builders/TransactionBuilder.d.ts +1 -1
- 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 +3 -1
- package/browser/transaction/interfaces/Tap.d.ts +1 -1
- package/browser/transaction/mineable/TimelockGenerator.d.ts +1 -1
- 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/interfaces/ISerializableState.d.ts +62 -0
- package/browser/transaction/offline/interfaces/ITypeSpecificData.d.ts +62 -0
- package/browser/transaction/processor/PsbtTransaction.d.ts +1 -1
- package/browser/transaction/shared/P2TR_MS.d.ts +1 -1
- package/browser/transaction/shared/TweakedTransaction.d.ts +15 -4
- package/browser/utxo/OPNetLimitedProvider.d.ts +1 -1
- package/browser/utxo/interfaces/IUTXO.d.ts +2 -0
- package/browser/valibot.js +4948 -0
- package/browser/vendors.js +12913 -0
- package/browser/verification/TapscriptVerificator.d.ts +1 -1
- 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 +14 -1
- package/build/opnet.js +11 -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/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/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 +62 -4
- 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 +16 -1
- package/src/signer/AddressRotation.ts +72 -0
- package/src/transaction/TransactionFactory.ts +87 -0
- package/src/transaction/builders/CancelTransaction.ts +4 -2
- package/src/transaction/builders/ConsolidatedInteractionTransaction.ts +561 -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/interfaces/ISerializableState.ts +141 -0
- package/src/transaction/offline/interfaces/ITypeSpecificData.ts +172 -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
- package/vite.config.browser.ts +92 -0
- package/webpack.config.js +143 -2
- package/browser/crypto/crypto-browser.d.ts +0 -11
- package/browser/index.js.LICENSE.txt +0 -29
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Psbt, PsbtInput, toXOnly, Transaction } from '@btc-vision/bitcoin';
|
|
3
|
+
import { ECPairInterface } from 'ecpair';
|
|
4
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
5
|
+
import { MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
|
|
6
|
+
import { HashCommitmentGenerator } from '../../generators/builders/HashCommitmentGenerator.js';
|
|
7
|
+
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
8
|
+
import {
|
|
9
|
+
IConsolidatedInteractionParameters,
|
|
10
|
+
IConsolidatedInteractionResult,
|
|
11
|
+
IHashCommittedP2WSH,
|
|
12
|
+
IRevealTransactionResult,
|
|
13
|
+
ISetupTransactionResult,
|
|
14
|
+
} from '../interfaces/IConsolidatedTransactionParameters.js';
|
|
15
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
16
|
+
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
17
|
+
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
18
|
+
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
19
|
+
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
20
|
+
import { Compressor } from '../../bytecode/Compressor.js';
|
|
21
|
+
import { Feature, FeaturePriority, Features } from '../../generators/Features.js';
|
|
22
|
+
import { AddressGenerator } from '../../generators/AddressGenerator.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Consolidated Interaction Transaction
|
|
26
|
+
*
|
|
27
|
+
* Drop-in replacement for InteractionTransaction that bypasses BIP110/Bitcoin Knots censorship.
|
|
28
|
+
*
|
|
29
|
+
* Uses the same parameters and sends the same data on-chain as InteractionTransaction,
|
|
30
|
+
* but embeds data in hash-committed P2WSH witnesses instead of Tapscript.
|
|
31
|
+
*
|
|
32
|
+
* Data is split into 80-byte chunks (P2WSH stack item limit), with up to 14 chunks
|
|
33
|
+
* batched per P2WSH output (~1,120 bytes per output). Each output's witness script
|
|
34
|
+
* commits to all its chunks via HASH160. When spent, all data chunks are revealed
|
|
35
|
+
* in the witness and verified at consensus level.
|
|
36
|
+
*
|
|
37
|
+
* Policy limits respected:
|
|
38
|
+
* - MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80 bytes per chunk
|
|
39
|
+
* - g_script_size_policy_limit = 1650 bytes total witness size (serialized)
|
|
40
|
+
* - MAX_STANDARD_P2WSH_STACK_ITEMS = 100 items per witness
|
|
41
|
+
*
|
|
42
|
+
* Data integrity is consensus-enforced: if any data is stripped or modified,
|
|
43
|
+
* HASH160(data) != committed_hash and the transaction is INVALID.
|
|
44
|
+
*
|
|
45
|
+
* Capacity: ~1.1KB per P2WSH output, ~220 outputs per reveal tx, ~242KB max.
|
|
46
|
+
*
|
|
47
|
+
* Usage:
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Same parameters as InteractionTransaction
|
|
50
|
+
* const tx = new ConsolidatedInteractionTransaction({
|
|
51
|
+
* calldata: myCalldata,
|
|
52
|
+
* to: contractAddress,
|
|
53
|
+
* contract: contractSecret,
|
|
54
|
+
* challenge: myChallenge,
|
|
55
|
+
* utxos: myUtxos,
|
|
56
|
+
* signer: mySigner,
|
|
57
|
+
* network: networks.bitcoin,
|
|
58
|
+
* feeRate: 10,
|
|
59
|
+
* priorityFee: 0n,
|
|
60
|
+
* gasSatFee: 330n,
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* const result = await tx.build();
|
|
64
|
+
* // Broadcast setup first, then reveal (can use CPFP)
|
|
65
|
+
* broadcast(result.setup.txHex);
|
|
66
|
+
* broadcast(result.reveal.txHex);
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export class ConsolidatedInteractionTransaction extends TransactionBuilder<TransactionType.INTERACTION> {
|
|
70
|
+
public readonly type: TransactionType.INTERACTION = TransactionType.INTERACTION;
|
|
71
|
+
/** Random bytes for interaction (same as InteractionTransaction) */
|
|
72
|
+
public readonly randomBytes: Buffer;
|
|
73
|
+
/** The contract address (same as InteractionTransaction.to) */
|
|
74
|
+
protected readonly contractAddress: string;
|
|
75
|
+
/** The contract secret - 32 bytes (same as InteractionTransaction) */
|
|
76
|
+
protected readonly contractSecret: Buffer;
|
|
77
|
+
/** The compressed calldata (same as InteractionTransaction) */
|
|
78
|
+
protected readonly calldata: Buffer;
|
|
79
|
+
/** Challenge solution for epoch (same as InteractionTransaction) */
|
|
80
|
+
protected readonly challenge: ChallengeSolution;
|
|
81
|
+
/** Epoch challenge P2WSH address (same as InteractionTransaction) */
|
|
82
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
83
|
+
/** Script signer for interaction (same as InteractionTransaction) */
|
|
84
|
+
protected readonly scriptSigner: ECPairInterface;
|
|
85
|
+
|
|
86
|
+
/** Calldata generator - produces same output as InteractionTransaction */
|
|
87
|
+
protected readonly calldataGenerator: CalldataGenerator;
|
|
88
|
+
|
|
89
|
+
/** Hash commitment generator for CHCT */
|
|
90
|
+
protected readonly hashCommitmentGenerator: HashCommitmentGenerator;
|
|
91
|
+
|
|
92
|
+
/** The compiled operation data - SAME as InteractionTransaction's compiledTargetScript */
|
|
93
|
+
protected readonly compiledTargetScript: Buffer;
|
|
94
|
+
|
|
95
|
+
/** Generated hash-committed P2WSH outputs */
|
|
96
|
+
protected readonly commitmentOutputs: IHashCommittedP2WSH[];
|
|
97
|
+
|
|
98
|
+
/** Disable auto refund (same as InteractionTransaction) */
|
|
99
|
+
protected readonly disableAutoRefund: boolean;
|
|
100
|
+
|
|
101
|
+
/** Maximum chunk size (default: 80 bytes per P2WSH stack item limit) */
|
|
102
|
+
protected readonly maxChunkSize: number;
|
|
103
|
+
|
|
104
|
+
/** Cached value per output (calculated once, used by setup and reveal) */
|
|
105
|
+
private cachedValuePerOutput: bigint | null = null;
|
|
106
|
+
|
|
107
|
+
constructor(parameters: IConsolidatedInteractionParameters) {
|
|
108
|
+
super(parameters);
|
|
109
|
+
|
|
110
|
+
// Same validation as InteractionTransaction
|
|
111
|
+
if (!parameters.to) {
|
|
112
|
+
throw new Error('Contract address (to) is required');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!parameters.contract) {
|
|
116
|
+
throw new Error('Contract secret (contract) is required');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!parameters.calldata) {
|
|
120
|
+
throw new Error('Calldata is required');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!parameters.challenge) {
|
|
124
|
+
throw new Error('Challenge solution is required');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.contractAddress = parameters.to;
|
|
128
|
+
this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
|
|
129
|
+
this.disableAutoRefund = parameters.disableAutoRefund || false;
|
|
130
|
+
this.maxChunkSize = parameters.maxChunkSize ?? HashCommitmentGenerator.MAX_CHUNK_SIZE;
|
|
131
|
+
|
|
132
|
+
// Validate contract secret (same as InteractionTransaction)
|
|
133
|
+
if (this.contractSecret.length !== 32) {
|
|
134
|
+
throw new Error('Invalid contract secret length. Expected 32 bytes.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Compress calldata (same as SharedInteractionTransaction)
|
|
138
|
+
this.calldata = Compressor.compress(parameters.calldata);
|
|
139
|
+
|
|
140
|
+
// Generate random bytes and script signer (same as SharedInteractionTransaction)
|
|
141
|
+
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
142
|
+
this.scriptSigner = EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
|
|
143
|
+
|
|
144
|
+
// Generate epoch challenge address (same as SharedInteractionTransaction)
|
|
145
|
+
this.challenge = parameters.challenge;
|
|
146
|
+
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(
|
|
147
|
+
this.challenge.publicKey.originalPublicKeyBuffer(),
|
|
148
|
+
this.network,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Create calldata generator (same as SharedInteractionTransaction)
|
|
152
|
+
this.calldataGenerator = new CalldataGenerator(
|
|
153
|
+
Buffer.from(this.signer.publicKey),
|
|
154
|
+
toXOnly(Buffer.from(this.scriptSigner.publicKey)),
|
|
155
|
+
this.network,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Compile the target script - SAME as InteractionTransaction
|
|
159
|
+
if (parameters.compiledTargetScript) {
|
|
160
|
+
if (Buffer.isBuffer(parameters.compiledTargetScript)) {
|
|
161
|
+
this.compiledTargetScript = parameters.compiledTargetScript;
|
|
162
|
+
} else if (typeof parameters.compiledTargetScript === 'string') {
|
|
163
|
+
this.compiledTargetScript = Buffer.from(parameters.compiledTargetScript, 'hex');
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error('Invalid compiled target script format.');
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
this.compiledTargetScript = this.calldataGenerator.compile(
|
|
169
|
+
this.calldata,
|
|
170
|
+
this.contractSecret,
|
|
171
|
+
this.challenge,
|
|
172
|
+
this.priorityFee,
|
|
173
|
+
this.generateFeatures(parameters),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Create hash commitment generator
|
|
178
|
+
this.hashCommitmentGenerator = new HashCommitmentGenerator(
|
|
179
|
+
Buffer.from(this.signer.publicKey),
|
|
180
|
+
this.network,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Split compiled data into hash-committed chunks
|
|
184
|
+
this.commitmentOutputs = this.hashCommitmentGenerator.prepareChunks(
|
|
185
|
+
this.compiledTargetScript,
|
|
186
|
+
this.maxChunkSize,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Validate output count
|
|
190
|
+
this.validateOutputCount();
|
|
191
|
+
|
|
192
|
+
const totalChunks = this.commitmentOutputs.reduce(
|
|
193
|
+
(sum, output) => sum + output.dataChunks.length,
|
|
194
|
+
0,
|
|
195
|
+
);
|
|
196
|
+
this.log(
|
|
197
|
+
`ConsolidatedInteractionTransaction: ${this.commitmentOutputs.length} outputs, ` +
|
|
198
|
+
`${totalChunks} chunks from ${this.compiledTargetScript.length} bytes compiled data`,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
this.internalInit();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get the compiled target script (same as InteractionTransaction).
|
|
206
|
+
*/
|
|
207
|
+
public exportCompiledTargetScript(): Buffer {
|
|
208
|
+
return this.compiledTargetScript;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the contract secret (same as InteractionTransaction).
|
|
213
|
+
*/
|
|
214
|
+
public getContractSecret(): Buffer {
|
|
215
|
+
return this.contractSecret;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get the random bytes (same as InteractionTransaction).
|
|
220
|
+
*/
|
|
221
|
+
public getRndBytes(): Buffer {
|
|
222
|
+
return this.randomBytes;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get the challenge solution (same as InteractionTransaction).
|
|
227
|
+
*/
|
|
228
|
+
public getChallenge(): ChallengeSolution {
|
|
229
|
+
return this.challenge;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get the commitment outputs for the setup transaction.
|
|
234
|
+
*/
|
|
235
|
+
public getCommitmentOutputs(): IHashCommittedP2WSH[] {
|
|
236
|
+
return this.commitmentOutputs;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get the number of P2WSH outputs.
|
|
241
|
+
*/
|
|
242
|
+
public getOutputCount(): number {
|
|
243
|
+
return this.commitmentOutputs.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get the total number of 80-byte chunks across all outputs.
|
|
248
|
+
*/
|
|
249
|
+
public getTotalChunkCount(): number {
|
|
250
|
+
return this.commitmentOutputs.reduce((sum, output) => sum + output.dataChunks.length, 0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Build both setup and reveal transactions.
|
|
255
|
+
*
|
|
256
|
+
* @returns Complete result with both transactions
|
|
257
|
+
*/
|
|
258
|
+
public async build(): Promise<IConsolidatedInteractionResult> {
|
|
259
|
+
// Build and sign setup transaction using base class flow
|
|
260
|
+
const setupTx = await this.signTransaction();
|
|
261
|
+
const setupTxId = setupTx.getId();
|
|
262
|
+
|
|
263
|
+
const setup: ISetupTransactionResult = {
|
|
264
|
+
txHex: setupTx.toHex(),
|
|
265
|
+
txId: setupTxId,
|
|
266
|
+
outputs: this.commitmentOutputs,
|
|
267
|
+
feesPaid: this.transactionFee,
|
|
268
|
+
chunkCount: this.getTotalChunkCount(),
|
|
269
|
+
totalDataSize: this.compiledTargetScript.length,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
this.log(`Setup transaction: ${setup.txId}`);
|
|
273
|
+
|
|
274
|
+
// Build reveal transaction
|
|
275
|
+
const reveal = this.buildRevealTransaction(setupTxId);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
setup,
|
|
279
|
+
reveal,
|
|
280
|
+
totalFees: setup.feesPaid + reveal.feesPaid,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Build the reveal transaction.
|
|
286
|
+
* Spends the P2WSH commitment outputs, revealing the compiled data in witnesses.
|
|
287
|
+
*
|
|
288
|
+
* Output structure matches InteractionTransaction:
|
|
289
|
+
* - Output to epochChallenge.address (miner reward)
|
|
290
|
+
* - Change output (if any)
|
|
291
|
+
*
|
|
292
|
+
* @param setupTxId The transaction ID of the setup transaction
|
|
293
|
+
*/
|
|
294
|
+
public buildRevealTransaction(setupTxId: string): IRevealTransactionResult {
|
|
295
|
+
const revealPsbt = new Psbt({ network: this.network });
|
|
296
|
+
|
|
297
|
+
// Get the value per output (same as used in setup transaction)
|
|
298
|
+
const valuePerOutput = this.calculateValuePerOutput();
|
|
299
|
+
|
|
300
|
+
// Add commitment outputs as inputs (from setup tx)
|
|
301
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
302
|
+
const commitment = this.commitmentOutputs[i];
|
|
303
|
+
|
|
304
|
+
revealPsbt.addInput({
|
|
305
|
+
hash: setupTxId,
|
|
306
|
+
index: i,
|
|
307
|
+
witnessUtxo: {
|
|
308
|
+
script: commitment.scriptPubKey,
|
|
309
|
+
value: Number(valuePerOutput),
|
|
310
|
+
},
|
|
311
|
+
witnessScript: commitment.witnessScript,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Calculate input value from commitments
|
|
316
|
+
const inputValue = BigInt(this.commitmentOutputs.length) * valuePerOutput;
|
|
317
|
+
|
|
318
|
+
// Calculate OPNet fee (same as InteractionTransaction)
|
|
319
|
+
const opnetFee = this.getTransactionOPNetFee();
|
|
320
|
+
const feeAmount = opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
321
|
+
|
|
322
|
+
// Add output to epoch challenge address (same as InteractionTransaction)
|
|
323
|
+
revealPsbt.addOutput({
|
|
324
|
+
address: this.epochChallenge.address,
|
|
325
|
+
value: Number(feeAmount),
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Estimate reveal transaction fee
|
|
329
|
+
const estimatedVBytes = this.estimateRevealVBytes();
|
|
330
|
+
const revealFee = BigInt(Math.ceil(estimatedVBytes * this.feeRate));
|
|
331
|
+
|
|
332
|
+
// Add change output if there's enough left
|
|
333
|
+
const changeValue = inputValue - feeAmount - revealFee;
|
|
334
|
+
if (changeValue > TransactionBuilder.MINIMUM_DUST) {
|
|
335
|
+
const refundAddress = this.getRefundAddress();
|
|
336
|
+
revealPsbt.addOutput({
|
|
337
|
+
address: refundAddress,
|
|
338
|
+
value: Number(changeValue),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Sign all commitment inputs
|
|
343
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
344
|
+
revealPsbt.signInput(i, this.signer);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Finalize all inputs with hash-commitment finalizer
|
|
348
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
349
|
+
const commitment = this.commitmentOutputs[i];
|
|
350
|
+
revealPsbt.finalizeInput(i, (_inputIndex: number, input: PsbtInput) => {
|
|
351
|
+
return this.finalizeCommitmentInput(input, commitment);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const revealTx: Transaction = revealPsbt.extractTransaction();
|
|
356
|
+
|
|
357
|
+
const result: IRevealTransactionResult = {
|
|
358
|
+
txHex: revealTx.toHex(),
|
|
359
|
+
txId: revealTx.getId(),
|
|
360
|
+
dataSize: this.compiledTargetScript.length,
|
|
361
|
+
feesPaid: revealFee,
|
|
362
|
+
inputCount: this.commitmentOutputs.length,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
this.log(`Reveal transaction: ${result.txId}`);
|
|
366
|
+
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get the value per commitment output (for external access).
|
|
372
|
+
*/
|
|
373
|
+
public getValuePerOutput(): bigint {
|
|
374
|
+
return this.calculateValuePerOutput();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Build the setup transaction.
|
|
379
|
+
* Creates P2WSH outputs with hash commitments to the compiled data chunks.
|
|
380
|
+
* This is called by signTransaction() in the base class.
|
|
381
|
+
*/
|
|
382
|
+
protected override async buildTransaction(): Promise<void> {
|
|
383
|
+
// Add funding UTXOs as inputs
|
|
384
|
+
this.addInputsFromUTXO();
|
|
385
|
+
|
|
386
|
+
// Calculate value per output (includes reveal fee + OPNet fee)
|
|
387
|
+
const valuePerOutput = this.calculateValuePerOutput();
|
|
388
|
+
|
|
389
|
+
// Add each hash-committed P2WSH as an output
|
|
390
|
+
for (const commitment of this.commitmentOutputs) {
|
|
391
|
+
this.addOutput({
|
|
392
|
+
value: Number(valuePerOutput),
|
|
393
|
+
address: commitment.address,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Calculate total spent on commitment outputs
|
|
398
|
+
const totalCommitmentValue = BigInt(this.commitmentOutputs.length) * valuePerOutput;
|
|
399
|
+
|
|
400
|
+
// Add optional outputs
|
|
401
|
+
const optionalAmount = this.addOptionalOutputsAndGetAmount();
|
|
402
|
+
|
|
403
|
+
// Add refund/change output
|
|
404
|
+
await this.addRefundOutput(totalCommitmentValue + optionalAmount);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Finalize a commitment input.
|
|
409
|
+
*
|
|
410
|
+
* Witness stack: [signature, data_1, data_2, ..., data_N, witnessScript]
|
|
411
|
+
*
|
|
412
|
+
* The witness script verifies each data chunk against its committed hash.
|
|
413
|
+
* If any data is wrong or missing, the transaction is INVALID at consensus level.
|
|
414
|
+
*/
|
|
415
|
+
private finalizeCommitmentInput(
|
|
416
|
+
input: PsbtInput,
|
|
417
|
+
commitment: IHashCommittedP2WSH,
|
|
418
|
+
): {
|
|
419
|
+
finalScriptSig: Buffer | undefined;
|
|
420
|
+
finalScriptWitness: Buffer | undefined;
|
|
421
|
+
} {
|
|
422
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
423
|
+
throw new Error('No signature for commitment input');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!input.witnessScript) {
|
|
427
|
+
throw new Error('No witness script for commitment input');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Witness stack for hash-committed P2WSH with multiple chunks
|
|
431
|
+
// Order: [signature, data_1, data_2, ..., data_N, witnessScript]
|
|
432
|
+
const witnessStack: Buffer[] = [
|
|
433
|
+
input.partialSig[0].signature, // Signature for OP_CHECKSIG
|
|
434
|
+
...commitment.dataChunks, // All data chunks for OP_HASH160 verification
|
|
435
|
+
input.witnessScript, // The witness script
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
finalScriptSig: undefined,
|
|
440
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Estimate reveal transaction vBytes.
|
|
446
|
+
*/
|
|
447
|
+
private estimateRevealVBytes(): number {
|
|
448
|
+
const inputCount = this.commitmentOutputs.length;
|
|
449
|
+
|
|
450
|
+
// Calculate actual witness weight based on chunks per output
|
|
451
|
+
let witnessWeight = 0;
|
|
452
|
+
for (const commitment of this.commitmentOutputs) {
|
|
453
|
+
// Per input: 41 bytes base (× 4) + witness data
|
|
454
|
+
// Witness: signature (~72) + chunks (N × 80) + script (N × 23 + 35) + overhead (~20)
|
|
455
|
+
const numChunks = commitment.dataChunks.length;
|
|
456
|
+
const chunkDataWeight = numChunks * 80; // actual data
|
|
457
|
+
const scriptWeight = numChunks * 23 + 35; // witness script
|
|
458
|
+
const sigWeight = 72;
|
|
459
|
+
const overheadWeight = 20;
|
|
460
|
+
|
|
461
|
+
witnessWeight += 164 + chunkDataWeight + scriptWeight + sigWeight + overheadWeight;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const weight = 40 + witnessWeight + 200; // tx overhead + witnesses + outputs
|
|
465
|
+
return Math.ceil(weight / 4);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Calculate the required value per commitment output.
|
|
470
|
+
* This must cover: dust minimum + share of reveal fee + share of OPNet fee
|
|
471
|
+
*/
|
|
472
|
+
private calculateValuePerOutput(): bigint {
|
|
473
|
+
// Return cached value if already calculated
|
|
474
|
+
if (this.cachedValuePerOutput !== null) {
|
|
475
|
+
return this.cachedValuePerOutput;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const numOutputs = this.commitmentOutputs.length;
|
|
479
|
+
|
|
480
|
+
// Calculate OPNet fee
|
|
481
|
+
const opnetFee = this.getTransactionOPNetFee();
|
|
482
|
+
const feeAmount = opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
483
|
+
|
|
484
|
+
// Calculate reveal fee
|
|
485
|
+
const estimatedVBytes = this.estimateRevealVBytes();
|
|
486
|
+
const revealFee = BigInt(Math.ceil(estimatedVBytes * this.feeRate));
|
|
487
|
+
|
|
488
|
+
// Total needed: OPNet fee + reveal fee + dust for change
|
|
489
|
+
const totalNeeded = feeAmount + revealFee + TransactionBuilder.MINIMUM_DUST;
|
|
490
|
+
|
|
491
|
+
// Distribute across outputs, ensuring at least MIN_OUTPUT_VALUE per output
|
|
492
|
+
const valuePerOutput = BigInt(Math.ceil(Number(totalNeeded) / numOutputs));
|
|
493
|
+
const minValue = HashCommitmentGenerator.MIN_OUTPUT_VALUE;
|
|
494
|
+
|
|
495
|
+
this.cachedValuePerOutput = valuePerOutput > minValue ? valuePerOutput : minValue;
|
|
496
|
+
return this.cachedValuePerOutput;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Get refund address.
|
|
501
|
+
*/
|
|
502
|
+
private getRefundAddress(): string {
|
|
503
|
+
if (this.from) {
|
|
504
|
+
return this.from;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return AddressGenerator.generatePKSH(this.signer.publicKey, this.network);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Generate features (same as InteractionTransaction).
|
|
512
|
+
*/
|
|
513
|
+
private generateFeatures(parameters: IConsolidatedInteractionParameters): Feature<Features>[] {
|
|
514
|
+
const features: Feature<Features>[] = [];
|
|
515
|
+
|
|
516
|
+
if (parameters.loadedStorage) {
|
|
517
|
+
features.push({
|
|
518
|
+
priority: FeaturePriority.ACCESS_LIST,
|
|
519
|
+
opcode: Features.ACCESS_LIST,
|
|
520
|
+
data: parameters.loadedStorage,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const submission = parameters.challenge.getSubmission();
|
|
525
|
+
if (submission) {
|
|
526
|
+
features.push({
|
|
527
|
+
priority: FeaturePriority.EPOCH_SUBMISSION,
|
|
528
|
+
opcode: Features.EPOCH_SUBMISSION,
|
|
529
|
+
data: submission,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (parameters.revealMLDSAPublicKey && !parameters.linkMLDSAPublicKeyToAddress) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
'To reveal the MLDSA public key, you must set linkMLDSAPublicKeyToAddress to true.',
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (parameters.linkMLDSAPublicKeyToAddress) {
|
|
540
|
+
this.generateMLDSALinkRequest(parameters, features);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return features;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Validate output count is within standard tx limits.
|
|
548
|
+
*/
|
|
549
|
+
private validateOutputCount(): void {
|
|
550
|
+
const maxInputs = HashCommitmentGenerator.calculateMaxInputsPerTx();
|
|
551
|
+
|
|
552
|
+
if (this.commitmentOutputs.length > maxInputs) {
|
|
553
|
+
const maxData = HashCommitmentGenerator.calculateMaxDataPerTx();
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Data too large: ${this.commitmentOutputs.length} P2WSH outputs needed, ` +
|
|
556
|
+
`max ${maxInputs} per standard transaction (~${Math.floor(maxData / 1024)}KB). ` +
|
|
557
|
+
`Compiled data: ${this.compiledTargetScript.length} bytes.`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
@@ -20,8 +20,10 @@ import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
|
20
20
|
import { AddressGenerator } from '../../generators/AddressGenerator.js';
|
|
21
21
|
import { ECPairInterface } from 'ecpair';
|
|
22
22
|
|
|
23
|
-
export interface ICustomTransactionParameters
|
|
24
|
-
|
|
23
|
+
export interface ICustomTransactionParameters extends Omit<
|
|
24
|
+
SharedInteractionParameters,
|
|
25
|
+
'challenge'
|
|
26
|
+
> {
|
|
25
27
|
script: (Buffer | Stack)[];
|
|
26
28
|
witnesses: Buffer[];
|
|
27
29
|
|
|
@@ -21,8 +21,10 @@ import { UTXO } from '../../utxo/interfaces/IUTXO.js';
|
|
|
21
21
|
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
22
22
|
import { ECPairInterface } from 'ecpair';
|
|
23
23
|
|
|
24
|
-
export interface MultiSignParameters
|
|
25
|
-
|
|
24
|
+
export interface MultiSignParameters extends Omit<
|
|
25
|
+
ITransactionParameters,
|
|
26
|
+
'gasSatFee' | 'priorityFee' | 'signer'
|
|
27
|
+
> {
|
|
26
28
|
readonly pubkeys: Buffer[];
|
|
27
29
|
readonly minimumSignatures: number;
|
|
28
30
|
readonly from?: undefined;
|
|
@@ -1081,8 +1081,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
1081
1081
|
|
|
1082
1082
|
for (let i = 0; i < this.utxos.length; i++) {
|
|
1083
1083
|
const utxo = this.utxos[i];
|
|
1084
|
-
const input = this.generatePsbtInputExtended(utxo, i);
|
|
1085
1084
|
|
|
1085
|
+
// Register signer BEFORE generating input (needed for tapInternalKey)
|
|
1086
|
+
this.registerInputSigner(i, utxo);
|
|
1087
|
+
|
|
1088
|
+
const input = this.generatePsbtInputExtended(utxo, i);
|
|
1086
1089
|
this.addInput(input);
|
|
1087
1090
|
}
|
|
1088
1091
|
}
|
|
@@ -1094,8 +1097,11 @@ export abstract class TransactionBuilder<T extends TransactionType> extends Twea
|
|
|
1094
1097
|
i++
|
|
1095
1098
|
) {
|
|
1096
1099
|
const utxo = this.optionalInputs[i - this.utxos.length];
|
|
1097
|
-
const input = this.generatePsbtInputExtended(utxo, i, true);
|
|
1098
1100
|
|
|
1101
|
+
// Register signer BEFORE generating input (needed for tapInternalKey)
|
|
1102
|
+
this.registerInputSigner(i, utxo);
|
|
1103
|
+
|
|
1104
|
+
const input = this.generatePsbtInputExtended(utxo, i, true);
|
|
1099
1105
|
this.addInput(input);
|
|
1100
1106
|
}
|
|
1101
1107
|
}
|