@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
JavaScript
;
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