| const jeton = require('../../index') | ||
| const PrivateKey = jeton.PrivateKey | ||
| const Signature = jeton.Signature | ||
| const Script = jeton.Script | ||
| const OutputScript = jeton.covenant.OutputScript | ||
| const Transaction = jeton.Transaction | ||
| const Sighash = Transaction.Sighash | ||
| const Output = Transaction.Output | ||
| // Create keypairs for 3 players and a referee | ||
| const priv1 = new PrivateKey("L1wChPjacPamAFVbUsZZi5cEd3kMysZSgfDGprGEj91wTP6sh7KH") | ||
| const pub1 = priv1.toPublicKey() | ||
| const priv2 = new PrivateKey("KzyhHmmxwFbv2Mo8bQsJQwXhrCgAtjsCmuqBBmGZrcjfTn1Xvzw1") | ||
| const pub2 = priv2.toPublicKey() | ||
| const priv3 = new PrivateKey("KzwmMwHjbmRRdtwVUowKpYmpnJmMaVyGTwYLmh2qmiWcqgd7W9fG") | ||
| const pub3 = priv3.toPublicKey() | ||
| var refPriv = new PrivateKey('L5FDo3MEb2QNs2aQJ5DVGSDE5eBzVsgZny15Ri649RjysWAeLkTs') | ||
| var refpk = refPriv.toPublicKey(); | ||
| var utxoForPub2 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'b2b671f0cb3d7710b5d8a8420fff14b18173de876da710438dafa0ae6e8f5357', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a9149383fa6588a176c2592cb2f4008d779293246adb88ac' | ||
| }) | ||
| var utxoForPub3 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'ee874221a431cf09d3373c4b9ffbb1e8fe80526d4304695e2f97541fc084c8f4', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a914b011100d12d0537232692b3c113be5a8f505395588ac' | ||
| }) | ||
| // Create array of UTXO arrays | ||
| var splitUtxos = [utxoForPub2, utxoForPub3] | ||
| // Create the final covenant destination outputs | ||
| var output1 = new Output ({ | ||
| script: Script.fromAddress(priv1.toAddress()), | ||
| satoshis: 10010 | ||
| }) | ||
| var output2 = new Output ({ | ||
| script: Script.fromAddress(priv2.toAddress()), | ||
| satoshis: 3003 | ||
| }) | ||
| var output3 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 4004 | ||
| }) | ||
| // Create the output script | ||
| var outputScriptData = { | ||
| outputs: [output1, output2, output3], | ||
| } | ||
| outScript = new OutputScript(outputScriptData) | ||
| var outScriptHex = outScript.toScript().toBuffer().toString('hex') | ||
| var parsedScript = OutputScript.parseScriptPubKey(outScriptHex) | ||
| var outScript2 = new OutputScript(parsedScript) | ||
| var areEqual = outScript.toAddress().toString() == outScript2.toAddress().toString() | ||
| // Set miner fee and total amount to send (will be split between UTXOs from useUTXOs array) | ||
| var splitUtxoMinerFee = 200 | ||
| var amountToSend = 20000 | ||
| var splitTxSendAmount = (amountToSend / splitUtxos.length) | ||
| // Create two separate transactions from players 2 and 3 to fund the escrow | ||
| var sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_ANYONECANPAY) | ||
| var txArray = [] | ||
| for (let i = 0; i < splitUtxos.length; i++) { | ||
| txArray[i] = new Transaction() | ||
| .from(splitUtxos[i]) // Feed information about what unspent outputs one can use | ||
| .toP2SH(outScript, amountToSend) | ||
| .sign([priv2, priv3], sighash) // Signs all the inputs it can | ||
| } | ||
| // Combine the transactions by merging the inputs | ||
| var fundTx = Transaction.mergeTransactionInputs(txArray) | ||
| var itx = new Transaction(fundTx.toString()) | ||
| // Now spend the escrow transaction... | ||
| sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID ) | ||
| var escrowUtxo = Transaction.utxoFromTxOutput(fundTx, 0) | ||
| // Make Transaction from escrow UTXO | ||
| var spendTx = new Transaction() | ||
| .from(escrowUtxo) | ||
| .addOutput(output1) | ||
| .addOutput(output2) | ||
| .addOutput(output3) | ||
| var spendTxBuf = spendTx.toBuffer() | ||
| // Sign covenant input at index 0 as party 1 | ||
| spendTx.signCovenant(0, [priv1], outScript.toScript(), sighash, true) // note the "true" boolean as final argument | ||
| console.log(spendTx.toObject()) | ||
| console.log('OutputScript parsing correctly?', areEqual) | ||
| console.log('scriptSig size', Buffer.from(spendTx.toObject().inputs[0].script, 'hex').byteLength) | ||
| console.log('estimated size', spendTx._estimateSize()) | ||
| console.log('verify tx full sig', spendTx.verify()) | ||
| console.log('jeton signature verified?', spendTx.verifyScriptSig(0)) |
| const jeton = require('../../index') | ||
| const PrivateKey = jeton.PrivateKey | ||
| const Signature = jeton.Signature | ||
| const Script = jeton.Script | ||
| const OutputScript = jeton.covenant.OutputScript | ||
| const Transaction = jeton.Transaction | ||
| const Sighash = Transaction.Sighash | ||
| const Output = Transaction.Output | ||
| // Create keypairs for 3 players and a referee | ||
| const priv1 = new PrivateKey("L1wChPjacPamAFVbUsZZi5cEd3kMysZSgfDGprGEj91wTP6sh7KH") | ||
| const pub1 = priv1.toPublicKey() | ||
| const priv2 = new PrivateKey("KzyhHmmxwFbv2Mo8bQsJQwXhrCgAtjsCmuqBBmGZrcjfTn1Xvzw1") | ||
| const pub2 = priv2.toPublicKey() | ||
| const priv3 = new PrivateKey("KzwmMwHjbmRRdtwVUowKpYmpnJmMaVyGTwYLmh2qmiWcqgd7W9fG") | ||
| const pub3 = priv3.toPublicKey() | ||
| var refPriv = new PrivateKey('L5FDo3MEb2QNs2aQJ5DVGSDE5eBzVsgZny15Ri649RjysWAeLkTs') | ||
| var refpk = refPriv.toPublicKey(); | ||
| var utxoForPub2 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'b2b671f0cb3d7710b5d8a8420fff14b18173de876da710438dafa0ae6e8f5357', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a9149383fa6588a176c2592cb2f4008d779293246adb88ac' | ||
| }) | ||
| var utxoForPub3 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'ee874221a431cf09d3373c4b9ffbb1e8fe80526d4304695e2f97541fc084c8f4', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a914b011100d12d0537232692b3c113be5a8f505395588ac' | ||
| }) | ||
| // Create array of UTXO arrays | ||
| var splitUtxos = [utxoForPub2, utxoForPub3] | ||
| // Create the final covenant destination outputs | ||
| var output1 = new Output ({ | ||
| script: Script.fromAddress(priv1.toAddress()), | ||
| satoshis: 10010 | ||
| }) | ||
| var output2 = new Output ({ | ||
| script: Script.fromAddress(priv2.toAddress()), | ||
| satoshis: 3003 | ||
| }) | ||
| var output3 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 4004 | ||
| }) | ||
| // Create the output script | ||
| var outputScriptData = { | ||
| pubKeys: [pub1, pub2], // multiple signatures are required, submitted in proper | ||
| outputs: [output1, output2, output3] | ||
| } | ||
| outScript = new OutputScript(outputScriptData) | ||
| var outScriptHex = outScript.toScript().toBuffer().toString('hex') | ||
| var parsedScript = OutputScript.parseScriptPubKey(outScriptHex) | ||
| var outScript2 = new OutputScript(parsedScript) | ||
| var areEqual = outScript.toAddress().toString() == outScript2.toAddress().toString() | ||
| // Set miner fee and total amount to send (will be split between UTXOs from useUTXOs array) | ||
| var splitUtxoMinerFee = 200 | ||
| var amountToSend = 20000 | ||
| var splitTxSendAmount = (amountToSend / splitUtxos.length) | ||
| // Create two separate transactions from players 2 and 3 to fund the escrow | ||
| var sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_ANYONECANPAY) | ||
| var txArray = [] | ||
| for (let i = 0; i < splitUtxos.length; i++) { | ||
| txArray[i] = new Transaction() | ||
| .from(splitUtxos[i]) // Feed information about what unspent outputs one can use | ||
| .toP2SH(outScript, amountToSend) | ||
| .sign([priv2, priv3], sighash) // Signs all the inputs it can | ||
| } | ||
| // Combine the transactions by merging the inputs | ||
| var fundTx = Transaction.mergeTransactionInputs(txArray) | ||
| var itx = new Transaction(fundTx.toString()) | ||
| // Now spend the escrow transaction... | ||
| sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID ) | ||
| var escrowUtxo = Transaction.utxoFromTxOutput(fundTx, 0) | ||
| // Make Transaction from escrow UTXO | ||
| var spendTx = new Transaction() | ||
| .from(escrowUtxo) | ||
| .addOutput(output1) | ||
| .addOutput(output2) | ||
| .addOutput(output3) | ||
| var spendTxBuf = spendTx.toBuffer() | ||
| // Get signature for index 0 as party 2 (necessary for multisig) | ||
| var sigForParty2 = spendTx.getCovenantSignatureScript(0, priv2, outScript.toScript(), sighash) | ||
| // Sign covenant input at index 0 as party 1 and add signature Script from party 2 | ||
| spendTx.signCovenant(0, [priv1, sigForParty2], outScript.toScript(), sighash) | ||
| console.log(spendTx.toObject()) | ||
| console.log('OutputScript parsing correctly?', areEqual) | ||
| console.log('scriptSig size', Buffer.from(spendTx.toObject().inputs[0].script, 'hex').byteLength) | ||
| console.log('estimated size', spendTx._estimateSize()) | ||
| console.log('verify tx full sig', spendTx.verify()) | ||
| console.log('jeton signature verified?', spendTx.verifyScriptSig(0)) |
| const jeton = require('../../index') | ||
| const PrivateKey = jeton.PrivateKey | ||
| const Signature = jeton.Signature | ||
| const Script = jeton.Script | ||
| const OutputScript = jeton.covenant.OutputScript | ||
| const Transaction = jeton.Transaction | ||
| const Sighash = Transaction.Sighash | ||
| const Output = Transaction.Output | ||
| // Create keypairs for 3 players and a referee | ||
| const priv1 = new PrivateKey("L1wChPjacPamAFVbUsZZi5cEd3kMysZSgfDGprGEj91wTP6sh7KH") | ||
| const pub1 = priv1.toPublicKey() | ||
| const priv2 = new PrivateKey("KzyhHmmxwFbv2Mo8bQsJQwXhrCgAtjsCmuqBBmGZrcjfTn1Xvzw1") | ||
| const pub2 = priv2.toPublicKey() | ||
| const priv3 = new PrivateKey("KzwmMwHjbmRRdtwVUowKpYmpnJmMaVyGTwYLmh2qmiWcqgd7W9fG") | ||
| const pub3 = priv3.toPublicKey() | ||
| var refPriv = new PrivateKey('L5FDo3MEb2QNs2aQJ5DVGSDE5eBzVsgZny15Ri649RjysWAeLkTs') | ||
| var refpk = refPriv.toPublicKey(); | ||
| var utxoForPub2 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'b2b671f0cb3d7710b5d8a8420fff14b18173de876da710438dafa0ae6e8f5357', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a9149383fa6588a176c2592cb2f4008d779293246adb88ac' | ||
| }) | ||
| var utxoForPub3 = new Transaction.UnspentOutput({ | ||
| txid: | ||
| 'ee874221a431cf09d3373c4b9ffbb1e8fe80526d4304695e2f97541fc084c8f4', | ||
| vout: 1, | ||
| satoshis: 10200, | ||
| scriptPubKey: '76a914b011100d12d0537232692b3c113be5a8f505395588ac' | ||
| }) | ||
| // Create array of UTXO arrays | ||
| var splitUtxos = [utxoForPub2, utxoForPub3] | ||
| // Create the final covenant destination outputs | ||
| var output1 = new Output ({ | ||
| script: Script.fromAddress(priv1.toAddress()), | ||
| satoshis: 10010 | ||
| }) | ||
| var output2 = new Output ({ | ||
| script: Script.fromAddress(priv2.toAddress()), | ||
| satoshis: 3003 | ||
| }) | ||
| var output3 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 2002 | ||
| }) | ||
| var output4 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 1001 | ||
| }) | ||
| var output5 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 1001 | ||
| }) | ||
| var output6 = new Output ({ | ||
| script: Script.fromAddress(priv3.toAddress()), | ||
| satoshis: 1001 | ||
| }) | ||
| // Create the output script | ||
| var outputScriptData = { | ||
| outputs: [output1, output2, output3, output4, output5, output6], // For pay-to-addresses | ||
| prorata: [10, 3, 2, 1, 1, 1] // Amounts must be in this proportion | ||
| } | ||
| outScript = new OutputScript(outputScriptData) | ||
| var outScriptHex = outScript.toScript().toBuffer().toString('hex') | ||
| var parsedScript = OutputScript.parseScriptPubKey(outScriptHex) | ||
| var outScript2 = new OutputScript(parsedScript) | ||
| var areEqual = outScript.toAddress().toString() == outScript2.toAddress().toString() | ||
| // Set miner fee and total amount to send (will be split between UTXOs from useUTXOs array) | ||
| var splitUtxoMinerFee = 200 | ||
| var amountToSend = 20000 | ||
| var splitTxSendAmount = (amountToSend / splitUtxos.length) | ||
| // Create two separate transactions from players 2 and 3 to fund the escrow | ||
| var sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID | Signature.SIGHASH_ANYONECANPAY) | ||
| var txArray = [] | ||
| for (let i = 0; i < splitUtxos.length; i++) { | ||
| txArray[i] = new Transaction() | ||
| .from(splitUtxos[i]) // Feed information about what unspent outputs one can use | ||
| .toP2SH(outScript, amountToSend) | ||
| .sign([priv2, priv3], sighash) // Signs all the inputs it can | ||
| } | ||
| // Combine the transactions by merging the inputs | ||
| var fundTx = Transaction.mergeTransactionInputs(txArray) | ||
| var itx = new Transaction(fundTx.toString()) | ||
| // Now spend the escrow transaction... | ||
| sighash = (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID ) | ||
| var escrowUtxo = Transaction.utxoFromTxOutput(fundTx, 0) | ||
| // Make Transaction from escrow UTXO | ||
| var spendTx = new Transaction() | ||
| .from(escrowUtxo) | ||
| .addOutput(output1) | ||
| .addOutput(output2) | ||
| .addOutput(output3) | ||
| .addOutput(output4) | ||
| .addOutput(output5) | ||
| .addOutput(output6) | ||
| var spendTxBuf = spendTx.toBuffer() | ||
| // Sign covenant input at index 0 as party 1 and add signature Script from party 2 | ||
| spendTx.signCovenant(0, [priv1], outScript.toScript(), sighash, true) // Note the "true" boolean as final argument (anyonecanspend) | ||
| console.log(spendTx.toObject()) | ||
| console.log('OutputScript parsing correctly?', areEqual) | ||
| console.log('scriptSig size', Buffer.from(spendTx.toObject().inputs[0].script, 'hex').byteLength) | ||
| console.log('estimated size', spendTx._estimateSize()) | ||
| console.log('verify tx full sig', spendTx.verify()) | ||
| console.log('jeton signature verified?', spendTx.verifyScriptSig(0)) |
| # Jeton Lib | ||
| **Extension of bitcore-lib-cash for advanced Bitcoin Cash transaction types** | ||
| ## Examples | ||
| Contained in this directory are complete examples for funding and spending of the transaction types made possible by jeton-lib. | ||
| [escrow.js](https://github.com/jeton-tech/jeton-lib/tree/master/examples/escrow.js) - Multi-Party escrow where an oracle provides a signature for a given message based on the outcome of an event | ||
| [threshold.js](https://github.com/jeton-tech/jeton-lib/tree/master/examples/threshold.js) - Multi-Party escrow where an oracle provides a signature for a message containing blockheight and a value (such as price). The party who is either "greater than" or "less than or equal to" the value in the message can collect after the given blockheight | ||
| [covenant/multisig.js](https://github.com/jeton-tech/jeton-lib/tree/master/examples/covenant/multisig.js) - A covenant escrow requiring multiple signatures in which specific amounts must be paid to specific addresses | ||
| [covenant/anyonecanpay.js](https://github.com/jeton-tech/jeton-lib/tree/master/examples/covenant/anyonecanpay.js) - A covenant escrow in which specific amounts must be paid to specific addresses and any private key holder can sign | ||
| [covenant/proportional.js](https://github.com/jeton-tech/jeton-lib/tree/master/examples/covenant/proportional.js) - A covenant escrow in which amounts must be paid to specific addresses in a prescribed proportion (prorata) and any private key holder can sign | ||
| ## License | ||
| Code released under [the MIT license](https://github.com/jeton-tech/jeton-lib/blob/master/LICENSE). |
| module.exports.InputScript = require('./InputScript') | ||
| module.exports.OutputScript = require('./OutputScript') |
| const bitcore = require('bitcore-lib-cash') | ||
| const Hash = bitcore.crypto.Hash | ||
| const Script = bitcore.Script | ||
| const PublicKey = bitcore.PublicKey | ||
| /** | ||
| * Instantiate an object to create covenant scriptSig | ||
| * | ||
| * @param {object} data - The encoded data in various formats | ||
| * @param {Script[]} data.sigScripts - Array of P2PKH signatures for the transaction, in proper order | ||
| * @param {Script} data.outputScript - The original (non-P2SH) scriptPubKey for this input | ||
| * | ||
| * @constructor | ||
| */ | ||
| var InputScript = function (data) { | ||
| this.sigScripts = data.sigScripts | ||
| this.outputScript = data.outputScript | ||
| this.preimage = data.preimage | ||
| } | ||
| /** | ||
| * @returns {Script} | ||
| */ | ||
| InputScript.prototype.toScript = function () { | ||
| let outputBuf = this.outputScript.toBuffer() | ||
| let inScript = Script() | ||
| .add(this.preimage.outputs) | ||
| .add(this.preimage.preimage) | ||
| for (let i = this.sigScripts.length - 1; i >=0; i--) { | ||
| inScript.add(this.sigScripts[i]) | ||
| } | ||
| inScript.add(outputBuf) | ||
| return inScript | ||
| } | ||
| /** | ||
| * @returns {Buffer} | ||
| */ | ||
| InputScript.prototype.toBuffer = function () { | ||
| let outScript = this.toScript() | ||
| return outScript.toBuffer() | ||
| } | ||
| module.exports = InputScript |
| const bitcore = require('bitcore-lib-cash') | ||
| const Address = bitcore.Address | ||
| const Hash = bitcore.crypto.Hash | ||
| const Script = bitcore.Script | ||
| const PublicKey = bitcore.PublicKey | ||
| const BufferReader = bitcore.encoding.BufferReader | ||
| const Output = bitcore.Transaction.Output | ||
| const ScriptNumber = require('../threshold/ScriptNumber') | ||
| const gcd = function (input) { | ||
| if (toString.call(input) !== "[object Array]") | ||
| return false; | ||
| var len, a, b; | ||
| len = input.length; | ||
| if ( !len ) { | ||
| return null; | ||
| } | ||
| a = input[ 0 ]; | ||
| for ( var i = 1; i < len; i++ ) { | ||
| b = input[ i ]; | ||
| a = gcd_two_numbers( a, b ); | ||
| } | ||
| return a; | ||
| }; | ||
| const gcd_two_numbers = function(x, y) { | ||
| if ((typeof x !== 'number') || (typeof y !== 'number')) | ||
| return false; | ||
| x = Math.abs(x); | ||
| y = Math.abs(y); | ||
| while(y) { | ||
| var t = y; | ||
| y = x % y; | ||
| x = t; | ||
| } | ||
| return x; | ||
| }; | ||
| /** | ||
| * Instantiate an object to create covenant scriptPubKey | ||
| * | ||
| * @param {object} data - The encoded data in various formats | ||
| * @param {PublicKey[]} data.pubKeys - (optional) The public keys for required signatures | ||
| * @param {Output[] | object[]} data.outputs - array of party objects | ||
| * @param {int[]} data.prorata - array of integers corresponding to the proportional representation of data.output values | ||
| * | ||
| * @constructor | ||
| */ | ||
| var OutputScript = function (data) { | ||
| this.pubKeys = data.pubKeys ? data.pubKeys : [] | ||
| if (data.outputs) { | ||
| try { | ||
| this.outputs = data.outputs.map(function (out) { | ||
| if (!(out instanceof Output)) { | ||
| out = new Output(out) | ||
| } | ||
| return out | ||
| }) | ||
| } catch(err) { | ||
| throw new Error ('Invalid format for outputs') | ||
| } | ||
| } | ||
| this.prorata = !Array.isArray(data.prorata) ? null : data.prorata.map(x => x / gcd(data.prorata)) | ||
| } | ||
| /** | ||
| * @returns {Script} | ||
| */ | ||
| OutputScript.prototype.toScript = function () { | ||
| //Output Script | ||
| let outScript = new Script() | ||
| // MultiSig | ||
| .add(this.buildMultiSigOut(this.pubKeys)) | ||
| .add('OP_2DUP') | ||
| .add('OP_CHECKSIGVERIFY') | ||
| .add('OP_SWAP') | ||
| .add('OP_SIZE') | ||
| .add('OP_1SUB') | ||
| .add('OP_SPLIT') | ||
| .add('OP_DROP') | ||
| .add('OP_SWAP') | ||
| .add('OP_ROT') | ||
| // Get outputs | ||
| .add('OP_DUP') | ||
| .add('OP_SIZE') | ||
| .add(ScriptNumber.encode(40)) | ||
| .add('OP_SUB') | ||
| .add('OP_SPLIT') | ||
| .add('OP_NIP') | ||
| .add(ScriptNumber.encode(32)) | ||
| .add('OP_SPLIT') | ||
| .add('OP_DROP') | ||
| .add('OP_4') | ||
| .add('OP_ROLL') | ||
| .add('OP_DUP') | ||
| // Begin output checks | ||
| for (let i = 0; i < this.outputs.length; i++) { | ||
| let output = this.outputs[i] | ||
| let fullOutBuf = output.toBufferWriter().toBuffer() | ||
| // Proportional | ||
| if (this.prorata && this.prorata.length > 0) { | ||
| if (this.prorata.length < 2) { | ||
| throw new Error ('Prorportional convenants must have at least 2 amounts in prorata array') | ||
| } | ||
| if (this.prorata.length != this.outputs.length) { | ||
| throw new Error ('Proportional covenants must have a prorata value for every specified output') | ||
| } | ||
| outScript.add(this.buildProrataCheck(i)) | ||
| let outScriptBuf = fullOutBuf.slice(8) | ||
| outScript.add(outScriptBuf) | ||
| } | ||
| else { | ||
| // Exact amount | ||
| outScript.add(fullOutBuf) | ||
| } | ||
| outScript.add(this.buildOutputCheck(output)) | ||
| } | ||
| outScript.add('OP_DROP') | ||
| .add('OP_HASH256') | ||
| .add('OP_EQUALVERIFY') // End Outputs | ||
| .add('OP_SHA256') | ||
| .add('OP_SWAP') | ||
| .add('OP_CHECKDATASIG') | ||
| return outScript | ||
| } | ||
| /** | ||
| * @returns {Buffer} | ||
| */ | ||
| OutputScript.prototype.toBuffer = function () { | ||
| let outScript = this.toScript() | ||
| return outScript.toBuffer() | ||
| } | ||
| /** | ||
| * Return P2SH version of script | ||
| * | ||
| * @returns {Script} | ||
| */ | ||
| OutputScript.prototype.toScriptHash = function () { | ||
| let outputBuf = this.toBuffer() | ||
| var outputP2SH = new Script() | ||
| .add('OP_HASH160') | ||
| .add(Hash.sha256ripemd160(outputBuf)) | ||
| .add('OP_EQUAL') | ||
| return outputP2SH | ||
| } | ||
| /** | ||
| * Return P2SH address | ||
| * @param {Network|string=} network - a {@link Network} object, or a string with the network name ('livenet' or 'testnet') | ||
| * | ||
| * @returns {Address} | ||
| */ | ||
| OutputScript.prototype.toAddress = function (network) { | ||
| network = network || 'livenet' | ||
| let address = new Address(this.toScriptHash(), network, 'scripthash') | ||
| return address | ||
| } | ||
| /** | ||
| * @returns {Script} | ||
| */ | ||
| OutputScript.prototype.buildProrataCheck = function(index) { | ||
| let value = ScriptNumber.encode(this.prorata[index]) | ||
| // Enforce minimum encoding | ||
| if (this.prorata[index] <= 16) { | ||
| if (this.prorata[index] <= 0) { | ||
| throw new Error ('Invalid value in prorata array at index ' + index) | ||
| } | ||
| value = 'OP_' + this.prorata[index] | ||
| } | ||
| let s = new Script() | ||
| .add('OP_8') | ||
| .add('OP_SPLIT') | ||
| .add('OP_SWAP') | ||
| .add('OP_BIN2NUM') | ||
| .add('OP_DUP') | ||
| .add(value) | ||
| .add('OP_BIN2NUM') | ||
| .add('OP_MOD') | ||
| .add('OP_0') | ||
| .add('OP_EQUALVERIFY') | ||
| .add(value) | ||
| .add('OP_BIN2NUM') | ||
| .add('OP_DIV') | ||
| // Logic for inputs after the first | ||
| if (index > 0) { | ||
| s.add('OP_DUP') | ||
| .add('OP_FROMALTSTACK') | ||
| .add('OP_EQUALVERIFY') | ||
| } | ||
| s.add('OP_TOALTSTACK') | ||
| return s | ||
| } | ||
| /** | ||
| * @returns {Script} | ||
| */ | ||
| OutputScript.prototype.buildOutputCheck = function(output) { | ||
| let s = new Script() | ||
| .add('OP_SIZE') | ||
| .add('OP_ROT') | ||
| .add('OP_SWAP') | ||
| .add('OP_SPLIT') | ||
| .add('OP_SWAP') | ||
| .add('OP_ROT') | ||
| .add('OP_EQUALVERIFY') | ||
| return s | ||
| } | ||
| /** | ||
| * @returns {Script} | ||
| */ | ||
| OutputScript.prototype.buildMultiSigOut = function(pubKeys) { | ||
| let s = new Script() | ||
| for (i = 0; i < pubKeys.length; i++) { | ||
| s.add(pubKeys[i].toBuffer()) | ||
| if (pubKeys.length > i + 1) { | ||
| s.add('OP_CHECKSIGVERIFY') | ||
| } | ||
| } | ||
| return s | ||
| } | ||
| /** | ||
| * Parse scriptPubKey into object reflecting inputs | ||
| * @param {string} | ||
| * | ||
| * @returns {object} | ||
| */ | ||
| OutputScript.parseScriptPubKey = function(hexString) { | ||
| let getAllIndexes = function (arr, val) { | ||
| var indexes = [], i; | ||
| for(i = 0; i < arr.length; i++) | ||
| if (arr[i] === val) | ||
| indexes.push(i); | ||
| return indexes; | ||
| } | ||
| let parsedData = { | ||
| pubKeys: [], | ||
| outputs: [], | ||
| prorata: null | ||
| } | ||
| let raw_script = Buffer.from(hexString, 'hex') | ||
| let script = new Script(raw_script) | ||
| let outscriptArr = script.toASM().split(' ') | ||
| // console.log('outscriptArr',outscriptArr) | ||
| // Get OutputScript.pubKeys | ||
| let csvAppearances = getAllIndexes(outscriptArr, 'OP_CHECKSIGVERIFY') | ||
| // console.log('csvAppearances', csvAppearances) | ||
| for(let i = 0; i < csvAppearances.length; i++) { | ||
| let csvIndex = csvAppearances[i] | ||
| if (outscriptArr[csvIndex - 1] == 'OP_2DUP') { | ||
| if (csvIndex - 2 >= 0) | ||
| parsedData.pubKeys.push(PublicKey.fromString(outscriptArr[csvIndex - 2])) | ||
| } else { | ||
| parsedData.pubKeys.push(PublicKey.fromString(outscriptArr[csvIndex - 1])) | ||
| } | ||
| } | ||
| // Get OutputScript.prorata | ||
| let b2nAppearances = getAllIndexes(outscriptArr, 'OP_BIN2NUM') | ||
| for(let i = 0; i < b2nAppearances.length; i++) { | ||
| if (!parsedData.prorata) | ||
| parsedData.prorata = [] | ||
| let b2nIndex = b2nAppearances[i] | ||
| if (outscriptArr[b2nIndex + 1] == 'OP_MOD') { | ||
| let proNum = outscriptArr[b2nIndex - 1] | ||
| // Handle 1-16 as versus all other byte-encoded numbers | ||
| if (proNum.includes('OP_')) { | ||
| proNum = parseInt(proNum.replace('OP_', '')) | ||
| } else { | ||
| let proNumBuf = Buffer.from(proNum, 'hex') | ||
| proNum = ScriptNumber.decode(proNumBuf) | ||
| } | ||
| parsedData.prorata.push(proNum) | ||
| } | ||
| } | ||
| // Get OutputScript.outputs if prorata versus exact | ||
| let altStackAppearances = getAllIndexes(outscriptArr, 'OP_TOALTSTACK') | ||
| if (altStackAppearances.length > 0) { | ||
| for(let i = 0; i < altStackAppearances.length; i++) { | ||
| let altStackIndex = altStackAppearances[i] | ||
| // Slice off first 2 bytes (length) to get scriptPubkey | ||
| let scriptPubKey = outscriptArr[altStackIndex + 1].slice(2) | ||
| let outObj = { | ||
| script: scriptPubKey, | ||
| satoshis: 0 | ||
| } | ||
| parsedData.outputs.push(Output.fromObject(outObj)) | ||
| } | ||
| } else { | ||
| let evAppearances = getAllIndexes(outscriptArr, 'OP_EQUALVERIFY') | ||
| for(let i = 0; i < evAppearances.length; i++) { | ||
| let evIndex = evAppearances[i] | ||
| if (outscriptArr[evIndex - 1] == 'OP_ROT') { | ||
| let outBuf = Buffer.from(outscriptArr[evIndex - 7], 'hex') | ||
| let reader = new BufferReader(outBuf) | ||
| parsedData.outputs.push(Output.fromBufferReader(reader)) | ||
| } | ||
| } | ||
| } | ||
| return parsedData | ||
| } | ||
| module.exports = OutputScript |
| module.exports = require('./Transaction'); | ||
| module.exports.Sighash = require('./Sighash'); |
| const bitcore = require('bitcore-lib-cash') | ||
| const Sighash = bitcore.Transaction.Sighash | ||
| const BufferWriter = bitcore.encoding.BufferWriter; | ||
| const BufferReader = bitcore.encoding.BufferReader; | ||
| const BN = bitcore.crypto.BN; | ||
| const Hash = bitcore.crypto.Hash; | ||
| const Signature = bitcore.crypto.Signature; | ||
| const BufferUtil = bitcore.util.buffer; | ||
| const $ = bitcore.util.preconditions; | ||
| const _ = bitcore.deps._; | ||
| Sighash.getPreimage = function(transaction, sighashType, inputNumber, subscript) { | ||
| var input = transaction.inputs[inputNumber]; | ||
| var satoshisBN = input.output.satoshisBN; | ||
| $.checkArgument( | ||
| satoshisBN instanceof BN, | ||
| 'For ForkId=0 signatures, satoshis or complete input must be provided' | ||
| ); | ||
| function GetForkId() { | ||
| return 0; // In the UAHF, a fork id of 0 is used (see [4] REQ-6-2 NOTE 4) | ||
| }; | ||
| function GetPrevouts(tx) { | ||
| var writer = new BufferWriter() | ||
| _.each(tx.inputs, function(input) { | ||
| writer.writeReverse(input.prevTxId); | ||
| writer.writeUInt32LE(input.outputIndex); | ||
| }); | ||
| var buf = writer.toBuffer(); | ||
| return buf | ||
| } | ||
| function GetSequences(tx) { | ||
| var writer = new BufferWriter() | ||
| _.each(tx.inputs, function(input) { | ||
| writer.writeUInt32LE(input.sequenceNumber); | ||
| }); | ||
| var buf = writer.toBuffer(); | ||
| return buf; | ||
| } | ||
| function GetOutputs(tx, n) { | ||
| var writer = new BufferWriter() | ||
| if ( _.isUndefined(n)) { | ||
| _.each(tx.outputs, function(output) { | ||
| output.toBufferWriter(writer); | ||
| }); | ||
| } else { | ||
| tx.outputs[n].toBufferWriter(writer); | ||
| } | ||
| var buf = writer.toBuffer(); | ||
| return buf; | ||
| } | ||
| var hashPrevouts = BufferUtil.emptyBuffer(32); | ||
| var hashSequences = BufferUtil.emptyBuffer(32); | ||
| var hashOutputs = BufferUtil.emptyBuffer(32); | ||
| var prevouts = GetPrevouts(transaction); | ||
| if (!(sighashType & Signature.SIGHASH_ANYONECANPAY)) { | ||
| hashPrevouts = Hash.sha256sha256(prevouts); | ||
| } | ||
| var sequences = GetSequences(transaction); | ||
| if (!(sighashType & Signature.SIGHASH_ANYONECANPAY) && | ||
| (sighashType & 31) != Signature.SIGHASH_SINGLE && | ||
| (sighashType & 31) != Signature.SIGHASH_NONE) { | ||
| hashSequences = Hash.sha256sha256(sequences); | ||
| } | ||
| var outputs = GetOutputs(transaction); | ||
| if ((sighashType & 31) != Signature.SIGHASH_SINGLE && (sighashType & 31) != Signature.SIGHASH_NONE) { | ||
| hashOutputs = Hash.sha256sha256(outputs); | ||
| } else if ((sighashType & 31) == Signature.SIGHASH_SINGLE && inputNumber < transaction.outputs.length) { | ||
| outputs = GetOutputs(transaction, inputNumber) | ||
| hashOutputs = Hash.sha256sha256(outputs); | ||
| } | ||
| var writer = new BufferWriter() | ||
| // Version | ||
| writer.writeInt32LE(transaction.version); | ||
| // Input prevouts/nSequence (none/all, depending on flags) | ||
| writer.write(hashPrevouts); | ||
| writer.write(hashSequences); | ||
| // outpoint (32-byte hash + 4-byte little endian) | ||
| writer.writeReverse(input.prevTxId); | ||
| writer.writeUInt32LE(input.outputIndex); | ||
| // scriptCode of the input (serialized as scripts inside CTxOuts) | ||
| writer.writeVarintNum(subscript.toBuffer().length) | ||
| writer.write(subscript.toBuffer()); | ||
| // value of the output spent by this input (8-byte little endian) | ||
| writer.writeUInt64LEBN(satoshisBN); | ||
| // nSequence of the input (4-byte little endian) | ||
| var sequenceNumber = input.sequenceNumber; | ||
| writer.writeUInt32LE(sequenceNumber); | ||
| // Outputs (none/one/all, depending on flags) | ||
| writer.write(hashOutputs); | ||
| // Locktime | ||
| writer.writeUInt32LE(transaction.nLockTime); | ||
| // sighashType | ||
| writer.writeUInt32LE(sighashType >>>0); | ||
| // console.log('preimage', [ | ||
| // transaction.version, | ||
| // hashPrevouts.toString('hex'), | ||
| // hashSequences.toString('hex'), | ||
| // input.prevTxId.toString('hex'), | ||
| // input.outputIndex, | ||
| // subscript.toBuffer().length, | ||
| // subscript.toBuffer().toString('hex'), | ||
| // satoshisBN, | ||
| // sequenceNumber, | ||
| // hashOutputs.toString('hex'), | ||
| // transaction.nLockTime, | ||
| // sighashType >>>0 | ||
| // ]) | ||
| var buf = writer.toBuffer(); | ||
| var buf256 = Hash.sha256(buf); | ||
| var ret = Hash.sha256sha256(buf); | ||
| // ret = new BufferReader(ret).readReverse(); | ||
| resObj = { | ||
| prevouts: prevouts, | ||
| prevoutsHash: hashPrevouts, | ||
| sequences: sequences, | ||
| hashSequences: hashSequences, | ||
| outputs: outputs, | ||
| hashOutputs: hashOutputs, | ||
| preimage: buf, | ||
| // preimage256: buf256, | ||
| // hashPreimage: ret, | ||
| } | ||
| return resObj; | ||
| } | ||
| module.exports = Sighash |
| const bitcore = require('bitcore-lib-cash') | ||
| const Transaction = bitcore.Transaction | ||
| const Script = bitcore.Script | ||
| const PrivateKey = bitcore.PrivateKey | ||
| const Hash = bitcore.crypto.Hash | ||
| const Interpreter = bitcore.Script.Interpreter | ||
| const BN = bitcore.crypto.BN | ||
| const BufferUtil = bitcore.util.buffer | ||
| const Sighash = require('./Sighash') | ||
| const Signature = require('../Signature') | ||
| const InputScript = require('../escrow/InputScript') | ||
| const ThresholdInputScript = require('../threshold/InputScript') | ||
| const CovenantInputScript = require('../covenant/InputScript') | ||
| Transaction.P2SHFlags = Interpreter.SCRIPT_VERIFY_P2SH | ||
| | Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ||
| | Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID | ||
| | Interpreter.SCRIPT_ENABLE_CHECKDATASIG | ||
| | Interpreter.SCRIPT_VERIFY_STRICTENC | ||
| | Interpreter.SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE | ||
| /** | ||
| * Adds a Pay-to-Script-Hash input to transaction | ||
| * | ||
| * @param {Script} subScript - original Script | ||
| * @param {number} satoshis - output amount | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.toP2SH = function (subScript, satoshis) { | ||
| let outputBuf = subScript.toBuffer() | ||
| let outputP2SH = new Script() | ||
| .add('OP_HASH160') | ||
| .add(Hash.sha256ripemd160(outputBuf)) | ||
| .add('OP_EQUAL') | ||
| this.addOutput(new Transaction.Output({ | ||
| script: outputP2SH, | ||
| satoshis: satoshis | ||
| })) | ||
| return this | ||
| } | ||
| /** | ||
| * Signs an escrow transaction as defined in jeton.escrow.OutputScript | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey} winnerPrivKey - the private key of the escrow beneficiary | ||
| * @param {string} refMsg - the message for the outcome | ||
| * @param {Signature} refSig - the signature for the message and referee public key | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.signEscrow = function (inputIndex, winnerPrivKey, refMsg, refSig, subscript, sighash) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, winnerPrivKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| let winnerScript = Script.buildPublicKeyHashIn(winnerPrivKey.toPublicKey(), p2pkhSig, sighash) | ||
| // Generate scriptSig | ||
| let inputScriptData = { | ||
| refereeSig: refSig, | ||
| message: refMsg, | ||
| winnerScript: winnerScript, | ||
| outputScript: subscript | ||
| } | ||
| inScript = new InputScript(inputScriptData) | ||
| // Set scriptSig for inputIndex | ||
| this.inputs[inputIndex].setScript(inScript.toScript()) | ||
| return this | ||
| } | ||
| /** | ||
| * Signs an threshold escrow transaction as defined in jeton.threshold.OutputScript | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey} winnerPrivKey - the private key of the escrow beneficiary | ||
| * @param {string} oracleMsg - the message for the outcome | ||
| * @param {Signature} oracleSig - the signature for the message and referee public key | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.signThreshold = function (inputIndex, winnerPrivKey, oracleMsg, oracleSig, subscript, sighash) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, winnerPrivKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| let winnerScript = Script.buildPublicKeyHashIn(winnerPrivKey.toPublicKey(), p2pkhSig, sighash) | ||
| // Generate scriptSig | ||
| let inputScriptData = { | ||
| oracleSig: oracleSig, | ||
| message: oracleMsg, | ||
| winnerScript: winnerScript, | ||
| outputScript: subscript | ||
| } | ||
| inScript = new ThresholdInputScript(inputScriptData) | ||
| // Set scriptSig for inputIndex | ||
| this.inputs[inputIndex].setScript(inScript.toScript()) | ||
| return this | ||
| } | ||
| /** | ||
| * Signs an threshold escrow transaction as defined in jeton.covenant.OutputScript | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey[] | Script[]} privKeysOrScripts - mixed array of PrivateKeys or Scripts representing signing parties. Must be in order. | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * @param {boolean} anyoneCanSpend - signifies if there are no specific required signatures | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.signCovenant = function (inputIndex, privKeysOrScripts, subscript, sighash, anyoneCanSpend = false) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let sigScripts = [] | ||
| for (let i = 0; i < privKeysOrScripts.length; i++) { | ||
| let sigScript = privKeysOrScripts[i] | ||
| if (sigScript instanceof PrivateKey) { | ||
| let privKey = sigScript | ||
| if(!anyoneCanSpend) { | ||
| sigScript = this.getCovenantSignatureScript(inputIndex, privKey, subscript, sighash) | ||
| } else { | ||
| if (privKeysOrScripts.length > 1) { | ||
| throw new Error('For anyoneCanSpend inputs, privKeysOrScripts must have a length of 1 and consist of a single PrivateKey') | ||
| } | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, privKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| sigScript = Script.buildPublicKeyHashIn(privKey.toPublicKey(), p2pkhSig, sighash) | ||
| } | ||
| } | ||
| sigScripts.push(sigScript) | ||
| } | ||
| let preimage = Sighash.getPreimage(this, sighash, inputIndex, subscript) | ||
| // Generate scriptSig | ||
| let inputScriptData = { | ||
| sigScripts: sigScripts, | ||
| outputScript: subscript, | ||
| preimage: preimage | ||
| } | ||
| inScript = new CovenantInputScript(inputScriptData) | ||
| // Set scriptSig for inputIndex | ||
| this.inputs[inputIndex].setScript(inScript.toScript()) | ||
| return this | ||
| } | ||
| /** | ||
| * Returns a signature, in Script form, that can be used in covenant multisig | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey} privKey - the private key of the escrow beneficiary | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * | ||
| * @returns {Script} | ||
| */ | ||
| Transaction.prototype.getCovenantSignatureScript = function (inputIndex, privKey, subscript, sighash) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, privKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| let sigScript = new Script() | ||
| .add(BufferUtil.concat([ | ||
| p2pkhSig.toBuffer(), | ||
| BufferUtil.integerAsSingleByteBuffer(sighash) | ||
| ])) | ||
| return sigScript | ||
| } | ||
| /** | ||
| * Verify that a P2SH input is properly signed | ||
| * | ||
| * @param {number} inputIndex - the index of the input to be verified | ||
| * | ||
| * @returns {boolean} | ||
| */ | ||
| Transaction.prototype.verifyScriptSig = function (inputIndex) { | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let inScriptHex = input.script | ||
| let inScript = Script.fromHex(inScriptHex) | ||
| let outScriptHex = input.output.script | ||
| let outScript = Script.fromHex(outScriptHex) | ||
| let verified = Interpreter().verify(inScript, outScript, this, inputIndex, Transaction.P2SHFlags, BN.fromNumber(input.output.satoshis)) | ||
| return verified | ||
| } | ||
| /** | ||
| * Merges transactions (must use SIGHASH_ANYONECANPAY) | ||
| * | ||
| * @param {Transaction[]} txArray | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.mergeTransactionInputs = function (txArray) { | ||
| let baseTx = txArray.shift() | ||
| for(let i = 0; i < txArray.length; i++) { | ||
| let param = {} | ||
| param.prevTxId = txArray[i].inputs[0].prevTxId | ||
| param.outputIndex = txArray[i].inputs[0].outputIndex | ||
| param.sequenceNumber = txArray[i].inputs[0].sequenceNumber | ||
| param.script = txArray[i].inputs[0].script | ||
| let input = new Transaction.Input(param) | ||
| baseTx.addInput(input, param.script, 1000) | ||
| } | ||
| return baseTx | ||
| } | ||
| /** | ||
| * Format UTXOs for easy use with Transaction.to() | ||
| * | ||
| * @param {object[]} utxoArray | ||
| * | ||
| * @returns {UnspentOutput[][]} | ||
| */ | ||
| Transaction.formatUtxos = function (utxoArray) { | ||
| let result = [] | ||
| for (let i = 0; i < utxoArray.length; i++) { | ||
| let scriptPubKey = utxoArray[i].scriptPubKey | ||
| let utxos = utxoArray[i].utxos | ||
| utxos = utxos.map(function addScriptPubKey(utxo) { | ||
| utxo.scriptPubKey = scriptPubKey | ||
| return new Transaction.UnspentOutput(utxo) | ||
| }) | ||
| result[i] = utxos | ||
| } | ||
| return result | ||
| } | ||
| /** | ||
| * Get the satoshi total for an array of utxos | ||
| * | ||
| * @param {object[] | UnspentOutput[]} utxos | ||
| * | ||
| * @returns {number} | ||
| */ | ||
| Transaction.utxosTotalSatoshis = function (utxos) { | ||
| let totalSats = utxos.reduce(function sumSats(total, utxo) { | ||
| return total + utxo.satoshis | ||
| }, 0) | ||
| return totalSats | ||
| } | ||
| Transaction.constructSplitTx = function (inputUtxos, destinationAddr, amountToSend, minerFee) { | ||
| totalSats = Transaction.utxosTotalSatoshis(inputUtxos) | ||
| tx = new Transaction() | ||
| for (let i=0; i < inputUtxos.length; i++) { | ||
| tx.from(inputUtxos[i]) | ||
| } | ||
| tx.to(destinationAddr, amountToSend) | ||
| tx.to(destinationAddr, totalSats - amountToSend - minerFee) | ||
| return tx | ||
| } | ||
| /** | ||
| * Generate an UnspentOutput from the output of a transaction | ||
| * @param {Transaction} transaction | ||
| * @param {number} outputIndex | ||
| * | ||
| * @returns {UnspentOutput} | ||
| */ | ||
| Transaction.utxoFromTxOutput = function (transaction, outputIndex) { | ||
| txObj = transaction.toObject() | ||
| return new Transaction.UnspentOutput({ | ||
| "txId" : txObj.hash, | ||
| "outputIndex" : outputIndex, | ||
| "script" : txObj.outputs[outputIndex].script, | ||
| "satoshis" : txObj.outputs[outputIndex].satoshis | ||
| }) | ||
| } | ||
| module.exports = Transaction |
@@ -112,5 +112,5 @@ const jeton = require('../index') | ||
| // console.log(spendTx.toObject()) | ||
| console.log(spendTx.toObject()) | ||
| console.log('estimated size', spendTx._estimateSize()) | ||
| console.log('verify tx full sig', spendTx.verify()) | ||
| console.log('jeton signature verified?', spendTx.verifyScriptSig(0)) |
+6
-3
@@ -12,5 +12,5 @@ 'use strict'; | ||
| // Escrow | ||
| jeton.escrow = {} | ||
| jeton.escrow.InputScript = require('./lib/escrow/InputScript') | ||
| jeton.escrow.OutputScript = require('./lib/escrow/OutputScript') | ||
| jeton.escrow = require('./lib/escrow') | ||
| // jeton.escrow.InputScript = require('./lib/escrow/InputScript') | ||
| // jeton.escrow.OutputScript = require('./lib/escrow/OutputScript') | ||
@@ -22,2 +22,5 @@ // Threshold | ||
| // Covenant | ||
| jeton.covenant = require('./lib/covenant') | ||
| module.exports = jeton |
+1
-1
| { | ||
| "name": "jeton-lib", | ||
| "version": "2.0.1", | ||
| "version": "2.1.0", | ||
| "description": "Extension of bitcore-lib-cash for advanced Bitcoin Cash transaction types", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+6
-1
@@ -19,3 +19,3 @@ # Jeton Lib | ||
| "dependencies": { | ||
| "jeton-lib": "^2.0.1", | ||
| "jeton-lib": "^2.1.0", | ||
| ... | ||
@@ -29,2 +29,7 @@ } | ||
| Examples include: | ||
| * Multi-party escrow with an arbitrary message signed by an oracle | ||
| * Multi-party escrow with a price message signed by an oracle | ||
| * Covenant escrow - multisig, anyone-can-spend, and proportional | ||
| ### Include jeton-lib wherever you use bitcore-lib-cash | ||
@@ -31,0 +36,0 @@ |
| const bitcore = require('bitcore-lib-cash') | ||
| const Transaction = bitcore.Transaction | ||
| const Script = bitcore.Script | ||
| const Hash = bitcore.crypto.Hash | ||
| const Interpreter = bitcore.Script.Interpreter | ||
| const Sighash = Transaction.Sighash | ||
| const BN = bitcore.crypto.BN | ||
| const Signature = require('./Signature') | ||
| const InputScript = require('./escrow/InputScript') | ||
| const ThresholdInputScript = require('./threshold/InputScript') | ||
| Transaction.P2SHFlags = Interpreter.SCRIPT_VERIFY_P2SH | ||
| | Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ||
| | Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID | ||
| | Interpreter.SCRIPT_ENABLE_CHECKDATASIG | ||
| | Interpreter.SCRIPT_VERIFY_STRICTENC | ||
| | Interpreter.SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE | ||
| /** | ||
| * Adds a Pay-to-Script-Hash input to transaction | ||
| * | ||
| * @param {Script} scriptPubKey - original Script | ||
| * @param {number} satoshis - output amount | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.toP2SH = function (scriptPubKey, satoshis) { | ||
| let outputBuf = scriptPubKey.toBuffer() | ||
| let outputP2SH = new Script() | ||
| .add('OP_HASH160') | ||
| .add(Hash.sha256ripemd160(outputBuf)) | ||
| .add('OP_EQUAL') | ||
| this.addOutput(new Transaction.Output({ | ||
| script: outputP2SH, | ||
| satoshis: satoshis | ||
| })) | ||
| return this | ||
| } | ||
| /** | ||
| * Signs an escrow transaction as defined in jeton.escrow.OutputScript | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey} winnerPrivKey - the private key of the escrow beneficiary | ||
| * @param {string} refMsg - the message for the outcome | ||
| * @param {Signature} refSig - the signature for the message and referee public key | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.signEscrow = function (inputIndex, winnerPrivKey, refMsg, refSig, subscript, sighash) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, winnerPrivKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| let winnerScript = Script.buildPublicKeyHashIn(winnerPrivKey.toPublicKey(), p2pkhSig, sighash) | ||
| // Generate scriptSig | ||
| let inputScriptData = { | ||
| refereeSig: refSig, | ||
| message: refMsg, | ||
| winnerScript: winnerScript, | ||
| outputScript: subscript | ||
| } | ||
| inScript = new InputScript(inputScriptData) | ||
| // Set scriptSig for inputIndex | ||
| this.inputs[inputIndex].setScript(inScript.toScript()) | ||
| return this | ||
| } | ||
| /** | ||
| * Signs an threshold escrow transaction as defined in jeton.threshold.OutputScript | ||
| * | ||
| * @param {number} inputIndex - index of the input to sign | ||
| * @param {PrivateKey} winnerPrivKey - the private key of the escrow beneficiary | ||
| * @param {string} oracleMsg - the message for the outcome | ||
| * @param {Signature} oracleSig - the signature for the message and referee public key | ||
| * @param {Script} subscript - the non-P2SH (original) scriptPubKey | ||
| * @param {number} sighash - the type of signature | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.prototype.signThreshold = function (inputIndex, winnerPrivKey, oracleMsg, oracleSig, subscript, sighash) { | ||
| sighash = sighash || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID) | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let p2pkhSig = Sighash.sign(this, winnerPrivKey, sighash, inputIndex, subscript, BN.fromNumber(input.output.satoshis), Transaction.P2SHFlags) | ||
| let winnerScript = Script.buildPublicKeyHashIn(winnerPrivKey.toPublicKey(), p2pkhSig, sighash) | ||
| // Generate scriptSig | ||
| let inputScriptData = { | ||
| oracleSig: oracleSig, | ||
| message: oracleMsg, | ||
| winnerScript: winnerScript, | ||
| outputScript: subscript | ||
| } | ||
| inScript = new ThresholdInputScript(inputScriptData) | ||
| // Set scriptSig for inputIndex | ||
| this.inputs[inputIndex].setScript(inScript.toScript()) | ||
| return this | ||
| } | ||
| /** | ||
| * Verify that a P2SH input is properly signed | ||
| * | ||
| * @param {number} inputIndex - the index of the input to be verified | ||
| * | ||
| * @returns {boolean} | ||
| */ | ||
| Transaction.prototype.verifyScriptSig = function (inputIndex) { | ||
| let input = this.toObject()['inputs'][inputIndex] | ||
| let inScriptHex = input.script | ||
| let inScript = Script.fromHex(inScriptHex) | ||
| let outScriptHex = input.output.script | ||
| let outScript = Script.fromHex(outScriptHex) | ||
| let verified = Interpreter().verify(inScript, outScript, this, inputIndex, Transaction.P2SHFlags, BN.fromNumber(input.output.satoshis)) | ||
| return verified | ||
| } | ||
| /** | ||
| * Merges transactions (must use SIGHASH_ANYONECANPAY) | ||
| * | ||
| * @param {Transaction[]} txArray | ||
| * | ||
| * @returns {Transaction} | ||
| */ | ||
| Transaction.mergeTransactionInputs = function (txArray) { | ||
| let baseTx = txArray.shift() | ||
| for(let i = 0; i < txArray.length; i++) { | ||
| let param = {} | ||
| param.prevTxId = txArray[i].inputs[0].prevTxId | ||
| param.outputIndex = txArray[i].inputs[0].outputIndex | ||
| param.sequenceNumber = txArray[i].inputs[0].sequenceNumber | ||
| param.script = txArray[i].inputs[0].script | ||
| let input = new Transaction.Input(param) | ||
| baseTx.addInput(input, param.script, 1000) | ||
| } | ||
| return baseTx | ||
| } | ||
| /** | ||
| * Format UTXOs for easy use with Transaction.to() | ||
| * | ||
| * @param {object[]} utxoArray | ||
| * | ||
| * @returns {UnspentOutput[][]} | ||
| */ | ||
| Transaction.formatUtxos = function (utxoArray) { | ||
| let result = [] | ||
| for (let i = 0; i < utxoArray.length; i++) { | ||
| let scriptPubKey = utxoArray[i].scriptPubKey | ||
| let utxos = utxoArray[i].utxos | ||
| utxos = utxos.map(function addScriptPubKey(utxo) { | ||
| utxo.scriptPubKey = scriptPubKey | ||
| return new Transaction.UnspentOutput(utxo) | ||
| }) | ||
| result[i] = utxos | ||
| } | ||
| return result | ||
| } | ||
| /** | ||
| * Get the satoshi total for an array of utxos | ||
| * | ||
| * @param {object[] | UnspentOutput[]} utxos | ||
| * | ||
| * @returns {number} | ||
| */ | ||
| Transaction.utxosTotalSatoshis = function (utxos) { | ||
| let totalSats = utxos.reduce(function sumSats(total, utxo) { | ||
| return total + utxo.satoshis | ||
| }, 0) | ||
| return totalSats | ||
| } | ||
| Transaction.constructSplitTx = function (inputUtxos, destinationAddr, amountToSend, minerFee) { | ||
| totalSats = Transaction.utxosTotalSatoshis(inputUtxos) | ||
| tx = new Transaction() | ||
| for (let i=0; i < inputUtxos.length; i++) { | ||
| tx.from(inputUtxos[i]) | ||
| } | ||
| tx.to(destinationAddr, amountToSend) | ||
| tx.to(destinationAddr, totalSats - amountToSend - minerFee) | ||
| return tx | ||
| } | ||
| /** | ||
| * Generate an UnspentOutput from the output of a transaction | ||
| * @param {Transaction} transaction | ||
| * @param {number} outputIndex | ||
| * | ||
| * @returns {UnspentOutput} | ||
| */ | ||
| Transaction.utxoFromTxOutput = function (transaction, outputIndex) { | ||
| txObj = transaction.toObject() | ||
| return new Transaction.UnspentOutput({ | ||
| "txId" : txObj.hash, | ||
| "outputIndex" : outputIndex, | ||
| "script" : txObj.outputs[outputIndex].script, | ||
| "satoshis" : txObj.outputs[outputIndex].satoshis | ||
| }) | ||
| } | ||
| module.exports = Transaction |
69287
88.95%25
56.25%1668
94.18%111
4.72%