UNPKG

@muirglacier/jellyfish-transaction-signature

Version:

A collection of TypeScript + JavaScript tools and libraries for DeFi Blockchain developers to build decentralized finance for Bitcoin

210 lines 9.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionSigner = void 0; const jellyfish_crypto_1 = require("@muirglacier/jellyfish-crypto"); const smart_buffer_1 = require("smart-buffer"); const jellyfish_transaction_1 = require("@muirglacier/jellyfish-transaction"); function hashPrevouts(transaction, sigHashType) { if (sigHashType !== jellyfish_transaction_1.SIGHASH.ALL) { throw new Error('currently only SIGHASH.ALL is supported'); } const buffer = new smart_buffer_1.SmartBuffer(); for (const vin of transaction.vin) { const txid = Buffer.from(vin.txid, 'hex').reverse(); buffer.writeBuffer(txid); buffer.writeUInt32LE(vin.index); } return jellyfish_crypto_1.dSHA256(buffer.toBuffer()).toString('hex'); } function hashSequence(transaction, sigHashType) { if (sigHashType !== jellyfish_transaction_1.SIGHASH.ALL) { throw new Error('currently only SIGHASH.ALL is supported'); } const buffer = new smart_buffer_1.SmartBuffer(); for (const vin of transaction.vin) { buffer.writeUInt32LE(vin.sequence); } return jellyfish_crypto_1.dSHA256(buffer.toBuffer()).toString('hex'); } function hashOutputs(transaction, sigHashType) { if (sigHashType !== jellyfish_transaction_1.SIGHASH.ALL) { throw new Error('currently only SIGHASH.ALL is supported'); } const buffer = new smart_buffer_1.SmartBuffer(); transaction.vout.forEach(vout => (new jellyfish_transaction_1.CVoutV4(vout)).toBuffer(buffer)); return jellyfish_crypto_1.dSHA256(buffer.toBuffer()).toString('hex'); } /** * * The witness must consist of exactly 2 items. * The '0' in scriptPubKey indicates the following push is a version 0 witness program. * The length of 20 indicates that it is a P2WPKH type. * * @param {SignInputOption} signInputOption to check is is V0 P2WPKH */ function isV0P2WPKH(signInputOption) { return __awaiter(this, void 0, void 0, function* () { const stack = signInputOption.prevout.script.stack; if (stack.length !== 2) { return false; } if (stack[0].type !== 'OP_0') { return false; } if (stack[1].type !== 'OP_PUSHDATA') { return false; } if (stack[1].length() !== 20) { return false; } const pubkey = yield signInputOption.publicKey(); const pubkeyHashHex = jellyfish_crypto_1.HASH160(pubkey).toString('hex'); const pushDataHex = stack[1].hex; if (pubkeyHashHex === pushDataHex) { return true; } throw new Error('invalid input option - attempting to sign a mismatch vout and publicKey is not allowed'); }); } /** * If script is not provided, it needs to be guessed * * @param {Vin} vin of the script * @param {SignInputOption} signInputOption to sign the vin */ function getScriptCode(vin, signInputOption) { return __awaiter(this, void 0, void 0, function* () { if (signInputOption.witnessScript !== undefined) { return signInputOption.witnessScript; } if (yield isV0P2WPKH(signInputOption)) { const pubkey = yield signInputOption.publicKey(); const pubkeyHash = jellyfish_crypto_1.HASH160(pubkey); return { stack: [ jellyfish_transaction_1.OP_CODES.OP_DUP, jellyfish_transaction_1.OP_CODES.OP_HASH160, jellyfish_transaction_1.OP_CODES.OP_PUSHDATA(pubkeyHash, 'little'), jellyfish_transaction_1.OP_CODES.OP_EQUALVERIFY, jellyfish_transaction_1.OP_CODES.OP_CHECKSIG ] }; } throw new Error('witnessScript required, only P2WPKH can be guessed'); }); } function asWitnessProgram(transaction, vin, signInputOption, sigHashType) { return __awaiter(this, void 0, void 0, function* () { return { version: transaction.version, hashPrevouts: hashPrevouts(transaction, sigHashType), hashSequence: hashSequence(transaction, sigHashType), outpointTxId: vin.txid, outpointIndex: vin.index, scriptCode: yield getScriptCode(vin, signInputOption), value: signInputOption.prevout.value, sequence: vin.sequence, hashOutputs: hashOutputs(transaction, sigHashType), lockTime: transaction.lockTime, hashType: sigHashType }; }); } /** * TransactionSigner * 1. you can sign an unsigned transaction and get a signed transaction. * 2. you can sign a vin and get a witness in tx for that vin * * https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki * https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki * https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki */ exports.TransactionSigner = { /** * @param {Transaction} transaction to sign * @param {number} index of the vin to sign * @param {SignInputOption} option input option * @param {SIGHASH} sigHashType SIGHASH type */ signInput(transaction, index, option, sigHashType = jellyfish_transaction_1.SIGHASH.ALL) { return __awaiter(this, void 0, void 0, function* () { const vin = transaction.vin[index]; const program = yield asWitnessProgram(transaction, vin, option, sigHashType); const preimage = new jellyfish_transaction_1.CWitnessProgram(program).asBuffer(); const sigHash = jellyfish_crypto_1.dSHA256(preimage); const derSignature = yield option.sign(sigHash); const sigHashBuffer = Buffer.alloc(1, sigHashType); // signature + pubKey const signature = Buffer.concat([derSignature, Buffer.alloc(1, sigHashBuffer)]); const pubkey = yield option.publicKey(); return { scripts: [ { hex: signature.toString('hex') }, { hex: pubkey.toString('hex') } ] }; }); }, sign(transaction, inputOptions, option = {}) { return __awaiter(this, void 0, void 0, function* () { exports.TransactionSigner.validate(transaction, inputOptions, option); const { sigHashType = jellyfish_transaction_1.SIGHASH.ALL } = option; const witnesses = []; for (let i = 0; i < transaction.vin.length; i++) { const witness = yield this.signInput(transaction, i, inputOptions[i], sigHashType); witnesses.push(witness); } return { version: transaction.version, marker: jellyfish_transaction_1.DeFiTransactionConstants.WitnessMarker, flag: jellyfish_transaction_1.DeFiTransactionConstants.WitnessFlag, vin: transaction.vin, vout: transaction.vout, witness: witnesses, lockTime: transaction.lockTime }; }); }, signPrevoutsWithEllipticPairs(transaction, prevouts, ellipticPairs, option = {}) { return __awaiter(this, void 0, void 0, function* () { const inputs = prevouts.map((prevout, index) => { const ellipticPair = ellipticPairs[index]; return { prevout: prevout, publicKey: () => __awaiter(this, void 0, void 0, function* () { return yield ellipticPair.publicKey(); }), sign: (hash) => __awaiter(this, void 0, void 0, function* () { return yield ellipticPair.sign(hash); }) }; }); return yield exports.TransactionSigner.sign(transaction, inputs, option); }); }, validate(transaction, inputOptions, option) { const { version = true, lockTime = true } = (option.validate !== undefined) ? option.validate : {}; if (transaction.vin.length === 0) { throw new Error('vin.length = 0 - attempting to sign transaction without vin is not allowed'); } if (transaction.vin.length !== inputOptions.length) { throw new Error('vin.length and inputOptions.length must match'); } if (version && transaction.version !== jellyfish_transaction_1.DeFiTransactionConstants.Version) { throw new Error(`option.validate.version = true - trying to sign a txn ${transaction.version} different from ${jellyfish_transaction_1.DeFiTransactionConstants.Version} is not supported`); } if (lockTime && transaction.lockTime !== 0) { throw new Error(`option.validate.lockTime = true - lockTime: ${transaction.lockTime} must be zero`); } } }; //# sourceMappingURL=tx_signature.js.map