liquidjs-lib
Version:
Client-side Liquid JavaScript library
1,009 lines (1,008 loc) • 35.4 kB
JavaScript
'use strict';
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (
!desc ||
('get' in desc ? !m.__esModule : desc.writable || desc.configurable)
) {
desc = {
enumerable: true,
get: function () {
return m[k];
},
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __setModuleDefault =
(this && this.__setModuleDefault) ||
(Object.create
? function (o, v) {
Object.defineProperty(o, 'default', { enumerable: true, value: v });
}
: function (o, v) {
o['default'] = v;
});
var __importStar =
(this && this.__importStar) ||
function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null)
for (var k in mod)
if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k))
__createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, '__esModule', { value: true });
exports.Transaction = exports.ZERO = void 0;
const bufferutils_1 = require('./bufferutils');
const bcrypto = __importStar(require('./crypto'));
const bscript = __importStar(require('./script'));
const script_1 = require('./script');
const types = __importStar(require('./types'));
const { typeforce } = types;
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
const EMPTY_WITNESS = [];
exports.ZERO = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex',
);
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex',
);
const WITNESS_SCALE_FACTOR = 4;
const OUTPOINT_ISSUANCE_FLAG = (1 << 31) >>> 0;
const OUTPOINT_PEGIN_FLAG = (1 << 30) >>> 0;
const OUTPOINT_INDEX_MASK = 0x3fffffff;
const MINUS_1 = 4294967295;
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
const BLANK_OUTPUT = {
script: EMPTY_BUFFER,
asset: exports.ZERO,
nonce: exports.ZERO,
valueBuffer: VALUE_UINT64_MAX,
};
class Transaction {
constructor() {
this.version = 1;
this.locktime = 0;
this.flag = 0;
this.ins = [];
this.outs = [];
}
static fromBuffer(buffer, _NO_STRICT) {
const bufferReader = new bufferutils_1.BufferReader(buffer);
const tx = new Transaction();
tx.version = bufferReader.readInt32();
tx.flag = bufferReader.readUInt8();
let hasWitnesses = false;
if (tx.flag & Transaction.ADVANCED_TRANSACTION_FLAG) {
hasWitnesses = true;
}
const vinLen = bufferReader.readVarInt();
for (let i = 0; i < vinLen; ++i) {
const inHash = bufferReader.readSlice(32);
let inIndex = bufferReader.readUInt32();
const inScript = bufferReader.readVarSlice();
const inSequence = bufferReader.readUInt32();
let inIsPegin = false;
let inIssuance;
if (inIndex !== MINUS_1) {
if (inIndex & OUTPOINT_ISSUANCE_FLAG) {
inIssuance = bufferReader.readIssuance();
}
if (inIndex & OUTPOINT_PEGIN_FLAG) {
inIsPegin = true;
}
inIndex &= OUTPOINT_INDEX_MASK;
}
tx.ins.push({
hash: inHash,
index: inIndex,
script: inScript,
sequence: inSequence,
witness: EMPTY_WITNESS,
isPegin: inIsPegin,
issuance: inIssuance,
peginWitness: EMPTY_WITNESS,
issuanceRangeProof: EMPTY_BUFFER,
inflationRangeProof: EMPTY_BUFFER,
});
}
const voutLen = bufferReader.readVarInt();
for (let i = 0; i < voutLen; ++i) {
const asset = bufferReader.readConfidentialAsset();
const value = bufferReader.readConfidentialValue();
const nonce = bufferReader.readConfidentialNonce();
const script = bufferReader.readVarSlice();
tx.outs.push({
asset,
value,
nonce,
script,
rangeProof: EMPTY_BUFFER,
surjectionProof: EMPTY_BUFFER,
});
}
tx.locktime = bufferReader.readUInt32();
if (hasWitnesses) {
for (let i = 0; i < vinLen; ++i) {
const {
witness,
peginWitness,
issuanceRangeProof,
inflationRangeProof,
} = bufferReader.readConfidentialInFields();
tx.ins[i].witness = witness;
tx.ins[i].peginWitness = peginWitness;
tx.ins[i].issuanceRangeProof = issuanceRangeProof;
tx.ins[i].inflationRangeProof = inflationRangeProof;
}
for (let i = 0; i < voutLen; ++i) {
const { rangeProof, surjectionProof } =
bufferReader.readConfidentialOutFields();
tx.outs[i].rangeProof = rangeProof;
tx.outs[i].surjectionProof = surjectionProof;
}
}
if (_NO_STRICT) return tx;
if (bufferReader.offset !== buffer.length)
throw new Error('Transaction has unexpected data');
return tx;
}
static fromHex(hex) {
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
}
static isCoinbaseHash(buffer) {
typeforce(types.Hash256bit, buffer);
for (let i = 0; i < 32; ++i) {
if (buffer[i] !== 0) return false;
}
return true;
}
isCoinbase() {
return (
this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)
);
}
// A quick and reliable way to validate that all the buffers are of correct type and length
validateIssuance(assetBlindingNonce, assetEntropy, assetAmount, tokenAmount) {
typeforce(types.Hash256bit, assetBlindingNonce);
typeforce(types.Hash256bit, assetEntropy);
typeforce(
types.oneOf(
types.ConfidentialValue,
types.ConfidentialCommitment,
types.BufferOne,
),
assetAmount,
);
typeforce(
types.oneOf(
types.ConfidentialValue,
types.ConfidentialCommitment,
types.BufferOne,
),
tokenAmount,
);
return true;
}
addInput(hash, index, sequence, scriptSig, issuance) {
typeforce(
types.tuple(
types.Hash256bit,
types.UInt32,
types.maybe(types.UInt32),
types.maybe(types.Buffer),
types.maybe(types.Object),
),
arguments,
);
let isPegin = false;
if (index !== MINUS_1) {
if (index & OUTPOINT_ISSUANCE_FLAG) {
if (!issuance) {
throw new Error(
'Issuance flag has been set but the Issuance object is not defined or invalid',
);
} else
this.validateIssuance(
issuance.assetBlindingNonce,
issuance.assetEntropy,
issuance.assetAmount,
issuance.tokenAmount,
);
}
if (index & OUTPOINT_PEGIN_FLAG) {
isPegin = true;
}
index &= OUTPOINT_INDEX_MASK;
}
// Add the input and return the input's index
return (
this.ins.push({
hash,
index,
isPegin,
issuance,
script: scriptSig || EMPTY_BUFFER,
witness: EMPTY_WITNESS,
peginWitness: EMPTY_WITNESS,
issuanceRangeProof: EMPTY_BUFFER,
inflationRangeProof: EMPTY_BUFFER,
sequence: sequence ?? Transaction.DEFAULT_SEQUENCE,
}) - 1
);
}
addOutput(scriptPubKey, value, asset, nonce, rangeProof, surjectionProof) {
typeforce(
types.tuple(
types.Buffer,
types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment),
types.AssetBufferWithFlag,
types.oneOf(types.ConfidentialCommitment, types.BufferOne),
types.maybe(types.Buffer),
types.maybe(types.Buffer),
),
arguments,
);
// Add the output and return the output's index
return (
this.outs.push({
script: scriptPubKey,
value,
asset,
nonce: nonce || EMPTY_BUFFER,
rangeProof: rangeProof || EMPTY_BUFFER,
surjectionProof: surjectionProof || EMPTY_BUFFER,
}) - 1
);
}
hasWitnesses() {
return (
this.flag === 1 ||
this.ins.some((x) => {
return x.witness.length !== 0;
}) ||
this.outs.some((x) => {
return x.rangeProof.length !== 0 && x.surjectionProof.length !== 0;
})
);
}
weight(discountCT = true) {
const base = this.__byteLength(false, false);
const total = this.__byteLength(true, false);
let weight = base * (WITNESS_SCALE_FACTOR - 1) + total;
if (!discountCT) {
return weight;
}
for (const txOut of this.outs) {
let witnessWeight = 0;
if (txOut.rangeProof !== undefined && txOut.rangeProof.length > 0) {
witnessWeight += txOut.rangeProof.length;
witnessWeight += bufferutils_1.varuint.encodingLength(
txOut.rangeProof.length,
);
}
if (
txOut.surjectionProof !== undefined &&
txOut.surjectionProof.length > 0
) {
witnessWeight += txOut.surjectionProof.length;
witnessWeight += bufferutils_1.varuint.encodingLength(
txOut.surjectionProof.length,
);
}
// Explicit transactions have 1 byte for each empty proof
weight -= Math.max(witnessWeight - 2, 0);
// When the output has a range proof, it is safe to assume that the nonce and value are confidential
if (txOut.rangeProof !== undefined && txOut.rangeProof.length > 0) {
weight -= (33 - 9) * 4;
weight -= (33 - 1) * 4;
}
}
return weight;
}
virtualSize(discountCT = true) {
const vsize =
(this.weight(discountCT) + WITNESS_SCALE_FACTOR - 1) /
WITNESS_SCALE_FACTOR;
return Math.floor(vsize);
}
byteLength(_ALLOW_WITNESS) {
return this.__byteLength(_ALLOW_WITNESS || true);
}
clone() {
const newTx = new Transaction();
newTx.version = this.version;
newTx.locktime = this.locktime;
newTx.flag = this.flag;
newTx.ins = this.ins.map((txIn) => {
return {
hash: txIn.hash,
index: txIn.index,
script: txIn.script,
sequence: txIn.sequence,
witness: txIn.witness,
isPegin: txIn.isPegin,
issuance: txIn.issuance,
peginWitness: txIn.peginWitness,
issuanceRangeProof: txIn.issuanceRangeProof,
inflationRangeProof: txIn.inflationRangeProof,
};
});
newTx.outs = this.outs.map((txOut) => {
return {
script: txOut.script,
value: txOut.value,
asset: txOut.asset,
nonce: txOut.nonce,
rangeProof: txOut.rangeProof,
surjectionProof: txOut.surjectionProof,
};
});
return newTx;
}
/**
* Hash transaction for signing a specific input.
*
* Bitcoin uses a different hash for each signed transaction input.
* This method copies the transaction, makes the necessary changes based on the
* hashType, and then hashes the result.
* This hash can then be used to sign the provided transaction input.
*/
hashForSignature(inIndex, prevOutScript, hashType) {
typeforce(
types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number),
arguments,
);
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
if (inIndex >= this.ins.length) return ONE;
// ignore OP_CODESEPARATOR
const ourScript = bscript.compile(
bscript.decompile(prevOutScript).filter((x) => {
return x !== script_1.OPS.OP_CODESEPARATOR;
}),
);
const txTmp = this.clone();
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
txTmp.outs = [];
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, i) => {
if (i === inIndex) return;
input.sequence = 0;
});
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
} else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
if (inIndex >= this.outs.length) return ONE;
// truncate outputs after
txTmp.outs.length = inIndex + 1;
// "blank" outputs before
for (let i = 0; i < inIndex; i++) {
txTmp.outs[i] = BLANK_OUTPUT;
}
// ignore sequence numbers (except at inIndex)
txTmp.ins.forEach((input, y) => {
if (y === inIndex) return;
input.sequence = 0;
});
}
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
txTmp.ins = [txTmp.ins[inIndex]];
txTmp.ins[0].script = ourScript;
// SIGHASH_ALL: only ignore input scripts
} else {
// "blank" others input scripts
txTmp.ins.forEach((input) => {
input.script = EMPTY_BUFFER;
});
txTmp.ins[inIndex].script = ourScript;
}
// serialize and hash
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false, true) + 4);
buffer.writeInt32LE(hashType, buffer.length - 4);
txTmp.__toBuffer(buffer, 0, false, true, true);
return bcrypto.hash256(buffer);
}
// differs from bitcoin core
// https://github.com/ElementsProject/elements/blob/84b3f7b0045b50a585d60e56e77e8914b6cf6040/doc/taproot-sighash.mediawiki
hashForWitnessV1(
inIndex,
prevOutScripts,
prevoutAssetsValues,
hashType,
genesisBlockHash,
leafHash,
annex,
) {
typeforce(
types.tuple(
types.UInt32,
typeforce.arrayOf(types.Buffer),
typeforce.arrayOf(types.Object),
types.UInt32,
),
arguments,
);
if (prevOutScripts.length !== this.ins.length) {
throw new Error('Must supply prevout script and value for all inputs');
}
const outputType =
hashType === Transaction.SIGHASH_DEFAULT
? Transaction.SIGHASH_ALL
: hashType & Transaction.SIGHASH_OUTPUT_MASK;
const inputType = hashType & Transaction.SIGHASH_INPUT_MASK;
const isAnyoneCanPay = inputType === Transaction.SIGHASH_ANYONECANPAY;
const isNone = outputType === Transaction.SIGHASH_NONE;
const isSingle = outputType === Transaction.SIGHASH_SINGLE;
let hashPrevouts = EMPTY_BUFFER;
let hashSequences = EMPTY_BUFFER;
let hashOutputs = EMPTY_BUFFER;
let hashIssuances = EMPTY_BUFFER;
let hashScriptPubKeys = EMPTY_BUFFER;
// elements new hashes for witness v1
let hashOutpointsFlags = EMPTY_BUFFER;
let hashIssuancesProofs = EMPTY_BUFFER;
let hashOutputsWitnesses = EMPTY_BUFFER;
let hashSpentAssetsAmounts = EMPTY_BUFFER;
if (!isAnyoneCanPay) {
hashPrevouts = getPrevoutsSHA256(this.ins);
hashOutpointsFlags = getOutpointFlagsSHA256(this.ins);
hashSpentAssetsAmounts = getSpentAssetsAmountsSHA256(prevoutAssetsValues);
hashIssuancesProofs = getIssuanceProofsSHA256(this.ins);
hashScriptPubKeys = getPrevoutScriptsSHA256(prevOutScripts);
hashSequences = getSequenceSHA256(this.ins);
hashIssuances = getIssuanceSHA256(this.ins);
}
if (!(isNone || isSingle)) {
hashOutputs = getOutputsSHA256(this.outs);
hashOutputsWitnesses = getOutputWitnessesSHA256(this.outs);
} else if (isSingle && inIndex < this.outs.length) {
const output = this.outs[inIndex];
hashOutputs = getOutputsSHA256([output]);
hashOutputsWitnesses = getOutputWitnessesSHA256([output]);
}
// key-path spent or a tapscript (annex is for future update)
const spendType = (leafHash ? 2 : 0) + (annex ? 1 : 0);
// Length calculation from:
// https://github.com/ElementsProject/elements/blob/84b3f7b0045b50a585d60e56e77e8914b6cf6040/doc/taproot-sighash.mediawiki
const inputPartSize = isAnyoneCanPay
? 1 +
32 +
4 +
prevoutAssetsValues[inIndex].asset.length +
prevoutAssetsValues[inIndex].value.length +
(0, bufferutils_1.varSliceSize)(prevOutScripts[inIndex]) +
4 +
(this.ins[inIndex].issuance ? getIssuanceSize(this.ins[inIndex]) : 1)
: 4;
const fullMsgSize = 32 * 2 + 1 + 4 + 4 + 1 + inputPartSize;
const sigMsgSize =
fullMsgSize +
(!isAnyoneCanPay ? 7 * 32 : 0) +
(!(isNone || isSingle) ? 32 + 32 : 0) +
(annex ? 32 : 0) +
(isSingle ? 32 + 32 : 0) +
(leafHash ? 37 : 0);
const sigMsgWriter = bufferutils_1.BufferWriter.withCapacity(sigMsgSize);
// this is "blockchain rationale", only used in elements
// it prevents signatures to be reused accross different Elements instance
sigMsgWriter.writeSlice(genesisBlockHash);
sigMsgWriter.writeSlice(genesisBlockHash);
sigMsgWriter.writeUInt8(hashType);
// Transaction
sigMsgWriter.writeInt32(this.version);
sigMsgWriter.writeUInt32(this.locktime);
if (!isAnyoneCanPay) {
sigMsgWriter.writeSlice(hashOutpointsFlags);
sigMsgWriter.writeSlice(hashPrevouts);
sigMsgWriter.writeSlice(hashSpentAssetsAmounts);
sigMsgWriter.writeSlice(hashScriptPubKeys);
sigMsgWriter.writeSlice(hashSequences);
sigMsgWriter.writeSlice(hashIssuances);
sigMsgWriter.writeSlice(hashIssuancesProofs);
}
if (!(isNone || isSingle)) {
sigMsgWriter.writeSlice(hashOutputs);
sigMsgWriter.writeSlice(hashOutputsWitnesses);
}
// Input
sigMsgWriter.writeUInt8(spendType);
if (isAnyoneCanPay) {
const input = this.ins[inIndex];
sigMsgWriter.writeUInt8(getInputFlag(input));
sigMsgWriter.writeSlice(input.hash);
sigMsgWriter.writeUInt32(input.index);
sigMsgWriter.writeSlice(prevoutAssetsValues[inIndex].asset);
sigMsgWriter.writeSlice(prevoutAssetsValues[inIndex].value);
sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]);
sigMsgWriter.writeUInt32(input.sequence);
if (input.issuance) {
sigMsgWriter.writeSlice(input.issuance.assetBlindingNonce);
sigMsgWriter.writeSlice(input.issuance.assetEntropy);
sigMsgWriter.writeSlice(input.issuance.assetAmount);
sigMsgWriter.writeSlice(input.issuance.tokenAmount);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
(0, bufferutils_1.varSliceSize)(input.issuanceRangeProof) +
(0, bufferutils_1.varSliceSize)(input.inflationRangeProof),
);
bufferWriter.writeVarSlice(input.issuanceRangeProof || Buffer.of(0x00));
bufferWriter.writeVarSlice(
input.inflationRangeProof || Buffer.of(0x00),
);
const hashIssuance = bcrypto.sha256(bufferWriter.end());
sigMsgWriter.writeSlice(hashIssuance);
} else {
sigMsgWriter.writeSlice(Buffer.of(0x00));
}
} else {
sigMsgWriter.writeUInt32(inIndex);
}
if (annex) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
(0, bufferutils_1.varSliceSize)(annex),
);
bufferWriter.writeVarSlice(annex);
sigMsgWriter.writeSlice(bcrypto.sha256(bufferWriter.end()));
}
if (isSingle) {
sigMsgWriter.writeSlice(hashOutputs);
sigMsgWriter.writeSlice(hashOutputsWitnesses);
}
// BIP342 extension
if (leafHash) {
sigMsgWriter.writeSlice(leafHash);
sigMsgWriter.writeUInt8(0);
sigMsgWriter.writeUInt32(0xffffffff);
}
return bcrypto.taggedHash('TapSighash/elements', sigMsgWriter.end());
}
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
typeforce(
types.tuple(types.UInt32, types.Buffer, types.Buffer, types.UInt32),
arguments,
);
let hashOutputs = exports.ZERO;
let hashPrevouts = exports.ZERO;
let hashSequence = exports.ZERO;
let hashIssuances = exports.ZERO;
// Inputs
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
const prevoutsHashWriter = bufferutils_1.BufferWriter.withCapacity(
(32 + 4) * this.ins.length,
);
this.ins.forEach((txIn) => {
prevoutsHashWriter.writeSlice(txIn.hash);
prevoutsHashWriter.writeUInt32(txIn.index);
});
hashPrevouts = bcrypto.hash256(prevoutsHashWriter.end());
}
// Sequences
if (
!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE
) {
const sequenceHashWriter = bufferutils_1.BufferWriter.withCapacity(
4 * this.ins.length,
);
this.ins.forEach((txIn) => {
sequenceHashWriter.writeUInt32(txIn.sequence);
});
hashSequence = bcrypto.hash256(sequenceHashWriter.end());
}
// Issuances
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
const sizeOfIssuances = this.ins.reduce(
(sum, txIn) => (txIn.issuance ? sum + getIssuanceSize(txIn) : sum + 1),
0,
);
const size = sizeOfIssuances === 0 ? this.ins.length : sizeOfIssuances;
const issuancesHashWriter = bufferutils_1.BufferWriter.withCapacity(size);
this.ins.forEach((txIn) => {
if (txIn.issuance) {
issuancesHashWriter.writeSlice(txIn.issuance.assetBlindingNonce);
issuancesHashWriter.writeSlice(txIn.issuance.assetEntropy);
issuancesHashWriter.writeSlice(txIn.issuance.assetAmount);
issuancesHashWriter.writeSlice(txIn.issuance.tokenAmount);
} else {
issuancesHashWriter.writeSlice(Buffer.of(0x00));
}
});
hashIssuances = bcrypto.hash256(issuancesHashWriter.end());
}
// Outputs
if (
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
(hashType & 0x1f) !== Transaction.SIGHASH_NONE
) {
const txOutsSize = this.outs.reduce(
(sum, output) =>
sum +
output.asset.length +
output.value.length +
output.nonce.length +
(0, bufferutils_1.varSliceSize)(output.script),
0,
);
const outputsHashWriter =
bufferutils_1.BufferWriter.withCapacity(txOutsSize);
this.outs.forEach((out) => {
outputsHashWriter.writeSlice(out.asset);
outputsHashWriter.writeSlice(out.value);
outputsHashWriter.writeSlice(out.nonce);
outputsHashWriter.writeVarSlice(out.script);
});
hashOutputs = bcrypto.hash256(outputsHashWriter.end());
} else if (
(hashType & 0x1f) === Transaction.SIGHASH_SINGLE &&
inIndex < this.outs.length
) {
const output = this.outs[inIndex];
const size =
output.asset.length +
output.value.length +
output.nonce.length +
(0, bufferutils_1.varSliceSize)(output.script);
const outputsHashWriter = bufferutils_1.BufferWriter.withCapacity(size);
outputsHashWriter.writeSlice(output.asset);
outputsHashWriter.writeSlice(output.value);
outputsHashWriter.writeSlice(output.nonce);
outputsHashWriter.writeVarSlice(output.script);
hashOutputs = bcrypto.hash256(outputsHashWriter.end());
}
const input = this.ins[inIndex];
const hasIssuance = input.issuance !== undefined;
const bufferSize =
4 + // version
hashPrevouts.length +
hashSequence.length +
hashIssuances.length +
input.hash.length +
4 + // input.index
(0, bufferutils_1.varSliceSize)(prevOutScript) +
value.length +
4 + // input.sequence
hashOutputs.length +
getIssuanceSize(input) +
4 + // locktime
4; // hashType
const sigWriter = bufferutils_1.BufferWriter.withCapacity(bufferSize);
sigWriter.writeUInt32(this.version);
sigWriter.writeSlice(hashPrevouts);
sigWriter.writeSlice(hashSequence);
sigWriter.writeSlice(hashIssuances);
sigWriter.writeSlice(input.hash);
sigWriter.writeUInt32(input.index);
sigWriter.writeVarSlice(prevOutScript);
sigWriter.writeSlice(value);
sigWriter.writeUInt32(input.sequence);
if (hasIssuance) {
sigWriter.writeSlice(input.issuance.assetBlindingNonce);
sigWriter.writeSlice(input.issuance.assetEntropy);
sigWriter.writeSlice(input.issuance.assetAmount);
sigWriter.writeSlice(input.issuance.tokenAmount);
}
sigWriter.writeSlice(hashOutputs);
sigWriter.writeUInt32(this.locktime);
sigWriter.writeUInt32(hashType);
return bcrypto.hash256(sigWriter.end());
}
getHash(forWitness) {
// wtxid for coinbase is always 32 bytes of 0x00
if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0);
return bcrypto.hash256(
this.__toBuffer(undefined, undefined, forWitness, true),
);
}
getId() {
// transaction hash's are displayed in reverse order
return (0, bufferutils_1.reverseBuffer)(this.getHash(false)).toString(
'hex',
);
}
toBuffer(buffer, initialOffset) {
return this.__toBuffer(buffer, initialOffset, true, false);
}
toHex() {
return this.toBuffer(undefined, undefined).toString('hex');
}
setInputScript(index, scriptSig) {
typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.ins[index].script = scriptSig;
}
setWitness(index, witness) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
this.ins[index].witness = witness;
}
setPeginWitness(index, peginWitness) {
typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
this.ins[index].peginWitness = peginWitness;
}
setInputIssuanceRangeProof(index, issuanceRangeProof) {
typeforce(types.tuple(types.Buffer), arguments);
if (this.ins[index].issuance === undefined)
throw new Error('Issuance not set for input #' + index);
this.ins[index].issuanceRangeProof = issuanceRangeProof;
}
setInputInflationRangeProof(index, inflationRangeProof) {
typeforce(types.tuple(types.Buffer), arguments);
if (this.ins[index].issuance === undefined)
throw new Error('Issuance not set for input #' + index);
this.ins[index].inflationRangeProof = inflationRangeProof;
}
setOutputNonce(index, nonce) {
typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.outs[index].nonce = nonce;
}
setOutputRangeProof(index, proof) {
typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.outs[index].rangeProof = proof;
}
setOutputSurjectionProof(index, proof) {
typeforce(types.tuple(types.Number, types.Buffer), arguments);
this.outs[index].surjectionProof = proof;
}
__byteLength(_ALLOW_WITNESS, forSignature) {
const extraByte = forSignature ? 0 : 1;
let size =
8 +
extraByte +
bufferutils_1.varuint.encodingLength(this.ins.length) +
bufferutils_1.varuint.encodingLength(this.outs.length);
for (const txIn of this.ins) {
size += 40 + (0, bufferutils_1.varSliceSize)(txIn.script);
if (txIn.issuance) {
size +=
64 +
txIn.issuance.assetAmount.length +
txIn.issuance.tokenAmount.length;
}
}
for (const txOut of this.outs) {
size +=
txOut.asset.length +
txOut.value.length +
txOut.nonce.length +
(0, bufferutils_1.varSliceSize)(txOut.script);
}
if (_ALLOW_WITNESS && this.hasWitnesses()) {
for (const txIn of this.ins) {
size += (0, bufferutils_1.varSliceSize)(txIn.issuanceRangeProof);
size += (0, bufferutils_1.varSliceSize)(txIn.inflationRangeProof);
size += bufferutils_1.varuint.encodingLength(txIn.witness.length);
for (const wit of txIn.witness) {
size += (0, bufferutils_1.varSliceSize)(wit);
}
size += bufferutils_1.varuint.encodingLength(
(txIn.peginWitness || []).length,
);
for (const wit of txIn.peginWitness || []) {
size += (0, bufferutils_1.varSliceSize)(wit);
}
}
for (const txOut of this.outs) {
size += (0, bufferutils_1.varSliceSize)(txOut.surjectionProof);
size += (0, bufferutils_1.varSliceSize)(txOut.rangeProof);
}
}
return size;
}
__toBuffer(
buffer,
initialOffset,
_ALLOW_WITNESS,
forceZeroFlag,
forSignature,
) {
if (!buffer)
buffer = Buffer.allocUnsafe(
this.__byteLength(_ALLOW_WITNESS, forSignature),
);
const bufferWriter = new bufferutils_1.BufferWriter(
buffer,
initialOffset || 0,
);
bufferWriter.writeInt32(this.version);
const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
if (!forSignature) {
let flags = 0;
if (hasWitnesses && !forceZeroFlag) {
flags |= Transaction.ADVANCED_TRANSACTION_FLAG;
}
bufferWriter.writeUInt8(flags);
}
bufferWriter.writeVarInt(this.ins.length);
this.ins.forEach((txIn) => {
bufferWriter.writeSlice(txIn.hash);
let prevIndex = txIn.index;
if (txIn.issuance) {
prevIndex = (prevIndex | OUTPOINT_ISSUANCE_FLAG) >>> 0;
}
if (txIn.isPegin) {
prevIndex = (prevIndex | OUTPOINT_PEGIN_FLAG) >>> 0;
}
bufferWriter.writeUInt32(prevIndex);
bufferWriter.writeVarSlice(txIn.script);
bufferWriter.writeUInt32(txIn.sequence);
if (txIn.issuance) {
bufferWriter.writeSlice(txIn.issuance.assetBlindingNonce);
bufferWriter.writeSlice(txIn.issuance.assetEntropy);
bufferWriter.writeSlice(txIn.issuance.assetAmount);
bufferWriter.writeSlice(txIn.issuance.tokenAmount);
}
});
bufferWriter.writeVarInt(this.outs.length);
this.outs.forEach((txOut) => {
// if we are serializing a confidential output for producing a signature,
// we must exclude the confidential value from the serialization and
// use the satoshi 0 value instead, as done for typical bitcoin witness signatures.
const val = forSignature && hasWitnesses ? Buffer.alloc(1) : txOut.value;
bufferWriter.writeSlice(txOut.asset);
bufferWriter.writeSlice(val);
bufferWriter.writeSlice(txOut.nonce);
if (forSignature && hasWitnesses) bufferWriter.writeUInt64(0);
bufferWriter.writeVarSlice(txOut.script);
});
bufferWriter.writeUInt32(this.locktime);
if (!forSignature && hasWitnesses) {
this.ins.forEach((input) => {
bufferWriter.writeConfidentialInFields(input);
});
this.outs.forEach((output) => {
bufferWriter.writeConfidentialOutFields(output);
});
}
// avoid slicing unless necessary
if (initialOffset !== undefined)
return buffer.slice(initialOffset, bufferWriter.offset);
return buffer;
}
}
exports.Transaction = Transaction;
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
Transaction.SIGHASH_DEFAULT = 0x00;
Transaction.SIGHASH_ALL = 0x01;
Transaction.SIGHASH_NONE = 0x02;
Transaction.SIGHASH_SINGLE = 0x03;
Transaction.SIGHASH_ANYONECANPAY = 0x80;
Transaction.SIGHASH_OUTPUT_MASK = 0x03;
Transaction.SIGHASH_INPUT_MASK = 0x80;
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
function getOutputWitnessesSHA256(outs) {
const outProofsSize = (o) =>
(0, bufferutils_1.varSliceSize)(o.rangeProof || Buffer.alloc(0)) +
(0, bufferutils_1.varSliceSize)(o.surjectionProof || Buffer.alloc(0));
const size = outs.reduce((sum, o) => sum + outProofsSize(o), 0);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(size);
for (const out of outs) {
bufferWriter.writeVarSlice(out.surjectionProof || Buffer.of(0x00));
bufferWriter.writeVarSlice(out.rangeProof || Buffer.of(0x00));
}
return bcrypto.sha256(bufferWriter.end());
}
function getIssuanceProofsSHA256(ins) {
const inProofsSize = (i) =>
(0, bufferutils_1.varSliceSize)(i.issuanceRangeProof || Buffer.alloc(1)) +
(0, bufferutils_1.varSliceSize)(i.inflationRangeProof || Buffer.alloc(1));
const size = ins.reduce((sum, i) => sum + inProofsSize(i), 0);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(size);
for (const input of ins) {
bufferWriter.writeVarSlice(input.issuanceRangeProof || Buffer.of(0x00));
bufferWriter.writeVarSlice(input.inflationRangeProof || Buffer.of(0x00));
}
return bcrypto.sha256(bufferWriter.end());
}
function getSpentAssetsAmountsSHA256(outs) {
const size = outs.reduce(
(sum, o) => sum + o.asset.length + o.value.length,
0,
);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(size);
for (const out of outs) {
bufferWriter.writeSlice(out.asset);
bufferWriter.writeSlice(out.value);
}
return bcrypto.sha256(bufferWriter.end());
}
function getInputFlag(input) {
const hasIssuance = input.issuance !== undefined;
return (
(hasIssuance ? OUTPOINT_ISSUANCE_FLAG >>> 24 : 0) |
(input.isPegin ? OUTPOINT_PEGIN_FLAG >>> 24 : 0)
);
}
function getOutpointFlagsSHA256(ins) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(ins.length);
for (const input of ins) {
bufferWriter.writeUInt8(getInputFlag(input));
}
return bcrypto.sha256(bufferWriter.end());
}
function getIssuanceSize(txIn) {
if (txIn.issuance) {
return (
txIn.issuance.assetBlindingNonce.length +
txIn.issuance.assetEntropy.length +
txIn.issuance.assetAmount.length +
txIn.issuance.tokenAmount.length
);
}
return 0;
}
function getPrevoutsSHA256(inputs) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
inputs.length * (32 + 4),
);
for (const i of inputs) {
bufferWriter.writeSlice(i.hash);
bufferWriter.writeUInt32(i.index);
}
return bcrypto.sha256(bufferWriter.end());
}
function getPrevoutScriptsSHA256(scripts) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
scripts.map(bufferutils_1.varSliceSize).reduce((a, b) => a + b),
);
scripts.forEach((prevOutScript) => bufferWriter.writeVarSlice(prevOutScript));
return bcrypto.sha256(bufferWriter.end());
}
function getSequenceSHA256(inputs) {
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(
4 * inputs.length,
);
inputs.forEach((txIn) => bufferWriter.writeUInt32(txIn.sequence));
return bcrypto.sha256(bufferWriter.end());
}
function getIssuanceSHA256(inputs) {
const sizeOfIssuances = inputs.reduce(
(sum, txIn) => (txIn.issuance ? sum + getIssuanceSize(txIn) : sum + 1),
0,
);
const size = sizeOfIssuances === 0 ? inputs.length : sizeOfIssuances;
const writer = bufferutils_1.BufferWriter.withCapacity(size);
inputs.forEach((txIn) => {
if (txIn.issuance) {
writer.writeSlice(txIn.issuance.assetBlindingNonce);
writer.writeSlice(txIn.issuance.assetEntropy);
writer.writeSlice(txIn.issuance.assetAmount);
writer.writeSlice(txIn.issuance.tokenAmount);
} else {
writer.writeSlice(Buffer.of(0x00));
}
});
return bcrypto.sha256(writer.end());
}
function getOutputsSHA256(outputs) {
const txOutsSize = outputs.reduce(
(sum, output) =>
sum +
output.asset.length +
output.value.length +
output.nonce.length +
(0, bufferutils_1.varSliceSize)(output.script),
0,
);
const bufferWriter = bufferutils_1.BufferWriter.withCapacity(txOutsSize);
outputs.forEach((out) => {
bufferWriter.writeSlice(out.asset);
bufferWriter.writeSlice(out.value);
bufferWriter.writeSlice(out.nonce);
bufferWriter.writeVarSlice(out.script);
});
return bcrypto.sha256(bufferWriter.end());
}