UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

336 lines (335 loc) 14.4 kB
import { Signature } from '../crypto/signature.js'; import { Script, empty } from '../script.js'; import { Output } from './output.js'; import { BufferReader } from '../encoding/bufferreader.js'; import { BufferWriter } from '../encoding/bufferwriter.js'; import { BN } from '../crypto/bn.js'; import { Hash } from '../crypto/hash.js'; import { ECDSA } from '../crypto/ecdsa.js'; import { Schnorr } from '../crypto/schnorr.js'; import { Preconditions } from '../util/preconditions.js'; import { BufferUtil } from '../util/buffer.js'; import { Interpreter } from '../script/interpreter.js'; import { Transaction } from './transaction.js'; import { Input } from './input.js'; const SIGHASH_SINGLE_BUG_CONST = '0000000000000000000000000000000000000000000000000000000000000001'; const BITS_64_ON_CONST = 'ffffffffffffffff'; const NULL_HASH = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); const SIGHASH_ALGORITHM_MASK = 0x60; const DEFAULT_SIGN_FLAGS_CONST = 1 << 16; function GetForkId() { return 0; } function getMerkleRoot(hashes) { if (hashes.length === 0) { return { root: NULL_HASH, height: 0 }; } let currentHashes = [...hashes]; let height = 1; while (currentHashes.length > 1) { height++; const newHashes = []; for (let i = 0; i < currentHashes.length; i += 2) { const left = currentHashes[i]; const right = i + 1 < currentHashes.length ? currentHashes[i + 1] : NULL_HASH; const combined = Buffer.concat([left, right]); const pairHash = Hash.sha256sha256(combined); newHashes.push(pairHash); } currentHashes = newHashes; } return { root: currentHashes[0], height }; } function sighashForLotus(transaction, sighashType, inputNumber, spentOutputs, executedScriptHash, codeseparatorPos = 0xffffffff) { Preconditions.checkArgument(spentOutputs.length === transaction.inputs.length, 'Must provide spent output for each input'); Preconditions.checkArgument(inputNumber < transaction.inputs.length, 'Input index out of range'); const baseType = sighashType & 0x03; const unusedBits = sighashType & 0x1c; if (baseType === 0 || unusedBits !== 0) { throw new Error('Invalid sighash type for SIGHASH_LOTUS'); } const input = transaction.inputs[inputNumber]; const writer = new BufferWriter(); writer.writeUInt32LE(sighashType >>> 0); const spendType = executedScriptHash ? 2 : 0; const inputHashWriter = new BufferWriter(); inputHashWriter.writeUInt8(spendType); inputHashWriter.writeReverse(input.prevTxId); inputHashWriter.writeUInt32LE(input.outputIndex); inputHashWriter.writeUInt32LE(input.sequenceNumber); const spentOutput = spentOutputs[inputNumber]; inputHashWriter.writeUInt64LEBN(new BN(spentOutput.satoshis)); inputHashWriter.writeVarLengthBuffer(spentOutput.scriptBuffer); const inputHash = Hash.sha256sha256(inputHashWriter.toBuffer()); writer.write(inputHash); if (executedScriptHash) { Preconditions.checkArgument(executedScriptHash.length === 32, 'executed_script_hash must be 32 bytes'); writer.writeUInt32LE(codeseparatorPos); writer.write(executedScriptHash); } if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { writer.writeUInt32LE(inputNumber); const spentOutputHashes = spentOutputs.map(output => { const w = new BufferWriter(); w.writeUInt64LEBN(new BN(output.satoshis)); w.writeVarLengthBuffer(output.scriptBuffer); return Hash.sha256sha256(w.toBuffer()); }); const spentOutputsMerkle = getMerkleRoot(spentOutputHashes); writer.write(spentOutputsMerkle.root); const totalInputAmount = spentOutputs.reduce((sum, output) => sum + output.satoshis, 0); writer.writeUInt64LEBN(new BN(totalInputAmount)); } if (baseType === Signature.SIGHASH_ALL) { const totalOutputAmount = transaction.outputs.reduce((sum, output) => sum + output.satoshis, 0); writer.writeUInt64LEBN(new BN(totalOutputAmount)); } writer.writeUInt32LE(transaction.version || 2); if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { const inputHashes = transaction.inputs.map(inp => { const w = new BufferWriter(); w.writeReverse(inp.prevTxId); w.writeUInt32LE(inp.outputIndex); w.writeUInt32LE(inp.sequenceNumber); return Hash.sha256sha256(w.toBuffer()); }); const inputsMerkle = getMerkleRoot(inputHashes); writer.write(inputsMerkle.root); writer.writeUInt8(inputsMerkle.height); } if (baseType === Signature.SIGHASH_SINGLE) { if (inputNumber >= transaction.outputs.length) { throw new Error('SIGHASH_SINGLE: no corresponding output for input'); } const w = new BufferWriter(); transaction.outputs[inputNumber].toBufferWriter(w); const outputHash = Hash.sha256sha256(w.toBuffer()); writer.write(outputHash); } if (baseType === Signature.SIGHASH_ALL) { const outputHashes = transaction.outputs.map(output => { const w = new BufferWriter(); output.toBufferWriter(w); return Hash.sha256sha256(w.toBuffer()); }); const outputsMerkle = getMerkleRoot(outputHashes); writer.write(outputsMerkle.root); writer.writeUInt8(outputsMerkle.height); } writer.writeUInt32LE(transaction.nLockTime || 0); return Hash.sha256sha256(writer.toBuffer()); } function sighashForForkId(transaction, sighashType, inputNumber, subscript, satoshisBN) { const input = transaction.inputs[inputNumber]; Preconditions.checkArgument(satoshisBN instanceof BN, 'For ForkId=0 signatures, satoshis or complete input must be provided'); let hashPrevouts = BufferUtil.emptyBuffer(32); let hashSequence = BufferUtil.emptyBuffer(32); let hashOutputs = BufferUtil.emptyBuffer(32); if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { hashPrevouts = GetPrevoutHash(transaction); } if (!(sighashType & Signature.SIGHASH_ANYONECANPAY) && (sighashType & 31) !== Signature.SIGHASH_SINGLE && (sighashType & 31) !== Signature.SIGHASH_NONE) { hashSequence = GetSequenceHash(transaction); } if ((sighashType & 31) !== Signature.SIGHASH_SINGLE && (sighashType & 31) !== Signature.SIGHASH_NONE) { hashOutputs = GetOutputsHash(transaction); } else if ((sighashType & 31) === Signature.SIGHASH_SINGLE && inputNumber < transaction.outputs.length) { hashOutputs = GetOutputsHash(transaction, inputNumber); } const writer = new BufferWriter(); writer.writeUInt32LE(transaction.version || 2); writer.write(hashPrevouts); writer.write(hashSequence); writer.writeReverse(input.prevTxId); writer.writeUInt32LE(input.outputIndex); writer.writeVarintNum(subscript.toBuffer().length); writer.write(subscript.toBuffer()); writer.writeUInt64LEBN(satoshisBN); writer.writeUInt32LENumber(input.sequenceNumber); writer.write(hashOutputs); writer.writeUInt32LE(transaction.nLockTime || 0); writer.writeUInt32LE(sighashType >>> 0); const buf = writer.toBuffer(); const hash = Hash.sha256sha256(buf); return new BufferReader(hash).read(32); } function sighashLegacy(transaction, sighashType, inputNumber, subscript) { const input = transaction.inputs[inputNumber]; function getHash(w) { const buf = w.toBuffer(); return Hash.sha256sha256(buf); } const writer = new BufferWriter(); writer.writeUInt32LE(2); writer.writeVarintNum(transaction.inputs.length); for (let i = 0; i < transaction.inputs.length; i++) { const txInput = transaction.inputs[i]; writer.writeReverse(txInput.prevTxId); writer.writeUInt32LE(txInput.outputIndex); if (i === inputNumber) { writer.writeVarLengthBuffer(subscript.toBuffer()); } else { writer.writeVarintNum(0); } writer.writeUInt32LENumber(txInput.sequenceNumber); } writer.writeVarintNum(transaction.outputs.length); if ((sighashType & 31) !== Signature.SIGHASH_SINGLE && (sighashType & 31) !== Signature.SIGHASH_NONE) { for (const output of transaction.outputs) { output.toBufferWriter(writer); } } else if ((sighashType & 31) === Signature.SIGHASH_SINGLE && inputNumber < transaction.outputs.length) { transaction.outputs[inputNumber].toBufferWriter(writer); } writer.writeUInt32LE(transaction.nLockTime || 0); writer.writeUInt32LE(sighashType); return getHash(writer); } function sighash(transaction, sighashType, inputNumber, subscript, satoshisBN, flags) { if (flags === undefined) { flags = DEFAULT_SIGN_FLAGS_CONST; } const txcopy = Transaction.shallowCopy(transaction); subscript = new Script(subscript); if (flags & Interpreter.SCRIPT_ENABLE_REPLAY_PROTECTION) { const forkValue = sighashType >> 8; const newForkValue = 0xff0000 | (forkValue ^ 0xdead); sighashType = (newForkValue << 8) | (sighashType & 0xff); } const algorithmBits = sighashType & SIGHASH_ALGORITHM_MASK; if (algorithmBits === Signature.SIGHASH_LOTUS && flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) { const spentOutputs = transaction.spentOutputs; if (!spentOutputs || spentOutputs.length !== transaction.inputs.length) { throw new Error('SIGHASH_LOTUS requires spent outputs for all inputs (ensure all inputs have output information)'); } return sighashForLotus(txcopy, sighashType, inputNumber, spentOutputs); } if (algorithmBits === Signature.SIGHASH_FORKID && flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) { return sighashForForkId(txcopy, sighashType, inputNumber, subscript, satoshisBN); } subscript.removeCodeseparators(); for (let i = 0; i < txcopy.inputs.length; i++) { txcopy.inputs[i] = new Input({ prevTxId: txcopy.inputs[i].prevTxId, outputIndex: txcopy.inputs[i].outputIndex, sequenceNumber: txcopy.inputs[i].sequenceNumber, script: empty(), }); } txcopy.inputs[inputNumber] = new Input({ prevTxId: txcopy.inputs[inputNumber].prevTxId, outputIndex: txcopy.inputs[inputNumber].outputIndex, sequenceNumber: txcopy.inputs[inputNumber].sequenceNumber, script: subscript, }); if ((sighashType & 31) === Signature.SIGHASH_NONE || (sighashType & 31) === Signature.SIGHASH_SINGLE) { for (let i = 0; i < txcopy.inputs.length; i++) { if (i !== inputNumber) { txcopy.inputs[i].sequenceNumber = 0; } } } if ((sighashType & 31) === Signature.SIGHASH_NONE) { txcopy.outputs = []; } else if ((sighashType & 31) === Signature.SIGHASH_SINGLE) { if (inputNumber >= txcopy.outputs.length) { return Buffer.from(SIGHASH_SINGLE_BUG_CONST, 'hex'); } txcopy.outputs.length = inputNumber + 1; for (let i = 0; i < inputNumber; i++) { txcopy.outputs[i] = new Output({ satoshis: BN.fromBuffer(Buffer.from(BITS_64_ON_CONST, 'hex')), script: empty(), }); } } if (sighashType & Signature.SIGHASH_ANYONECANPAY) { txcopy.inputs = [txcopy.inputs[inputNumber]]; } const buf = new BufferWriter() .write(txcopy.toBuffer()) .writeInt32LE(sighashType >>> 0) .toBuffer(); const hash = Hash.sha256sha256(buf); return new BufferReader(hash).readReverse(32); } function sign(transaction, privateKey, sighashType, inputIndex, subscript, satoshisBN, flags, signingMethod) { const hashbuf = sighash(transaction, sighashType, inputIndex, subscript, satoshisBN, flags); signingMethod = signingMethod || 'ecdsa'; let sig; if (signingMethod === 'schnorr') { sig = Schnorr.sign(hashbuf, privateKey, 'big'); sig.nhashtype = sighashType; return sig; } else if (signingMethod === 'ecdsa') { sig = ECDSA.sign(hashbuf, privateKey, 'big'); sig.nhashtype = sighashType; return sig; } else { throw new Error('Invalid signing method. Must be "ecdsa" or "schnorr"'); } } function verify(transaction, signature, publicKey, inputIndex, subscript, satoshisBN, flags, signingMethod) { Preconditions.checkArgument(transaction !== undefined, 'Transaction is required'); Preconditions.checkArgument(signature !== undefined && signature.nhashtype !== undefined, 'Signature with nhashtype is required'); const hashbuf = sighash(transaction, signature.nhashtype, inputIndex, subscript, satoshisBN, flags); signingMethod = signingMethod || 'ecdsa'; if (signingMethod === 'schnorr') { return Schnorr.verify(hashbuf, signature, publicKey, 'big'); } else if (signingMethod === 'ecdsa') { return ECDSA.verify(hashbuf, signature, publicKey, 'big'); } else { throw new Error('Invalid signing method. Must be "ecdsa" or "schnorr"'); } } function GetPrevoutHash(tx) { const writer = new BufferWriter(); for (const input of tx.inputs) { writer.writeReverse(input.prevTxId); writer.writeUInt32LE(input.outputIndex); } const buf = writer.toBuffer(); return Hash.sha256sha256(buf); } function GetSequenceHash(tx) { const writer = new BufferWriter(); for (const input of tx.inputs) { writer.writeUInt32LENumber(input.sequenceNumber); } const buf = writer.toBuffer(); return Hash.sha256sha256(buf); } function GetOutputsHash(tx, n) { const writer = new BufferWriter(); if (n === undefined) { for (const output of tx.outputs) { output.toBufferWriter(writer); } } else { tx.outputs[n].toBufferWriter(writer); } const buf = writer.toBuffer(); return Hash.sha256sha256(buf); } export const DEFAULT_SIGN_FLAGS = DEFAULT_SIGN_FLAGS_CONST; export const SIGHASH_SINGLE_BUG = SIGHASH_SINGLE_BUG_CONST; export const BITS_64_ON = BITS_64_ON_CONST; export { sighash, sign, verify };