@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,16 @@
|
|
|
1
|
+
export function createSignerMap(pairs) {
|
|
2
|
+
return new Map(pairs);
|
|
3
|
+
}
|
|
4
|
+
export function createAddressRotation(signers) {
|
|
5
|
+
const signerMap = signers instanceof Map ? signers : createSignerMap(signers);
|
|
6
|
+
return {
|
|
7
|
+
enabled: true,
|
|
8
|
+
signerMap,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function disabledAddressRotation() {
|
|
12
|
+
return {
|
|
13
|
+
enabled: false,
|
|
14
|
+
signerMap: new Map(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -8,6 +8,7 @@ import { IDeploymentParameters, IFundingTransactionParameters, IInteractionParam
|
|
|
8
8
|
import { ICancelTransactionParametersWithoutSigner, ICustomTransactionWithoutSigner, InteractionParametersWithoutSigner } from './browser/Web3Provider.js';
|
|
9
9
|
import { RawChallenge } from '../epoch/interfaces/IChallengeSolution.js';
|
|
10
10
|
import { ICancelTransactionParameters } from './builders/CancelTransaction.js';
|
|
11
|
+
import { IConsolidatedInteractionParameters } from './interfaces/IConsolidatedTransactionParameters.js';
|
|
11
12
|
export interface DeploymentResult {
|
|
12
13
|
readonly transaction: [string, string];
|
|
13
14
|
readonly contractAddress: string;
|
|
@@ -48,6 +49,18 @@ export interface CancelledTransaction {
|
|
|
48
49
|
readonly nextUTXOs: UTXO[];
|
|
49
50
|
readonly inputUtxos: UTXO[];
|
|
50
51
|
}
|
|
52
|
+
export interface ConsolidatedInteractionResponse {
|
|
53
|
+
readonly setupTransaction: string;
|
|
54
|
+
readonly revealTransaction: string;
|
|
55
|
+
readonly setupTxId: string;
|
|
56
|
+
readonly revealTxId: string;
|
|
57
|
+
readonly totalFees: bigint;
|
|
58
|
+
readonly chunkCount: number;
|
|
59
|
+
readonly dataSize: number;
|
|
60
|
+
readonly challenge: RawChallenge;
|
|
61
|
+
readonly inputUtxos: UTXO[];
|
|
62
|
+
readonly compiledTargetScript: string;
|
|
63
|
+
}
|
|
51
64
|
export declare class TransactionFactory {
|
|
52
65
|
debug: boolean;
|
|
53
66
|
private readonly DUMMY_PUBKEY;
|
|
@@ -57,6 +70,7 @@ export declare class TransactionFactory {
|
|
|
57
70
|
createCancellableTransaction(params: ICancelTransactionParameters | ICancelTransactionParametersWithoutSigner): Promise<CancelledTransaction>;
|
|
58
71
|
createCustomScriptTransaction(interactionParameters: ICustomTransactionParameters | ICustomTransactionWithoutSigner): Promise<[string, string, UTXO[], UTXO[]]>;
|
|
59
72
|
signInteraction(interactionParameters: IInteractionParameters | InteractionParametersWithoutSigner): Promise<InteractionResponse>;
|
|
73
|
+
signConsolidatedInteraction(interactionParameters: IConsolidatedInteractionParameters): Promise<ConsolidatedInteractionResponse>;
|
|
60
74
|
signDeployment(deploymentParameters: IDeploymentParameters): Promise<DeploymentResult>;
|
|
61
75
|
createBTCTransfer(parameters: IFundingTransactionParameters): Promise<BitcoinTransferResponse>;
|
|
62
76
|
getAllNewUTXOs(original: TransactionBuilder<TransactionType>, tx: Transaction, to: string): UTXO[];
|
|
@@ -10,6 +10,7 @@ import { InteractionTransactionP2WDA } from './builders/InteractionTransactionP2
|
|
|
10
10
|
import { Address } from '../keypair/Address.js';
|
|
11
11
|
import { BitcoinUtils } from '../utils/BitcoinUtils.js';
|
|
12
12
|
import { CancelTransaction } from './builders/CancelTransaction.js';
|
|
13
|
+
import { ConsolidatedInteractionTransaction } from './builders/ConsolidatedInteractionTransaction.js';
|
|
13
14
|
export class TransactionFactory {
|
|
14
15
|
constructor() {
|
|
15
16
|
this.debug = false;
|
|
@@ -177,6 +178,41 @@ export class TransactionFactory {
|
|
|
177
178
|
compiledTargetScript: interactionTx.exportCompiledTargetScript().toString('hex'),
|
|
178
179
|
};
|
|
179
180
|
}
|
|
181
|
+
async signConsolidatedInteraction(interactionParameters) {
|
|
182
|
+
if (!interactionParameters.to) {
|
|
183
|
+
throw new Error('Field "to" not provided.');
|
|
184
|
+
}
|
|
185
|
+
if (!interactionParameters.from) {
|
|
186
|
+
throw new Error('Field "from" not provided.');
|
|
187
|
+
}
|
|
188
|
+
if (!interactionParameters.utxos[0]) {
|
|
189
|
+
throw new Error('Missing at least one UTXO.');
|
|
190
|
+
}
|
|
191
|
+
if (!('signer' in interactionParameters)) {
|
|
192
|
+
throw new Error('Field "signer" not provided.');
|
|
193
|
+
}
|
|
194
|
+
if (!interactionParameters.challenge) {
|
|
195
|
+
throw new Error('Field "challenge" not provided.');
|
|
196
|
+
}
|
|
197
|
+
const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs);
|
|
198
|
+
const consolidatedTx = new ConsolidatedInteractionTransaction({
|
|
199
|
+
...interactionParameters,
|
|
200
|
+
optionalInputs: inputs,
|
|
201
|
+
});
|
|
202
|
+
const result = await consolidatedTx.build();
|
|
203
|
+
return {
|
|
204
|
+
setupTransaction: result.setup.txHex,
|
|
205
|
+
revealTransaction: result.reveal.txHex,
|
|
206
|
+
setupTxId: result.setup.txId,
|
|
207
|
+
revealTxId: result.reveal.txId,
|
|
208
|
+
totalFees: result.totalFees,
|
|
209
|
+
chunkCount: result.setup.chunkCount,
|
|
210
|
+
dataSize: result.setup.totalDataSize,
|
|
211
|
+
challenge: consolidatedTx.getChallenge().toRaw(),
|
|
212
|
+
inputUtxos: interactionParameters.utxos,
|
|
213
|
+
compiledTargetScript: consolidatedTx.exportCompiledTargetScript().toString('hex'),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
180
216
|
async signDeployment(deploymentParameters) {
|
|
181
217
|
const opWalletDeployment = await this.detectDeploymentOPWallet(deploymentParameters);
|
|
182
218
|
if (opWalletDeployment) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { ECPairInterface } from 'ecpair';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { TransactionBuilder } from './TransactionBuilder.js';
|
|
5
|
+
import { HashCommitmentGenerator } from '../../generators/builders/HashCommitmentGenerator.js';
|
|
6
|
+
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
7
|
+
import { IConsolidatedInteractionParameters, IConsolidatedInteractionResult, IHashCommittedP2WSH, IRevealTransactionResult } from '../interfaces/IConsolidatedTransactionParameters.js';
|
|
8
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
9
|
+
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
10
|
+
export declare class ConsolidatedInteractionTransaction extends TransactionBuilder<TransactionType.INTERACTION> {
|
|
11
|
+
readonly type: TransactionType.INTERACTION;
|
|
12
|
+
protected readonly contractAddress: string;
|
|
13
|
+
protected readonly contractSecret: Buffer;
|
|
14
|
+
protected readonly calldata: Buffer;
|
|
15
|
+
protected readonly challenge: ChallengeSolution;
|
|
16
|
+
protected readonly epochChallenge: IP2WSHAddress;
|
|
17
|
+
readonly randomBytes: Buffer;
|
|
18
|
+
protected readonly scriptSigner: ECPairInterface;
|
|
19
|
+
protected readonly calldataGenerator: CalldataGenerator;
|
|
20
|
+
protected readonly hashCommitmentGenerator: HashCommitmentGenerator;
|
|
21
|
+
protected readonly compiledTargetScript: Buffer;
|
|
22
|
+
protected readonly commitmentOutputs: IHashCommittedP2WSH[];
|
|
23
|
+
protected readonly disableAutoRefund: boolean;
|
|
24
|
+
protected readonly maxChunkSize: number;
|
|
25
|
+
private cachedValuePerOutput;
|
|
26
|
+
constructor(parameters: IConsolidatedInteractionParameters);
|
|
27
|
+
exportCompiledTargetScript(): Buffer;
|
|
28
|
+
getContractSecret(): Buffer;
|
|
29
|
+
getRndBytes(): Buffer;
|
|
30
|
+
getChallenge(): ChallengeSolution;
|
|
31
|
+
getCommitmentOutputs(): IHashCommittedP2WSH[];
|
|
32
|
+
getOutputCount(): number;
|
|
33
|
+
getTotalChunkCount(): number;
|
|
34
|
+
build(): Promise<IConsolidatedInteractionResult>;
|
|
35
|
+
protected buildTransaction(): Promise<void>;
|
|
36
|
+
buildRevealTransaction(setupTxId: string): IRevealTransactionResult;
|
|
37
|
+
private finalizeCommitmentInput;
|
|
38
|
+
private estimateRevealVBytes;
|
|
39
|
+
private calculateValuePerOutput;
|
|
40
|
+
getValuePerOutput(): bigint;
|
|
41
|
+
private getRefundAddress;
|
|
42
|
+
private generateFeatures;
|
|
43
|
+
private validateOutputCount;
|
|
44
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Psbt, toXOnly } from '@btc-vision/bitcoin';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { MINIMUM_AMOUNT_REWARD, TransactionBuilder } from './TransactionBuilder.js';
|
|
5
|
+
import { HashCommitmentGenerator } from '../../generators/builders/HashCommitmentGenerator.js';
|
|
6
|
+
import { CalldataGenerator } from '../../generators/builders/CalldataGenerator.js';
|
|
7
|
+
import { TimeLockGenerator } from '../mineable/TimelockGenerator.js';
|
|
8
|
+
import { EcKeyPair } from '../../keypair/EcKeyPair.js';
|
|
9
|
+
import { BitcoinUtils } from '../../utils/BitcoinUtils.js';
|
|
10
|
+
import { Compressor } from '../../bytecode/Compressor.js';
|
|
11
|
+
import { FeaturePriority, Features } from '../../generators/Features.js';
|
|
12
|
+
import { AddressGenerator } from '../../generators/AddressGenerator.js';
|
|
13
|
+
export class ConsolidatedInteractionTransaction extends TransactionBuilder {
|
|
14
|
+
constructor(parameters) {
|
|
15
|
+
super(parameters);
|
|
16
|
+
this.type = TransactionType.INTERACTION;
|
|
17
|
+
this.cachedValuePerOutput = null;
|
|
18
|
+
if (!parameters.to) {
|
|
19
|
+
throw new Error('Contract address (to) is required');
|
|
20
|
+
}
|
|
21
|
+
if (!parameters.contract) {
|
|
22
|
+
throw new Error('Contract secret (contract) is required');
|
|
23
|
+
}
|
|
24
|
+
if (!parameters.calldata) {
|
|
25
|
+
throw new Error('Calldata is required');
|
|
26
|
+
}
|
|
27
|
+
if (!parameters.challenge) {
|
|
28
|
+
throw new Error('Challenge solution is required');
|
|
29
|
+
}
|
|
30
|
+
this.contractAddress = parameters.to;
|
|
31
|
+
this.contractSecret = Buffer.from(parameters.contract.replace('0x', ''), 'hex');
|
|
32
|
+
this.disableAutoRefund = parameters.disableAutoRefund || false;
|
|
33
|
+
this.maxChunkSize = parameters.maxChunkSize ?? HashCommitmentGenerator.MAX_CHUNK_SIZE;
|
|
34
|
+
if (this.contractSecret.length !== 32) {
|
|
35
|
+
throw new Error('Invalid contract secret length. Expected 32 bytes.');
|
|
36
|
+
}
|
|
37
|
+
this.calldata = Compressor.compress(parameters.calldata);
|
|
38
|
+
this.randomBytes = parameters.randomBytes || BitcoinUtils.rndBytes();
|
|
39
|
+
this.scriptSigner = EcKeyPair.fromSeedKeyPair(this.randomBytes, this.network);
|
|
40
|
+
this.challenge = parameters.challenge;
|
|
41
|
+
this.epochChallenge = TimeLockGenerator.generateTimeLockAddress(this.challenge.publicKey.originalPublicKeyBuffer(), this.network);
|
|
42
|
+
this.calldataGenerator = new CalldataGenerator(Buffer.from(this.signer.publicKey), toXOnly(Buffer.from(this.scriptSigner.publicKey)), this.network);
|
|
43
|
+
if (parameters.compiledTargetScript) {
|
|
44
|
+
if (Buffer.isBuffer(parameters.compiledTargetScript)) {
|
|
45
|
+
this.compiledTargetScript = parameters.compiledTargetScript;
|
|
46
|
+
}
|
|
47
|
+
else if (typeof parameters.compiledTargetScript === 'string') {
|
|
48
|
+
this.compiledTargetScript = Buffer.from(parameters.compiledTargetScript, 'hex');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new Error('Invalid compiled target script format.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.compiledTargetScript = this.calldataGenerator.compile(this.calldata, this.contractSecret, this.challenge, this.priorityFee, this.generateFeatures(parameters));
|
|
56
|
+
}
|
|
57
|
+
this.hashCommitmentGenerator = new HashCommitmentGenerator(Buffer.from(this.signer.publicKey), this.network);
|
|
58
|
+
this.commitmentOutputs = this.hashCommitmentGenerator.prepareChunks(this.compiledTargetScript, this.maxChunkSize);
|
|
59
|
+
this.validateOutputCount();
|
|
60
|
+
const totalChunks = this.commitmentOutputs.reduce((sum, output) => sum + output.dataChunks.length, 0);
|
|
61
|
+
this.log(`ConsolidatedInteractionTransaction: ${this.commitmentOutputs.length} outputs, ` +
|
|
62
|
+
`${totalChunks} chunks from ${this.compiledTargetScript.length} bytes compiled data`);
|
|
63
|
+
this.internalInit();
|
|
64
|
+
}
|
|
65
|
+
exportCompiledTargetScript() {
|
|
66
|
+
return this.compiledTargetScript;
|
|
67
|
+
}
|
|
68
|
+
getContractSecret() {
|
|
69
|
+
return this.contractSecret;
|
|
70
|
+
}
|
|
71
|
+
getRndBytes() {
|
|
72
|
+
return this.randomBytes;
|
|
73
|
+
}
|
|
74
|
+
getChallenge() {
|
|
75
|
+
return this.challenge;
|
|
76
|
+
}
|
|
77
|
+
getCommitmentOutputs() {
|
|
78
|
+
return this.commitmentOutputs;
|
|
79
|
+
}
|
|
80
|
+
getOutputCount() {
|
|
81
|
+
return this.commitmentOutputs.length;
|
|
82
|
+
}
|
|
83
|
+
getTotalChunkCount() {
|
|
84
|
+
return this.commitmentOutputs.reduce((sum, output) => sum + output.dataChunks.length, 0);
|
|
85
|
+
}
|
|
86
|
+
async build() {
|
|
87
|
+
const setupTx = await this.signTransaction();
|
|
88
|
+
const setupTxId = setupTx.getId();
|
|
89
|
+
const setup = {
|
|
90
|
+
txHex: setupTx.toHex(),
|
|
91
|
+
txId: setupTxId,
|
|
92
|
+
outputs: this.commitmentOutputs,
|
|
93
|
+
feesPaid: this.transactionFee,
|
|
94
|
+
chunkCount: this.getTotalChunkCount(),
|
|
95
|
+
totalDataSize: this.compiledTargetScript.length,
|
|
96
|
+
};
|
|
97
|
+
this.log(`Setup transaction: ${setup.txId}`);
|
|
98
|
+
const reveal = this.buildRevealTransaction(setupTxId);
|
|
99
|
+
return {
|
|
100
|
+
setup,
|
|
101
|
+
reveal,
|
|
102
|
+
totalFees: setup.feesPaid + reveal.feesPaid,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async buildTransaction() {
|
|
106
|
+
this.addInputsFromUTXO();
|
|
107
|
+
const valuePerOutput = this.calculateValuePerOutput();
|
|
108
|
+
for (const commitment of this.commitmentOutputs) {
|
|
109
|
+
this.addOutput({
|
|
110
|
+
value: Number(valuePerOutput),
|
|
111
|
+
address: commitment.address,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const totalCommitmentValue = BigInt(this.commitmentOutputs.length) * valuePerOutput;
|
|
115
|
+
const optionalAmount = this.addOptionalOutputsAndGetAmount();
|
|
116
|
+
await this.addRefundOutput(totalCommitmentValue + optionalAmount);
|
|
117
|
+
}
|
|
118
|
+
buildRevealTransaction(setupTxId) {
|
|
119
|
+
const revealPsbt = new Psbt({ network: this.network });
|
|
120
|
+
const valuePerOutput = this.calculateValuePerOutput();
|
|
121
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
122
|
+
const commitment = this.commitmentOutputs[i];
|
|
123
|
+
revealPsbt.addInput({
|
|
124
|
+
hash: setupTxId,
|
|
125
|
+
index: i,
|
|
126
|
+
witnessUtxo: {
|
|
127
|
+
script: commitment.scriptPubKey,
|
|
128
|
+
value: Number(valuePerOutput),
|
|
129
|
+
},
|
|
130
|
+
witnessScript: commitment.witnessScript,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const inputValue = BigInt(this.commitmentOutputs.length) * valuePerOutput;
|
|
134
|
+
const opnetFee = this.getTransactionOPNetFee();
|
|
135
|
+
const feeAmount = opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
136
|
+
revealPsbt.addOutput({
|
|
137
|
+
address: this.epochChallenge.address,
|
|
138
|
+
value: Number(feeAmount),
|
|
139
|
+
});
|
|
140
|
+
const estimatedVBytes = this.estimateRevealVBytes();
|
|
141
|
+
const revealFee = BigInt(Math.ceil(estimatedVBytes * this.feeRate));
|
|
142
|
+
const changeValue = inputValue - feeAmount - revealFee;
|
|
143
|
+
if (changeValue > TransactionBuilder.MINIMUM_DUST) {
|
|
144
|
+
const refundAddress = this.getRefundAddress();
|
|
145
|
+
revealPsbt.addOutput({
|
|
146
|
+
address: refundAddress,
|
|
147
|
+
value: Number(changeValue),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
151
|
+
revealPsbt.signInput(i, this.signer);
|
|
152
|
+
}
|
|
153
|
+
for (let i = 0; i < this.commitmentOutputs.length; i++) {
|
|
154
|
+
const commitment = this.commitmentOutputs[i];
|
|
155
|
+
revealPsbt.finalizeInput(i, (_inputIndex, input) => {
|
|
156
|
+
return this.finalizeCommitmentInput(input, commitment);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const revealTx = revealPsbt.extractTransaction();
|
|
160
|
+
const result = {
|
|
161
|
+
txHex: revealTx.toHex(),
|
|
162
|
+
txId: revealTx.getId(),
|
|
163
|
+
dataSize: this.compiledTargetScript.length,
|
|
164
|
+
feesPaid: revealFee,
|
|
165
|
+
inputCount: this.commitmentOutputs.length,
|
|
166
|
+
};
|
|
167
|
+
this.log(`Reveal transaction: ${result.txId}`);
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
finalizeCommitmentInput(input, commitment) {
|
|
171
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
172
|
+
throw new Error('No signature for commitment input');
|
|
173
|
+
}
|
|
174
|
+
if (!input.witnessScript) {
|
|
175
|
+
throw new Error('No witness script for commitment input');
|
|
176
|
+
}
|
|
177
|
+
const witnessStack = [
|
|
178
|
+
input.partialSig[0].signature,
|
|
179
|
+
...commitment.dataChunks,
|
|
180
|
+
input.witnessScript,
|
|
181
|
+
];
|
|
182
|
+
return {
|
|
183
|
+
finalScriptSig: undefined,
|
|
184
|
+
finalScriptWitness: TransactionBuilder.witnessStackToScriptWitness(witnessStack),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
estimateRevealVBytes() {
|
|
188
|
+
const inputCount = this.commitmentOutputs.length;
|
|
189
|
+
let witnessWeight = 0;
|
|
190
|
+
for (const commitment of this.commitmentOutputs) {
|
|
191
|
+
const numChunks = commitment.dataChunks.length;
|
|
192
|
+
const chunkDataWeight = numChunks * 80;
|
|
193
|
+
const scriptWeight = numChunks * 23 + 35;
|
|
194
|
+
const sigWeight = 72;
|
|
195
|
+
const overheadWeight = 20;
|
|
196
|
+
witnessWeight += 164 + chunkDataWeight + scriptWeight + sigWeight + overheadWeight;
|
|
197
|
+
}
|
|
198
|
+
const weight = 40 + witnessWeight + 200;
|
|
199
|
+
return Math.ceil(weight / 4);
|
|
200
|
+
}
|
|
201
|
+
calculateValuePerOutput() {
|
|
202
|
+
if (this.cachedValuePerOutput !== null) {
|
|
203
|
+
return this.cachedValuePerOutput;
|
|
204
|
+
}
|
|
205
|
+
const numOutputs = this.commitmentOutputs.length;
|
|
206
|
+
const opnetFee = this.getTransactionOPNetFee();
|
|
207
|
+
const feeAmount = opnetFee < MINIMUM_AMOUNT_REWARD ? MINIMUM_AMOUNT_REWARD : opnetFee;
|
|
208
|
+
const estimatedVBytes = this.estimateRevealVBytes();
|
|
209
|
+
const revealFee = BigInt(Math.ceil(estimatedVBytes * this.feeRate));
|
|
210
|
+
const totalNeeded = feeAmount + revealFee + TransactionBuilder.MINIMUM_DUST;
|
|
211
|
+
const valuePerOutput = BigInt(Math.ceil(Number(totalNeeded) / numOutputs));
|
|
212
|
+
const minValue = HashCommitmentGenerator.MIN_OUTPUT_VALUE;
|
|
213
|
+
this.cachedValuePerOutput = valuePerOutput > minValue ? valuePerOutput : minValue;
|
|
214
|
+
return this.cachedValuePerOutput;
|
|
215
|
+
}
|
|
216
|
+
getValuePerOutput() {
|
|
217
|
+
return this.calculateValuePerOutput();
|
|
218
|
+
}
|
|
219
|
+
getRefundAddress() {
|
|
220
|
+
if (this.from) {
|
|
221
|
+
return this.from;
|
|
222
|
+
}
|
|
223
|
+
return AddressGenerator.generatePKSH(this.signer.publicKey, this.network);
|
|
224
|
+
}
|
|
225
|
+
generateFeatures(parameters) {
|
|
226
|
+
const features = [];
|
|
227
|
+
if (parameters.loadedStorage) {
|
|
228
|
+
features.push({
|
|
229
|
+
priority: FeaturePriority.ACCESS_LIST,
|
|
230
|
+
opcode: Features.ACCESS_LIST,
|
|
231
|
+
data: parameters.loadedStorage,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const submission = parameters.challenge.getSubmission();
|
|
235
|
+
if (submission) {
|
|
236
|
+
features.push({
|
|
237
|
+
priority: FeaturePriority.EPOCH_SUBMISSION,
|
|
238
|
+
opcode: Features.EPOCH_SUBMISSION,
|
|
239
|
+
data: submission,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (parameters.revealMLDSAPublicKey && !parameters.linkMLDSAPublicKeyToAddress) {
|
|
243
|
+
throw new Error('To reveal the MLDSA public key, you must set linkMLDSAPublicKeyToAddress to true.');
|
|
244
|
+
}
|
|
245
|
+
if (parameters.linkMLDSAPublicKeyToAddress) {
|
|
246
|
+
this.generateMLDSALinkRequest(parameters, features);
|
|
247
|
+
}
|
|
248
|
+
return features;
|
|
249
|
+
}
|
|
250
|
+
validateOutputCount() {
|
|
251
|
+
const maxInputs = HashCommitmentGenerator.calculateMaxInputsPerTx();
|
|
252
|
+
if (this.commitmentOutputs.length > maxInputs) {
|
|
253
|
+
const maxData = HashCommitmentGenerator.calculateMaxDataPerTx();
|
|
254
|
+
throw new Error(`Data too large: ${this.commitmentOutputs.length} P2WSH outputs needed, ` +
|
|
255
|
+
`max ${maxInputs} per standard transaction (~${Math.floor(maxData / 1024)}KB). ` +
|
|
256
|
+
`Compiled data: ${this.compiledTargetScript.length} bytes.`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -620,6 +620,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
620
620
|
}
|
|
621
621
|
for (let i = 0; i < this.utxos.length; i++) {
|
|
622
622
|
const utxo = this.utxos[i];
|
|
623
|
+
this.registerInputSigner(i, utxo);
|
|
623
624
|
const input = this.generatePsbtInputExtended(utxo, i);
|
|
624
625
|
this.addInput(input);
|
|
625
626
|
}
|
|
@@ -627,6 +628,7 @@ export class TransactionBuilder extends TweakedTransaction {
|
|
|
627
628
|
if (this.optionalInputs) {
|
|
628
629
|
for (let i = this.utxos.length; i < this.optionalInputs.length + this.utxos.length; i++) {
|
|
629
630
|
const utxo = this.optionalInputs[i - this.utxos.length];
|
|
631
|
+
this.registerInputSigner(i, utxo);
|
|
630
632
|
const input = this.generatePsbtInputExtended(utxo, i, true);
|
|
631
633
|
this.addInput(input);
|
|
632
634
|
}
|
|
@@ -7,4 +7,6 @@ export var TransactionType;
|
|
|
7
7
|
TransactionType[TransactionType["MULTI_SIG"] = 4] = "MULTI_SIG";
|
|
8
8
|
TransactionType[TransactionType["CUSTOM_CODE"] = 5] = "CUSTOM_CODE";
|
|
9
9
|
TransactionType[TransactionType["CANCEL"] = 6] = "CANCEL";
|
|
10
|
+
TransactionType[TransactionType["CONSOLIDATED_SETUP"] = 7] = "CONSOLIDATED_SETUP";
|
|
11
|
+
TransactionType[TransactionType["CONSOLIDATED_REVEAL"] = 8] = "CONSOLIDATED_REVEAL";
|
|
10
12
|
})(TransactionType || (TransactionType = {}));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IInteractionParameters } from './ITransactionParameters.js';
|
|
2
|
+
import { IP2WSHAddress } from '../mineable/IP2WSHAddress.js';
|
|
3
|
+
export interface IHashCommittedP2WSH extends IP2WSHAddress {
|
|
4
|
+
readonly dataHashes: Buffer[];
|
|
5
|
+
readonly dataChunks: Buffer[];
|
|
6
|
+
readonly chunkStartIndex: number;
|
|
7
|
+
readonly scriptPubKey: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export interface IConsolidatedInteractionParameters extends IInteractionParameters {
|
|
10
|
+
readonly maxChunkSize?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ISetupTransactionResult {
|
|
13
|
+
readonly txHex: string;
|
|
14
|
+
readonly txId: string;
|
|
15
|
+
readonly outputs: IHashCommittedP2WSH[];
|
|
16
|
+
readonly feesPaid: bigint;
|
|
17
|
+
readonly chunkCount: number;
|
|
18
|
+
readonly totalDataSize: number;
|
|
19
|
+
}
|
|
20
|
+
export interface IRevealTransactionResult {
|
|
21
|
+
readonly txHex: string;
|
|
22
|
+
readonly txId: string;
|
|
23
|
+
readonly dataSize: number;
|
|
24
|
+
readonly feesPaid: bigint;
|
|
25
|
+
readonly inputCount: number;
|
|
26
|
+
}
|
|
27
|
+
export interface IConsolidatedInteractionResult {
|
|
28
|
+
readonly setup: ISetupTransactionResult;
|
|
29
|
+
readonly reveal: IRevealTransactionResult;
|
|
30
|
+
readonly totalFees: bigint;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,6 +3,7 @@ import { ITweakedTransactionData } from '../shared/TweakedTransaction.js';
|
|
|
3
3
|
import { ChainId } from '../../network/ChainId.js';
|
|
4
4
|
import { PsbtOutputExtended } from '@btc-vision/bitcoin';
|
|
5
5
|
import { ChallengeSolution } from '../../epoch/ChallengeSolution.js';
|
|
6
|
+
import { AddressRotationConfig } from '../../signer/AddressRotation.js';
|
|
6
7
|
export interface LoadedStorage {
|
|
7
8
|
[key: string]: string[];
|
|
8
9
|
}
|
|
@@ -25,6 +26,7 @@ export interface ITransactionParameters extends ITweakedTransactionData {
|
|
|
25
26
|
readonly priorityFee: bigint;
|
|
26
27
|
readonly gasSatFee: bigint;
|
|
27
28
|
readonly compiledTargetScript?: Buffer | string;
|
|
29
|
+
readonly addressRotation?: AddressRotationConfig;
|
|
28
30
|
}
|
|
29
31
|
export interface IFundingTransactionParameters extends ITransactionParameters {
|
|
30
32
|
amount: bigint;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Signer } from '@btc-vision/bitcoin';
|
|
2
|
+
import { ECPairInterface } from 'ecpair';
|
|
3
|
+
import { TransactionType } from '../enums/TransactionType.js';
|
|
4
|
+
import { TransactionBuilder } from '../builders/TransactionBuilder.js';
|
|
5
|
+
import { ISerializableTransactionState, PrecomputedData } from './interfaces/ISerializableState.js';
|
|
6
|
+
import { ReconstructionOptions } from './TransactionReconstructor.js';
|
|
7
|
+
import { IDeploymentParameters, IFundingTransactionParameters, IInteractionParameters, ITransactionParameters } from '../interfaces/ITransactionParameters.js';
|
|
8
|
+
export interface ExportOptions {
|
|
9
|
+
params: ITransactionParameters;
|
|
10
|
+
type: TransactionType;
|
|
11
|
+
precomputed?: Partial<PrecomputedData>;
|
|
12
|
+
}
|
|
13
|
+
export declare class OfflineTransactionManager {
|
|
14
|
+
static exportFunding(params: IFundingTransactionParameters, precomputed?: Partial<PrecomputedData>): string;
|
|
15
|
+
static exportDeployment(params: IDeploymentParameters, precomputed: Partial<PrecomputedData> & {
|
|
16
|
+
compiledTargetScript: string;
|
|
17
|
+
randomBytes: string;
|
|
18
|
+
}): string;
|
|
19
|
+
static exportInteraction(params: IInteractionParameters, precomputed: Partial<PrecomputedData> & {
|
|
20
|
+
compiledTargetScript: string;
|
|
21
|
+
randomBytes: string;
|
|
22
|
+
}): string;
|
|
23
|
+
static exportMultiSig(params: ITransactionParameters & {
|
|
24
|
+
pubkeys: Buffer[];
|
|
25
|
+
minimumSignatures: number;
|
|
26
|
+
receiver: string;
|
|
27
|
+
requestedAmount: bigint;
|
|
28
|
+
refundVault: string;
|
|
29
|
+
originalInputCount?: number;
|
|
30
|
+
existingPsbtBase64?: string;
|
|
31
|
+
}, precomputed?: Partial<PrecomputedData>): string;
|
|
32
|
+
static exportCustomScript(params: ITransactionParameters & {
|
|
33
|
+
scriptElements: (Buffer | number)[];
|
|
34
|
+
witnesses: Buffer[];
|
|
35
|
+
annex?: Buffer;
|
|
36
|
+
}, precomputed?: Partial<PrecomputedData>): string;
|
|
37
|
+
static exportCancel(params: ITransactionParameters & {
|
|
38
|
+
compiledTargetScript: Buffer | string;
|
|
39
|
+
}, precomputed?: Partial<PrecomputedData>): string;
|
|
40
|
+
static exportFromBuilder<T extends TransactionType>(builder: TransactionBuilder<T>, params: ITransactionParameters, precomputed?: Partial<PrecomputedData>): string;
|
|
41
|
+
static importForSigning(serializedState: string, options: ReconstructionOptions): TransactionBuilder<TransactionType>;
|
|
42
|
+
static signAndExport(builder: TransactionBuilder<TransactionType>): Promise<string>;
|
|
43
|
+
static importSignAndExport(serializedState: string, options: ReconstructionOptions): Promise<string>;
|
|
44
|
+
static rebuildWithNewFees(serializedState: string, newFeeRate: number): string;
|
|
45
|
+
static rebuildSignAndExport(serializedState: string, newFeeRate: number, options: ReconstructionOptions): Promise<string>;
|
|
46
|
+
static inspect(serializedState: string): ISerializableTransactionState;
|
|
47
|
+
static validate(serializedState: string): boolean;
|
|
48
|
+
static getType(serializedState: string): TransactionType;
|
|
49
|
+
static fromBase64(base64State: string): ISerializableTransactionState;
|
|
50
|
+
static toBase64(state: ISerializableTransactionState): string;
|
|
51
|
+
static toHex(serializedState: string): string;
|
|
52
|
+
static fromHex(hexState: string): string;
|
|
53
|
+
static multiSigAddSignature(serializedState: string, signer: Signer | ECPairInterface): Promise<{
|
|
54
|
+
state: string;
|
|
55
|
+
signed: boolean;
|
|
56
|
+
final: boolean;
|
|
57
|
+
psbtBase64: string;
|
|
58
|
+
}>;
|
|
59
|
+
static multiSigHasSigned(serializedState: string, signerPubKey: Buffer | string): boolean;
|
|
60
|
+
static multiSigGetSignatureStatus(serializedState: string): {
|
|
61
|
+
required: number;
|
|
62
|
+
collected: number;
|
|
63
|
+
isComplete: boolean;
|
|
64
|
+
signers: string[];
|
|
65
|
+
};
|
|
66
|
+
static multiSigFinalize(serializedState: string): string;
|
|
67
|
+
static multiSigGetPsbt(serializedState: string): string | null;
|
|
68
|
+
static multiSigUpdatePsbt(serializedState: string, psbtBase64: string): string;
|
|
69
|
+
}
|