UNPKG

bitcore-lib-cash

Version:

A pure and powerful JavaScript Bitcoin Cash library.

131 lines (118 loc) 3.95 kB
const _ = require('lodash'); const Hash = require('../crypto/hash'); const Opcode = require('../opcode'); const PublicKey = require('../publickey'); // This file implements the Zero-Confirmation Escrow scripts defined here: // https://ide.bitauth.com/import-gist/104baff7503d6a7ad619ad814153b059 const Escrow = {}; const pushOpcodeFromNumber = function(n) { return { 0: Opcode.OP_0, 1: Opcode.OP_1, 2: Opcode.OP_2, 3: Opcode.OP_3, 4: Opcode.OP_4, 5: Opcode.OP_5, 6: Opcode.OP_6, 7: Opcode.OP_7, 8: Opcode.OP_8, 9: Opcode.OP_9, 10: Opcode.OP_10, 11: Opcode.OP_11, 12: Opcode.OP_12, 13: Opcode.OP_13, 14: Opcode.OP_14, 15: Opcode.OP_15, 16: Opcode.OP_16 }[n]; }; const getNumMerkleLevels = function(numPublicKeys) { return Math.ceil(Math.log2(numPublicKeys)); }; Escrow.getMerkleRoot = function getMerkleRoot(hashes) { if (hashes.length === 1) { return hashes[0]; } const parentHashes = _.chunk(hashes, 2).map(hashPair => Hash.sha256ripemd160(Buffer.concat(hashPair))); return getMerkleRoot(parentHashes); }; Escrow.generateMerkleRootFromPublicKeys = function(publicKeys) { const numLevels = getNumMerkleLevels(publicKeys.length); const numItems = Math.pow(2, numLevels); const sortedPublicKeys = publicKeys .map(publicKey => publicKey.toString('hex')) .sort() .map(publicKeyString => PublicKey.fromString(publicKeyString).toBuffer()); const zeros = Array(numItems - publicKeys.length).fill(Buffer.alloc(0)); const leaves = sortedPublicKeys.concat(zeros).map(value => Hash.sha256ripemd160(value)); return Escrow.getMerkleRoot(leaves); }; const generateSingleInputPublicKeyValidationOperations = function(inputPublicKey) { const inputPublicKeyHash = Hash.sha256ripemd160(inputPublicKey.toBuffer()); return [Opcode.OP_DUP, Opcode.OP_HASH160, inputPublicKeyHash, Opcode.OP_EQUALVERIFY]; }; const generateMerkleBasedInputPublicKeyValidationOperations = function(inputPublicKeys) { const numLevels = getNumMerkleLevels(inputPublicKeys.length); const rootHash = Escrow.generateMerkleRootFromPublicKeys(inputPublicKeys); const merkleTreeConstructionOperationsForEachLevel = Array(numLevels) .fill([ Opcode.OP_FROMALTSTACK, Opcode.OP_IF, Opcode.OP_SWAP, Opcode.OP_ENDIF, Opcode.OP_CAT, Opcode.OP_HASH160 ]) .reduce((arr, item) => arr.concat(item), []); return [ ...Array(numLevels).fill(Opcode.OP_TOALTSTACK), pushOpcodeFromNumber(numLevels), Opcode.OP_PICK, Opcode.OP_HASH160, ...merkleTreeConstructionOperationsForEachLevel, rootHash, Opcode.OP_EQUALVERIFY ]; }; Escrow.generateInputPublicKeyValidationOperations = function(inputPublicKeys) { if (inputPublicKeys.length === 1) { return generateSingleInputPublicKeyValidationOperations(inputPublicKeys[0]); } return generateMerkleBasedInputPublicKeyValidationOperations(inputPublicKeys); }; Escrow.generateRedeemScriptOperations = function(inputPublicKeys, reclaimPublicKey) { const checkCustomerReclaimPublicKey = [ Opcode.OP_DUP, Opcode.OP_HASH160, Hash.sha256ripemd160(reclaimPublicKey.toBuffer()), Opcode.OP_EQUAL, Opcode.OP_IF, Opcode.OP_CHECKSIG, Opcode.OP_ELSE ]; const checkInputPublicKey = Escrow.generateInputPublicKeyValidationOperations(inputPublicKeys); const ensureTransactionsAreUnique = [ Opcode.OP_OVER, Opcode.OP_4, Opcode.OP_PICK, Opcode.OP_EQUAL, Opcode.OP_NOT, Opcode.OP_VERIFY ]; const ensureBothSignaturesAreValid = [ Opcode.OP_DUP, Opcode.OP_TOALTSTACK, Opcode.OP_CHECKDATASIGVERIFY, Opcode.OP_FROMALTSTACK, Opcode.OP_CHECKDATASIG, Opcode.OP_ENDIF ]; const allOperations = [ ...checkCustomerReclaimPublicKey, ...checkInputPublicKey, ...ensureTransactionsAreUnique, ...ensureBothSignaturesAreValid ]; return allOperations; }; module.exports = Escrow;