UNPKG

@trezor/utxo-lib

Version:
410 lines (409 loc) 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fromConstructor = fromConstructor; exports.fromBuffer = fromBuffer; const tslib_1 = require("tslib"); const blakejs_1 = require("blakejs"); const varuint = tslib_1.__importStar(require("varuint-bitcoin")); const bufferutils_1 = require("../bufferutils"); const base_1 = require("./base"); const crypto_1 = require("../crypto"); const ZCASH_JOINSPLITS_SUPPORT_VERSION = 2; const ZCASH_OVERWINTER_VERSION = 3; const ZCASH_SAPLING_VERSION = 4; const ZCASH_NU5_VERSION = 5; const ZCASH_NUM_JOINSPLITS_INPUTS = 2; const ZCASH_NUM_JOINSPLITS_OUTPUTS = 2; const ZCASH_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16; const ZCASH_G1_PREFIX_MASK = 0x02; const ZCASH_G2_PREFIX_MASK = 0x0a; function byteLength(tx) { const overwinterSize = tx.version >= ZCASH_OVERWINTER_VERSION ? 4 + 4 : 0; const txSpecific = tx.specific; const getJoinSplitsSize = () => { if (tx.version < ZCASH_JOINSPLITS_SUPPORT_VERSION || tx.version >= ZCASH_NU5_VERSION) return 0; const joinSplitsLen = txSpecific.joinsplits.length; if (joinSplitsLen < 1) return (0, bufferutils_1.varIntSize)(joinSplitsLen); return (0, bufferutils_1.varIntSize)(joinSplitsLen) + (tx.version >= ZCASH_SAPLING_VERSION ? 1698 * joinSplitsLen : 1802 * joinSplitsLen) + 32 + 64; }; const saplingSize = tx.version === ZCASH_SAPLING_VERSION ? 8 + varuint.encodingLength(txSpecific.vShieldedSpend.length) + 384 * txSpecific.vShieldedSpend.length + varuint.encodingLength(txSpecific.vShieldedOutput.length) + 948 * txSpecific.vShieldedOutput.length + (txSpecific.vShieldedSpend.length + txSpecific.vShieldedOutput.length > 0 ? 64 : 0) : 0; const NU5size = tx.version >= ZCASH_NU5_VERSION ? 4 + 1 + 1 + 1 : 0; return 4 + varuint.encodingLength(tx.ins.length) + varuint.encodingLength(tx.outs.length) + tx.ins.reduce((sum, input) => sum + 40 + (0, base_1.varSliceSize)(input.script), 0) + tx.outs.reduce((sum, output) => sum + 8 + (0, base_1.varSliceSize)(output.script), 0) + 4 + overwinterSize + getJoinSplitsSize() + saplingSize + NU5size; } function toBuffer(tx, buffer, initialOffset) { if (!buffer) buffer = Buffer.allocUnsafe(byteLength(tx)); const bufferWriter = new bufferutils_1.BufferWriter(buffer, initialOffset || 0); const txSpecific = tx.specific; if (tx.version >= ZCASH_OVERWINTER_VERSION) { const mask = txSpecific.overwintered ? 1 : 0; bufferWriter.writeInt32(tx.version | mask << 31); bufferWriter.writeUInt32(txSpecific.versionGroupId); } else { bufferWriter.writeInt32(tx.version); } if (tx.version >= ZCASH_NU5_VERSION) { bufferWriter.writeUInt32(txSpecific.consensusBranchId); bufferWriter.writeUInt32(tx.locktime); bufferWriter.writeUInt32(tx.expiry); } bufferWriter.writeVarInt(tx.ins.length); tx.ins.forEach(txIn => { bufferWriter.writeSlice(txIn.hash); bufferWriter.writeUInt32(txIn.index); bufferWriter.writeVarSlice(txIn.script); bufferWriter.writeUInt32(txIn.sequence); }); bufferWriter.writeVarInt(tx.outs.length); tx.outs.forEach(txOut => { bufferWriter.writeUInt64(txOut.value); bufferWriter.writeVarSlice(txOut.script); }); if (tx.version < ZCASH_NU5_VERSION) { bufferWriter.writeUInt32(tx.locktime); if (tx.version >= ZCASH_OVERWINTER_VERSION) { bufferWriter.writeUInt32(tx.expiry); } } if (tx.version === ZCASH_SAPLING_VERSION) { bufferWriter.writeInt64(txSpecific.valueBalance); bufferWriter.writeVarInt(txSpecific.vShieldedSpend.length); txSpecific.vShieldedSpend.forEach(shieldedSpend => { bufferWriter.writeSlice(shieldedSpend.cv); bufferWriter.writeSlice(shieldedSpend.anchor); bufferWriter.writeSlice(shieldedSpend.nullifier); bufferWriter.writeSlice(shieldedSpend.rk); bufferWriter.writeSlice(shieldedSpend.zkproof.sA); bufferWriter.writeSlice(shieldedSpend.zkproof.sB); bufferWriter.writeSlice(shieldedSpend.zkproof.sC); bufferWriter.writeSlice(shieldedSpend.spendAuthSig); }); bufferWriter.writeVarInt(txSpecific.vShieldedOutput.length); txSpecific.vShieldedOutput.forEach(shieldedOutput => { bufferWriter.writeSlice(shieldedOutput.cv); bufferWriter.writeSlice(shieldedOutput.cmu); bufferWriter.writeSlice(shieldedOutput.ephemeralKey); bufferWriter.writeSlice(shieldedOutput.encCiphertext); bufferWriter.writeSlice(shieldedOutput.outCiphertext); bufferWriter.writeSlice(shieldedOutput.zkproof.sA); bufferWriter.writeSlice(shieldedOutput.zkproof.sB); bufferWriter.writeSlice(shieldedOutput.zkproof.sC); }); } function writeCompressedG1(i) { bufferWriter.writeUInt8(ZCASH_G1_PREFIX_MASK | i.yLsb); bufferWriter.writeSlice(i.x); } function writeCompressedG2(i) { bufferWriter.writeUInt8(ZCASH_G2_PREFIX_MASK | i.yLsb); bufferWriter.writeSlice(i.x); } if (tx.version >= ZCASH_JOINSPLITS_SUPPORT_VERSION && tx.version < ZCASH_NU5_VERSION) { bufferWriter.writeVarInt(txSpecific.joinsplits.length); txSpecific.joinsplits.forEach(joinsplit => { bufferWriter.writeUInt64(joinsplit.vpubOld); bufferWriter.writeUInt64(joinsplit.vpubNew); bufferWriter.writeSlice(joinsplit.anchor); joinsplit.nullifiers.forEach(nullifier => { bufferWriter.writeSlice(nullifier); }); joinsplit.commitments.forEach(nullifier => { bufferWriter.writeSlice(nullifier); }); bufferWriter.writeSlice(joinsplit.ephemeralKey); bufferWriter.writeSlice(joinsplit.randomSeed); joinsplit.macs.forEach(nullifier => { bufferWriter.writeSlice(nullifier); }); if (joinsplit.zkproof.type === 'sapling') { bufferWriter.writeSlice(joinsplit.zkproof.sA); bufferWriter.writeSlice(joinsplit.zkproof.sB); bufferWriter.writeSlice(joinsplit.zkproof.sC); } else { writeCompressedG1(joinsplit.zkproof.gA); writeCompressedG1(joinsplit.zkproof.gAPrime); writeCompressedG2(joinsplit.zkproof.gB); writeCompressedG1(joinsplit.zkproof.gBPrime); writeCompressedG1(joinsplit.zkproof.gC); writeCompressedG1(joinsplit.zkproof.gCPrime); writeCompressedG1(joinsplit.zkproof.gK); writeCompressedG1(joinsplit.zkproof.gH); } joinsplit.ciphertexts.forEach(ciphertext => { bufferWriter.writeSlice(ciphertext); }); }); if (txSpecific.joinsplits.length > 0) { bufferWriter.writeSlice(txSpecific.joinsplitPubkey); bufferWriter.writeSlice(txSpecific.joinsplitSig); } } if (tx.version >= ZCASH_SAPLING_VERSION && txSpecific.vShieldedSpend.length + txSpecific.vShieldedOutput.length > 0) { bufferWriter.writeSlice(txSpecific.bindingSig); } if (tx.version === ZCASH_NU5_VERSION) { bufferWriter.writeVarInt(0); bufferWriter.writeVarInt(0); bufferWriter.writeUInt8(0x00); } if (initialOffset !== undefined) return buffer.subarray(initialOffset, bufferWriter.offset); return buffer; } function getExtraData(tx) { if (tx.version < ZCASH_JOINSPLITS_SUPPORT_VERSION || tx.version >= ZCASH_NU5_VERSION) return; const offset = 4 + (tx.version >= ZCASH_OVERWINTER_VERSION ? 8 : 0) + varuint.encodingLength(tx.ins.length) + varuint.encodingLength(tx.outs.length) + tx.ins.reduce((sum, input) => sum + 40 + (0, base_1.varSliceSize)(input.script), 0) + tx.outs.reduce((sum, output) => sum + 8 + (0, base_1.varSliceSize)(output.script), 0) + 4; return tx.toBuffer().subarray(offset); } function getBlake2bDigestHash(buffer, personalization) { const personalizedBuffer = typeof personalization === 'string' ? Buffer.from(personalization) : personalization; const hash = (0, blakejs_1.blake2b)(buffer, undefined, 32, undefined, personalizedBuffer); return Buffer.from(hash); } function getHeaderDigest(tx) { const mask = tx.specific.overwintered ? 1 : 0; const writer = new bufferutils_1.BufferWriter(Buffer.alloc(4 * 5)); writer.writeInt32(tx.version | mask << 31); writer.writeUInt32(tx.specific.versionGroupId); writer.writeUInt32(tx.specific.consensusBranchId); writer.writeUInt32(tx.locktime); writer.writeUInt32(tx.expiry); return getBlake2bDigestHash(writer.buffer, 'ZTxIdHeadersHash'); } function getPrevoutsDigest(ins) { const bufferWriter = new bufferutils_1.BufferWriter(Buffer.allocUnsafe(36 * ins.length)); ins.forEach(txIn => { bufferWriter.writeSlice(txIn.hash); bufferWriter.writeUInt32(txIn.index); }); return getBlake2bDigestHash(bufferWriter.buffer, 'ZTxIdPrevoutHash'); } function getSequenceDigest(ins) { const bufferWriter = new bufferutils_1.BufferWriter(Buffer.allocUnsafe(4 * ins.length)); ins.forEach(txIn => { bufferWriter.writeUInt32(txIn.sequence); }); return getBlake2bDigestHash(bufferWriter.buffer, 'ZTxIdSequencHash'); } function getOutputsDigest(outs) { const txOutsSize = outs.reduce((sum, output) => sum + 8 + (0, base_1.varSliceSize)(output.script), 0); const bufferWriter = new bufferutils_1.BufferWriter(Buffer.allocUnsafe(txOutsSize)); outs.forEach(out => { bufferWriter.writeUInt64(out.value); bufferWriter.writeVarSlice(out.script); }); return getBlake2bDigestHash(bufferWriter.buffer, 'ZTxIdOutputsHash'); } function getTransparentDigest(tx) { let buffer; if (tx.ins.length || tx.outs.length) { const writer = new bufferutils_1.BufferWriter(Buffer.alloc(32 * 3)); writer.writeSlice(getPrevoutsDigest(tx.ins)); writer.writeSlice(getSequenceDigest(tx.ins)); writer.writeSlice(getOutputsDigest(tx.outs)); buffer = writer.buffer; } else { buffer = Buffer.of(); } return getBlake2bDigestHash(buffer, 'ZTxIdTranspaHash'); } function getHash(tx, _forWitness = false) { if (tx.version < ZCASH_NU5_VERSION) { return (0, crypto_1.hash256)(toBuffer(tx)); } const writer = new bufferutils_1.BufferWriter(Buffer.alloc(32 * 4)); writer.writeSlice(getHeaderDigest(tx)); writer.writeSlice(getTransparentDigest(tx)); writer.writeSlice(getBlake2bDigestHash(Buffer.of(), 'ZTxIdSaplingHash')); writer.writeSlice(getBlake2bDigestHash(Buffer.of(), 'ZTxIdOrchardHash')); const personalizationTag = 'ZcashTxHash_'; const personalization = new bufferutils_1.BufferWriter(Buffer.alloc(personalizationTag.length + 4)); personalization.writeSlice(Buffer.from(personalizationTag)); personalization.writeUInt32(tx.specific.consensusBranchId); return getBlake2bDigestHash(writer.buffer, personalization.buffer); } function fromConstructor(options) { const tx = new base_1.TransactionBase(options); tx.specific = tx.specific || { type: 'zcash', joinsplits: [], joinsplitPubkey: base_1.EMPTY_SCRIPT, joinsplitSig: base_1.EMPTY_SCRIPT, overwintered: 0, versionGroupId: 0, valueBalance: 0, vShieldedSpend: [], vShieldedOutput: [], bindingSig: base_1.EMPTY_SCRIPT, consensusBranchId: 0 }; tx.byteLength = byteLength.bind(null, tx); tx.toBuffer = toBuffer.bind(null, tx); tx.getExtraData = getExtraData.bind(null, tx); tx.getHash = getHash.bind(null, tx); return tx; } function fromBuffer(buffer, options) { const bufferReader = new bufferutils_1.BufferReader(buffer); const tx = fromConstructor(options); const txSpecific = tx.specific; tx.version = bufferReader.readInt32(); txSpecific.overwintered = tx.version >>> 31; tx.version &= 0x07fffffff; if (tx.version >= ZCASH_OVERWINTER_VERSION) { txSpecific.versionGroupId = bufferReader.readUInt32(); } if (tx.version >= ZCASH_NU5_VERSION) { txSpecific.consensusBranchId = bufferReader.readUInt32(); tx.locktime = bufferReader.readUInt32(); tx.expiry = bufferReader.readUInt32(); } const vinLen = bufferReader.readVarInt(); for (let i = 0; i < vinLen; ++i) { tx.ins.push({ hash: bufferReader.readSlice(32), index: bufferReader.readUInt32(), script: bufferReader.readVarSlice(), sequence: bufferReader.readUInt32(), witness: [] }); } const voutLen = bufferReader.readVarInt(); for (let i = 0; i < voutLen; ++i) { tx.outs.push({ value: bufferReader.readUInt64String(), script: bufferReader.readVarSlice() }); } if (tx.version < ZCASH_NU5_VERSION) { tx.locktime = bufferReader.readUInt32(); tx.expiry = tx.version >= ZCASH_OVERWINTER_VERSION ? bufferReader.readUInt32() : 0; } function readCompressedG1() { const yLsb = bufferReader.readUInt8() & 1; const x = bufferReader.readSlice(32); return { x, yLsb }; } function readCompressedG2() { const yLsb = bufferReader.readUInt8() & 1; const x = bufferReader.readSlice(64); return { x, yLsb }; } function readSaplingZKProof() { return { type: 'sapling', sA: bufferReader.readSlice(48), sB: bufferReader.readSlice(96), sC: bufferReader.readSlice(48) }; } function readZKProof() { if (tx.version >= ZCASH_SAPLING_VERSION) { return readSaplingZKProof(); } return { type: 'joinsplit', gA: readCompressedG1(), gAPrime: readCompressedG1(), gB: readCompressedG2(), gBPrime: readCompressedG1(), gC: readCompressedG1(), gCPrime: readCompressedG1(), gK: readCompressedG1(), gH: readCompressedG1() }; } if (tx.version === ZCASH_SAPLING_VERSION) { txSpecific.valueBalance = bufferReader.readInt64(); const nShieldedSpend = bufferReader.readVarInt(); for (let i = 0; i < nShieldedSpend; ++i) { txSpecific.vShieldedSpend.push({ cv: bufferReader.readSlice(32), anchor: bufferReader.readSlice(32), nullifier: bufferReader.readSlice(32), rk: bufferReader.readSlice(32), zkproof: readSaplingZKProof(), spendAuthSig: bufferReader.readSlice(64) }); } const nShieldedOutput = bufferReader.readVarInt(); for (let i = 0; i < nShieldedOutput; ++i) { txSpecific.vShieldedOutput.push({ cv: bufferReader.readSlice(32), cmu: bufferReader.readSlice(32), ephemeralKey: bufferReader.readSlice(32), encCiphertext: bufferReader.readSlice(580), outCiphertext: bufferReader.readSlice(80), zkproof: readSaplingZKProof() }); } } if (tx.version >= ZCASH_JOINSPLITS_SUPPORT_VERSION && tx.version < ZCASH_NU5_VERSION) { const joinSplitsLen = bufferReader.readVarInt(); for (let i = 0; i < joinSplitsLen; ++i) { let j; const vpubOld = bufferReader.readUInt64(); const vpubNew = bufferReader.readUInt64(); const anchor = bufferReader.readSlice(32); const nullifiers = []; for (j = 0; j < ZCASH_NUM_JOINSPLITS_INPUTS; j++) { nullifiers.push(bufferReader.readSlice(32)); } const commitments = []; for (j = 0; j < ZCASH_NUM_JOINSPLITS_OUTPUTS; j++) { commitments.push(bufferReader.readSlice(32)); } const ephemeralKey = bufferReader.readSlice(32); const randomSeed = bufferReader.readSlice(32); const macs = []; for (j = 0; j < ZCASH_NUM_JOINSPLITS_INPUTS; j++) { macs.push(bufferReader.readSlice(32)); } const zkproof = readZKProof(); const ciphertexts = []; for (j = 0; j < ZCASH_NUM_JOINSPLITS_OUTPUTS; j++) { ciphertexts.push(bufferReader.readSlice(ZCASH_NOTECIPHERTEXT_SIZE)); } txSpecific.joinsplits.push({ vpubOld, vpubNew, anchor, nullifiers, commitments, ephemeralKey, randomSeed, macs, zkproof, ciphertexts }); } if (joinSplitsLen > 0) { txSpecific.joinsplitPubkey = bufferReader.readSlice(32); txSpecific.joinsplitSig = bufferReader.readSlice(64); } } if (tx.version >= ZCASH_SAPLING_VERSION && txSpecific.vShieldedSpend.length + txSpecific.vShieldedOutput.length > 0) { txSpecific.bindingSig = bufferReader.readSlice(64); } if (tx.version === ZCASH_NU5_VERSION) { if (bufferReader.readVarInt() !== 0) { throw Error('Unexpected vSpendsSapling vector'); } if (bufferReader.readVarInt() !== 0) { throw Error('Unexpected vOutputsSapling vector'); } if (bufferReader.readUInt8() !== 0x00) { throw Error('Unexpected orchard byte'); } } if (options.nostrict) return tx; if (bufferReader.offset !== buffer.length) throw new Error('Transaction has unexpected data'); return tx; } //# sourceMappingURL=zcash.js.map