diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..77d425745 --- /dev/null +++ b/.npmignore @@ -0,0 +1,9 @@ +# Dev directories +.gitignore +.travis.yml +.nyc_output +coverage + +# Dependency directory +node_modules +yarn.lock \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bd846c9d..95c3178db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 3.6.1 +__added__ +- Added `Transaction.getExtraData` method +- Added/replaced `network` parameter to `Transaction.fromBuffer` and `Transaction.fromHex` methods + +__fixed__ +- Dash special transaction support +- Zcash version 1 extra data + +# 3.6.0 +__added__ +- Added timestamp to `Transaction` object (Capricoin) + # 3.2.0 __added__ - Added `address.fromBech32/toBech32` (#846) diff --git a/README.md b/README.md index b2c5e85cb..322b13397 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ -This is a fork of https://github.com/bitcoinjs/bitcoinjs-lib with very simple support for zcash (parsing transactions + multibyte addresses). +Deprecation +=== -You should not use this if you don't have to support zcash, since it's not as well maintained and tested as bitcoin.js. +Do not use this -Read README at original bitcoinjs-lib - -## versions - -Version 3.3.2 is rebased on upstream 3.3.2 - -## LICENSE [MIT](LICENSE) +Use https://github.com/BitGo/bitgo-utxo-lib/ diff --git a/package.json b/package.json index c85725492..eae16524f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.4.2", - "description": "Client-side Bitcoin JavaScript library with simple zcash support", + "version": "3.6.1", + "description": "Client-side Bitcoin JavaScript library with simple zcash and capricoin support", "main": "./src/index.js", "engines": { "node": ">=4.0.0" @@ -9,6 +9,8 @@ "keywords": [ "bitcoinjs", "bitcoin", + "zcash", + "capricoin", "browserify", "javascript", "bitcoinjs" @@ -44,7 +46,7 @@ "pushdata-bitcoin": "^1.0.1", "randombytes": "^2.0.1", "safe-buffer": "^5.0.1", - "typeforce": "^1.11.3", + "typeforce": "1.11.3", "varuint-bitcoin": "^1.0.4", "wif": "^2.0.1" }, diff --git a/src/coins.js b/src/coins.js new file mode 100644 index 000000000..3820f3fba --- /dev/null +++ b/src/coins.js @@ -0,0 +1,62 @@ +// Coins supported by bitgo-bitcoinjs-lib +const typeforce = require('typeforce') + +const coins = { + BCH: 'bch', + BSV: 'bsv', + BTC: 'btc', + BTG: 'btg', + LTC: 'ltc', + ZEC: 'zec', + DASH: 'dash', + DIGIBYTE: 'dgb', + DOGECOIN: 'doge', + NAMECOIN: 'nmc', + VERTCOIN: 'vtc', + CAPRICOIN: 'cpc' +} + +coins.isBitcoin = function (network) { + return typeforce.value(coins.BTC)(network.coin) +} + +coins.isBitcoinCash = function (network) { + return typeforce.value(coins.BCH)(network.coin) +} + +coins.isBitcoinSV = function (network) { + return typeforce.value(coins.BSV)(network.coin) +} + +coins.isBitcoinGold = function (network) { + return typeforce.value(coins.BTG)(network.coin) +} + +coins.isLitecoin = function (network) { + return typeforce.value(coins.LTC)(network.coin) +} + +coins.isZcash = function (network) { + return typeforce.value(coins.ZEC)(network.coin) +} + +coins.isDash = function (network) { + return typeforce.value(coins.DASH)(network.coin) +} + +coins.isCapricoin = function (network) { + return typeforce.value(coins.CAPRICOIN)(network.coin) +} + +coins.isValidCoin = typeforce.oneOf( + coins.isBitcoin, + coins.isBitcoinCash, + coins.isBitcoinSV, + coins.isBitcoinGold, + coins.isLitecoin, + coins.isZcash, + coins.isDash, + coins.isCapricoin +) + +module.exports = coins diff --git a/src/networks.js b/src/networks.js index 43cd5fc66..dc0ba0cde 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,5 +1,6 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 +var coins = require('./coins') module.exports = { bitcoin: { @@ -11,7 +12,8 @@ module.exports = { }, pubKeyHash: 0x00, scriptHash: 0x05, - wif: 0x80 + wif: 0x80, + coin: coins.BTC }, testnet: { messagePrefix: '\x18Bitcoin Signed Message:\n', @@ -22,7 +24,8 @@ module.exports = { }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef + wif: 0xef, + coin: coins.BTC }, litecoin: { messagePrefix: '\x19Litecoin Signed Message:\n', @@ -32,6 +35,79 @@ module.exports = { }, pubKeyHash: 0x30, scriptHash: 0x32, - wif: 0xb0 + wif: 0xb0, + coin: coins.LTC + }, + dash: { + messagePrefix: '\x19DarkCoin Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x4c, // https://dash-docs.github.io/en/developer-reference#opcodes + scriptHash: 0x10, + wif: 0xcc, + coin: coins.DASH + }, + dashTest: { + messagePrefix: '\x19DarkCoin Signed Message:\n', + bip32: { + public: 0x043587cf, + private: 0x04358394 + }, + pubKeyHash: 0x8c, // https://dash-docs.github.io/en/developer-reference#opcodes + scriptHash: 0x13, + wif: 0xef, // https://github.com/dashpay/godashutil/blob/master/wif.go#L72 + coin: coins.DASH + }, + zcash: { + messagePrefix: '\x18ZCash Signed Message:\n', + bech32: 'bc', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x1cb8, + scriptHash: 0x1cbd, + wif: 0x80, + // This parameter was introduced in version 3 to allow soft forks, for version 1 and 2 transactions we add a + // dummy value. + consensusBranchId: { + 1: 0x00, + 2: 0x00, + 3: 0x5ba81b19, + 4: 0x76b809bb + }, + coin: coins.ZEC + }, + zcashTest: { + messagePrefix: '\x18ZCash Signed Message:\n', + bech32: 'tb', + bip32: { + public: 0x043587cf, + private: 0x04358394 + }, + pubKeyHash: 0x1d25, + scriptHash: 0x1cba, + wif: 0xef, + consensusBranchId: { + 1: 0x00, + 2: 0x00, + 3: 0x5ba81b19, + 4: 0x76b809bb + }, + coin: coins.ZEC + }, + capricoin: { + messagePrefix: '\x18Capricoin Signed Message:\n', + bech32: null, + bip32: { + public: 76067358, + private: 76066276 + }, + pubKeyHash: 28, + scriptHash: 35, + wif: 0xef, + coin: coins.CAPRICOIN } } diff --git a/src/transaction.js b/src/transaction.js index 825ace070..97b24a8a5 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -7,6 +7,8 @@ var opcodes = require('bitcoin-ops') var typeforce = require('typeforce') var types = require('./types') var varuint = require('varuint-bitcoin') +var networks = require('./networks') +var coins = require('./coins') function varSliceSize (someScript) { var length = someScript.length @@ -22,15 +24,21 @@ function vectorSize (someVector) { }, 0) } -function Transaction () { +function Transaction (network) { this.version = 3 this.locktime = 0 + this.timestamp = 0 // capricoin specific + this.network = network || networks.zcash this.ins = [] this.outs = [] - this.joinsplits = [] - this.versionGroupId = '0x03c48270' - this.expiry = 0 - this.zcash = true + this.joinsplits = [] // zcash specific + this.versionGroupId = '0x03c48270' // zcash specific + this.expiry = 0 // zcash specific + this.spendDescs = []; // zcash specific + this.outputDescs = []; // zcash specific + this.dashType = 0 // dash specific + this.dashPayload = 0 // dash specific + this.invalidTransaction = false; } Transaction.DEFAULT_SEQUENCE = 0xffffffff @@ -51,6 +59,9 @@ var BLANK_OUTPUT = { valueBuffer: VALUE_UINT64_MAX } +Transaction.ZCASH_OVERWINTER_VERSION = 3 +Transaction.ZCASH_SAPLING_VERSION = 4 +Transaction.ZCASH_JOINSPLITS_SUPPORT_VERSION = 2 Transaction.ZCASH_NUM_JS_INPUTS = 2 Transaction.ZCASH_NUM_JS_OUTPUTS = 2 Transaction.ZCASH_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16 @@ -58,13 +69,37 @@ Transaction.ZCASH_NOTECIPHERTEXT_SIZE = 1 + 8 + 32 + 32 + 512 + 16 Transaction.ZCASH_G1_PREFIX_MASK = 0x02 Transaction.ZCASH_G2_PREFIX_MASK = 0x0a +Transaction.DASH_NORMAL = 0 +Transaction.DASH_PROVIDER_REGISTER = 1 +Transaction.DASH_PROVIDER_UPDATE_SERVICE = 2 +Transaction.DASH_PROVIDER_UPDATE_REGISTRAR = 3 +Transaction.DASH_PROVIDER_UPDATE_REVOKE = 4 +Transaction.DASH_COINBASE = 5 +Transaction.DASH_QUORUM_COMMITMENT = 6 + Transaction.PREVOUTS_HASH_PERSON = new Buffer('ZcashPrevoutHash') Transaction.SEQUENCE_HASH_PERSON = new Buffer('ZcashSequencHash') Transaction.OUTPUTS_HASH_PERSON = new Buffer('ZcashOutputsHash') Transaction.JOINSPLITS_HASH_PERSON = new Buffer('ZcashJSplitsHash') Transaction.OVERWINTER_HASH_PERSON = Buffer.concat([new Buffer('ZcashSigHash'), Buffer.from('191ba85b', 'hex')]) -Transaction.fromBuffer = function (buffer, zcash, __noStrict) { +// Sapling note magic values, copied from src/zcash/Zcash.h +var NOTEENCRYPTION_AUTH_BYTES = 16; +var ZC_NOTEPLAINTEXT_LEADING = 1; +var ZC_V_SIZE = 8; +var ZC_RHO_SIZE = 32; +var ZC_R_SIZE = 32; +var ZC_MEMO_SIZE = 512; +var ZC_DIVERSIFIER_SIZE = 11; +var ZC_JUBJUB_POINT_SIZE = 32; +var ZC_JUBJUB_SCALAR_SIZE = 32; +var ZC_NOTEPLAINTEXT_SIZE = ZC_NOTEPLAINTEXT_LEADING + ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE; +var ZC_SAPLING_ENCPLAINTEXT_SIZE = ZC_NOTEPLAINTEXT_LEADING + ZC_DIVERSIFIER_SIZE + ZC_V_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE; +var ZC_SAPLING_OUTPLAINTEXT_SIZE = ZC_JUBJUB_POINT_SIZE + ZC_JUBJUB_SCALAR_SIZE; +var ZC_SAPLING_ENCCIPHERTEXT_SIZE = ZC_SAPLING_ENCPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES; +var ZC_SAPLING_OUTCIPHERTEXT_SIZE = ZC_SAPLING_OUTPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES; + +Transaction.fromBuffer = function (buffer, network, __noStrict) { var offset = 0 function readSlice (n) { offset += n @@ -130,27 +165,55 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { } } + // zcash sapling + function readSpentDesc () { + var res = {}; + res.cv = readSlice(32); + res.anchor = readSlice(32); + res.nullifier = readSlice(32); + res.rk = readSlice(32); + res.proof = readSlice(48 + 96 + 48); + res.spendAuthSig = readSlice(64); + return res; + } + + function readOutputDesc () { + var res = {}; + res.cv = readSlice(32); + res.cmu = readSlice(32); + res.ephemeralKey = readSlice(32); + res.encCipherText = readSlice(ZC_SAPLING_ENCCIPHERTEXT_SIZE); + res.outCipherText = readSlice(ZC_SAPLING_OUTCIPHERTEXT_SIZE); + res.proof = readSlice(48 + 96 + 48); + return res; + } + var tx = new Transaction() + tx.network = network || networks.bitcoin + tx.version = readInt32() - if (zcash) { - var header = readUInt32() - tx.version = header & 0x7fffffff - var overwintered = header >>> 31 + if (coins.isZcash(tx.network)) { + var overwintered = tx.version >>> 31 + tx.version = tx.version & 0x7fffffff if (tx.version >= 3) { if (!overwintered) { throw new Error('zcash tx v3+ not overwintered') } tx.versionGroupId = readUInt32() } - } else { - tx.version = readInt32() + } else if(coins.isDash(tx.network)) { + tx.dashType = tx.version >> 16 + tx.version = tx.version & 0xffff + if (tx.version === 3 && (tx.dashType < Transaction.DASH_NORMAL || tx.dashType > Transaction.DASH_QUORUM_COMMITMENT)) { + throw new Error('Unsupported Dash transaction type') + } } var marker = buffer.readUInt8(offset) var flag = buffer.readUInt8(offset + 1) var hasWitnesses = false - if (!zcash) { + if (!coins.isZcash(tx.network)) { if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG) { offset += 2 @@ -158,6 +221,10 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { } } + if (coins.isCapricoin(tx.network)) { + tx.timestamp = readUInt32() + } + var vinLen = readVarInt() for (var i = 0; i < vinLen; ++i) { tx.ins.push({ @@ -188,11 +255,26 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { tx.locktime = readUInt32() - if (tx.version >= 3 && zcash) { + if (tx.isOverwinterCompatible()) { tx.expiry = readUInt32() } - if (tx.version >= 2 && zcash) { + if (tx.isSaplingCompatible()) { + tx.valueBalance = readUInt64(); + var sizeSpendDescs = readVarInt(); + for (var i = 0; i < sizeSpendDescs; i++) { + var spend = readSpentDesc(); + tx.spendDescs.push(spend); + } + + var sizeOutputDescs = readVarInt(); + for (var i = 0; i < sizeOutputDescs; i++) { + var output = readOutputDesc(); + tx.outputDescs.push(output); + } + } + + if (tx.supportsJoinSplits()) { var jsLen = readVarInt() for (i = 0; i < jsLen; ++i) { var vpubOld = readUInt64() @@ -212,16 +294,24 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { macs.push(readSlice(32)) } - // TODO what are those exactly? Can it be expressed by BigNum? - var zproof = { - gA: readCompressedG1(), - gAPrime: readCompressedG1(), - gB: readCompressedG2(), - gBPrime: readCompressedG1(), - gC: readCompressedG1(), - gCPrime: readCompressedG1(), - gK: readCompressedG1(), - gH: readCompressedG1() + var zproof = {}; + if (tx.version <= 3) { + zproof = { + gA: readCompressedG1(), + gAPrime: readCompressedG1(), + gB: readCompressedG2(), + gBPrime: readCompressedG1(), + gC: readCompressedG1(), + gCPrime: readCompressedG1(), + gK: readCompressedG1(), + gH: readCompressedG1() + } + } else { + zproof = { + sA: readSlice(48), + sB: readSlice(96), + sC: readSlice(48) + } } var ciphertexts = [] for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { @@ -245,9 +335,15 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { tx.joinsplitPubkey = readSlice(32) tx.joinsplitSig = readSlice(64) } + if (tx.isSaplingCompatible() && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { + tx.bindingSig = readSlice(64); + } } + - tx.zcash = !!zcash + if (tx.isDashSpecialTransaction()) { + tx.dashPayload = readVarSlice() + } if (__noStrict) return tx if (offset !== buffer.length) throw new Error('Transaction has unexpected data') @@ -255,8 +351,8 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { return tx } -Transaction.fromHex = function (hex, zcash) { - return Transaction.fromBuffer(new Buffer(hex, 'hex'), zcash) +Transaction.fromHex = function (hex, network) { + return Transaction.fromBuffer(new Buffer(hex, 'hex'), network) } Transaction.isCoinbaseHash = function (buffer) { @@ -324,54 +420,103 @@ Transaction.prototype.byteLength = function () { } Transaction.prototype.joinsplitByteLength = function () { - if (this.version < 2) { - return 0 - } - if (!this.zcash) { - return 0 + var joinSplitsLen = this.joinsplits.length + var byteLength = 0 + byteLength += bufferutils.varIntSize(joinSplitsLen) // vJoinSplit + + if (joinSplitsLen > 0) { + // Both pre and post Sapling JoinSplits are encoded with the following data: + // 8 vpub_old, 8 vpub_new, 32 anchor, joinSplitsLen * 32 nullifiers, joinSplitsLen * 32 commitments, 32 ephemeralKey + // 32 ephemeralKey, 32 randomSeed, joinsplit.macs.length * 32 vmacs + if (this.isSaplingCompatible()) { + byteLength += 1698 * joinSplitsLen // vJoinSplit using JSDescriptionGroth16 + } else { + byteLength += 1802 * joinSplitsLen // vJoinSplit using JSDescriptionPHGR13 + } + byteLength += 32 // joinSplitPubKey + byteLength += 64 // joinSplitSig } - var pubkeySigLength = (this.joinsplits.length > 0) ? (32 + 64) : 0 - return ( - bufferutils.varIntSize(this.joinsplits.length) + - this.joinsplits.reduce(function (sum, joinsplit) { - return ( - sum + - 8 + 8 + 32 + - joinsplit.nullifiers.length * 32 + - joinsplit.commitments.length * 32 + - 32 + 32 + - joinsplit.macs.length * 32 + - 65 + 33 * 7 + - joinsplit.ciphertexts.length * Transaction.ZCASH_NOTECIPHERTEXT_SIZE - ) - }, 0) + - pubkeySigLength - ) + return byteLength +} + +Transaction.prototype.spendDescsByteLength = function () { + var byteLength = 0 + byteLength += varuint.encodingLength(this.spendDescs.length) // nShieldedSpend + byteLength += (384 * this.spendDescs.length) // vShieldedSpend + return byteLength +} + +Transaction.prototype.outputDescsByteLength = function () { + var byteLength = 0 + byteLength += varuint.encodingLength(this.outputDescs.length) // nShieldedOutput + byteLength += (948 * this.outputDescs.length) // vShieldedOutput + return byteLength +} + +Transaction.prototype.zcashTransactionByteLength = function() { + if (!coins.isZcash(this.network)) { + throw new Error('zcashTransactionByteLength can only be called when using Zcash network') + } + var byteLength = 0 + byteLength += 4 // Header + if (this.isOverwinterCompatible()) { + byteLength += 4 // nVersionGroupId + } + byteLength += varuint.encodingLength(this.ins.length) // tx_in_count + byteLength += this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) // tx_in + byteLength += varuint.encodingLength(this.outs.length) // tx_out_count + byteLength += this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) // tx_out + byteLength += 4 // lock_time + if (this.isOverwinterCompatible()) { + byteLength += 4 // nExpiryHeight + } + if (this.isSaplingCompatible()) { + byteLength += 8 // valueBalance + byteLength += this.spendDescsByteLength() + byteLength += this.outputDescsByteLength() + } + if (this.supportsJoinSplits()) { + byteLength += this.joinsplitByteLength() + } + if (this.isSaplingCompatible() && + this.spendDescs.length + this.outputDescs.length > 0) { + byteLength += 64 // bindingSig + } + return byteLength } Transaction.prototype.__byteLength = function (__allowWitness) { + if (coins.isZcash(this.network)) { + return this.zcashTransactionByteLength(); + } + var hasWitnesses = __allowWitness && this.hasWitnesses() return ( (hasWitnesses ? 10 : 8) + + (this.timestamp ? 4 : 0) + varuint.encodingLength(this.ins.length) + varuint.encodingLength(this.outs.length) + this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) + this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) + - (hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0) + - this.joinsplitByteLength() + - (this.version === 3 ? 12 : 0) + (this.isDashSpecialTransaction() ? varSliceSize(this.dashPayload) : 0) + + (hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0) ) } +// note - this is not updated for sapling and overwinter, do not use anywhere Transaction.prototype.clone = function () { var newTx = new Transaction() newTx.version = this.version newTx.locktime = this.locktime - newTx.zcash = this.zcash - if (newTx.zcash) { + newTx.timestamp = this.timestamp + newTx.network = this.network + newTx.dashType = this.dashType + newTx.dashPayload = this.dashPayload + newTx.invalidTransaction = this.invalidTransaction + if (coins.isZcash(newTx.network)) { newTx.versionGroupId = this.versionGroupId newTx.expiry = this.expiry } @@ -675,6 +820,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne function writeSlice (slice) { offset += slice.copy(buffer, offset) } function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) } function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) } + function writeInt16 (i) { offset = buffer.writeInt16LE(i, offset) } function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) } function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) } function writeVarInt (i) { @@ -694,9 +840,31 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(i.x) } - if (this.version >= 3 && this.zcash) { + // zcash sapling + function writeSpentDesc (i) { + writeSlice(i.cv); + writeSlice(i.anchor); + writeSlice(i.nullifier); + writeSlice(i.rk); + writeSlice(i.proof); + writeSlice(i.spendAuthSig); + } + + function writeOutputDesc (i) { + writeSlice(i.cv); + writeSlice(i.cmu); + writeSlice(i.ephemeralKey); + writeSlice(i.encCipherText); + writeSlice(i.outCipherText); + writeSlice(i.proof); + } + + if (this.isOverwinterCompatible()) { writeInt32(this.version | (1 << 31)) writeUInt32(this.versionGroupId) + } else if(this.isDashSpecialTransaction()) { + writeInt16(this.version) + writeInt16(this.dashType) } else { writeInt32(this.version) } @@ -708,6 +876,10 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) } + if (this.timestamp) { + writeUInt32(this.timestamp) + } + writeVarInt(this.ins.length) this.ins.forEach(function (txIn) { @@ -736,12 +908,25 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt32(this.locktime) - if (this.version >= 3 && this.zcash) { + if (this.isOverwinterCompatible()) { writeUInt32(this.expiry) } - if (this.version >= 2 && this.zcash) { + if (this.isSaplingCompatible()) { + writeUInt64(this.valueBalance); + writeVarInt(this.spendDescs.length); + for (var i = 0; i < this.spendDescs.length; i++) { + writeSpentDesc(this.spendDescs[i]); + } + writeVarInt(this.outputDescs.length); + for (var i = 0; i < this.outputDescs.length; i++) { + writeOutputDesc(this.outputDescs[i]); + } + } + + if (this.supportsJoinSplits()) { writeVarInt(this.joinsplits.length) + var version = this.version; this.joinsplits.forEach(function (joinsplit) { writeUInt64(joinsplit.vpubOld) writeUInt64(joinsplit.vpubNew) @@ -757,14 +942,20 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne joinsplit.macs.forEach(function (nullifier) { writeSlice(nullifier) }) - writeCompressedG1(joinsplit.zproof.gA) - writeCompressedG1(joinsplit.zproof.gAPrime) - writeCompressedG2(joinsplit.zproof.gB) - writeCompressedG1(joinsplit.zproof.gBPrime) - writeCompressedG1(joinsplit.zproof.gC) - writeCompressedG1(joinsplit.zproof.gCPrime) - writeCompressedG1(joinsplit.zproof.gK) - writeCompressedG1(joinsplit.zproof.gH) + if (version <= 3) { + writeCompressedG1(joinsplit.zproof.gA) + writeCompressedG1(joinsplit.zproof.gAPrime) + writeCompressedG2(joinsplit.zproof.gB) + writeCompressedG1(joinsplit.zproof.gBPrime) + writeCompressedG1(joinsplit.zproof.gC) + writeCompressedG1(joinsplit.zproof.gCPrime) + writeCompressedG1(joinsplit.zproof.gK) + writeCompressedG1(joinsplit.zproof.gH) + } else { + writeSlice(joinsplit.zproof.sA) + writeSlice(joinsplit.zproof.sB) + writeSlice(joinsplit.zproof.sC) + } joinsplit.ciphertexts.forEach(function (ciphertext) { writeSlice(ciphertext) }) @@ -773,6 +964,13 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(this.joinsplitPubkey) writeSlice(this.joinsplitSig) } + if (this.isSaplingCompatible() && ((this.spendDescs.length + this.outputDescs.length) > 0)) { + writeSlice(this.bindingSig); + } + } + + if (this.isDashSpecialTransaction()) { + writeVarSlice(this.dashPayload) } // avoid slicing unless necessary @@ -796,4 +994,39 @@ Transaction.prototype.setWitness = function (index, witness) { this.ins[index].witness = witness } +Transaction.prototype.getExtraData = function () { + if (this.supportsJoinSplits()) { + var buffer = this.toBuffer() + var joinsplitByteLength = this.joinsplitByteLength() + var res = buffer.slice(buffer.length - joinsplitByteLength) + return res + } + // if (coins.isDash(this.network) && this.dashPayload) { + if (this.isDashSpecialTransaction()) { + var extraDataLength = varuint.encode(this.dashPayload.length) + return Buffer.concat([extraDataLength, this.dashPayload]); + } + return null +} + +Transaction.prototype.isZcashTransaction = function () { + return coins.isZcash(this.network) +} + +Transaction.prototype.isSaplingCompatible = function () { + return coins.isZcash(this.network) && this.version >= Transaction.ZCASH_SAPLING_VERSION +} + +Transaction.prototype.isOverwinterCompatible = function () { + return coins.isZcash(this.network) && this.version >= Transaction.ZCASH_OVERWINTER_VERSION +} + +Transaction.prototype.supportsJoinSplits = function () { + return coins.isZcash(this.network) && this.version >= Transaction.ZCASH_JOINSPLITS_SUPPORT_VERSION +} + +Transaction.prototype.isDashSpecialTransaction = function () { + return coins.isDash(this.network) && this.version === 3 && this.dashType !== Transaction.DASH_NORMAL +} + module.exports = Transaction diff --git a/types.js b/types.js new file mode 100644 index 000000000..07993709b --- /dev/null +++ b/types.js @@ -0,0 +1,384 @@ +/* @flow */ + +// TODO import BigInteger from 'bigi' +// TODO import Point from 'ecurve' +// For now, I just copy-paste the definition from there + +// ---------- copypasta start ---- +declare class $npm$bigi$BigInteger { + constructor(input: string | Array, base?: number): void, + static (input: string | Array, base?: number): $npm$bigi$BigInteger, + + toString(base?: number): string, + toByteArray(): Array, + bitLength(): number, + byteLength(): number, + add(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + subtract(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + multiply(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + divide(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + mod(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + modInverse(o: $npm$bigi$BigInteger): $npm$bigi$BigInteger, + shiftLeft(o: number): $npm$bigi$BigInteger, + shiftRight(o: number): $npm$bigi$BigInteger, + isProbablePrime(): boolean, + + static fromByteArrayUnsigned(array: Array): $npm$bigi$BigInteger, + static fromBuffer(buffer: Buffer): $npm$bigi$BigInteger, + static fromDERInteger(buffer: Buffer): $npm$bigi$BigInteger, + static fromHex(hex: string): $npm$bigi$BigInteger, + + toByteArrayUnsigned(): Array, + toBuffer(): Buffer, + toDERInteger(): Buffer, + toHex(): string, +} + +declare module 'bigi' { + declare export default typeof $npm$bigi$BigInteger; +} + +declare class $npm$ecurve$Curve { + p: $npm$bigi$BigInteger, + a: $npm$bigi$BigInteger, + b: $npm$bigi$BigInteger, + G: $npm$ecurve$Point, + n: $npm$bigi$BigInteger, + h: $npm$bigi$BigInteger, + + constructor( + p: $npm$bigi$BigInteger, + a: $npm$bigi$BigInteger, + b: $npm$bigi$BigInteger, + Gx: $npm$bigi$BigInteger, + Gy: $npm$bigi$BigInteger, + n: $npm$bigi$BigInteger, + h: $npm$bigi$BigInteger + ): void, + + infinity: $npm$ecurve$Point, + isInfinity(point: $npm$ecurve$Point): boolean, + validate(a: $npm$ecurve$Point): boolean, + isOnCurve(a: $npm$ecurve$Point): boolean, + pointFromX(odd: boolean, x: $npm$ecurve$Point): $npm$ecurve$Point, +} + +declare class $npm$ecurve$Point { + constructor( + curve: $npm$ecurve$Curve, + x: $npm$bigi$BigInteger, + y: $npm$bigi$BigInteger, + z: $npm$bigi$BigInteger + ): void, + + x: $npm$bigi$BigInteger, + y: $npm$bigi$BigInteger, + z: $npm$bigi$BigInteger, + + zInv: $npm$bigi$BigInteger, + affineX: $npm$bigi$BigInteger, + affineY: $npm$bigi$BigInteger, + + static fromAffine(curve: $npm$ecurve$Curve, x: $npm$bigi$BigInteger, y: $npm$bigi$BigInteger): $npm$ecurve$Point, + equals(other: $npm$ecurve$Point): boolean, + negate(): $npm$ecurve$Point, + add(other: $npm$ecurve$Point): $npm$ecurve$Point, + twice(): $npm$ecurve$Point, + multiply(k: $npm$bigi$BigInteger): $npm$ecurve$Point, + multiplyTwo(j: $npm$bigi$BigInteger, x: $npm$ecurve$Point, k: $npm$bigi$BigInteger): $npm$ecurve$Point, + + static decodeFrom(curve: $npm$ecurve$Curve, buffer: Buffer): $npm$ecurve$Point, + getEncoded(compressed: boolean): Buffer, + + toString(): string, +} + +declare module 'ecurve' { + declare var Point: typeof $npm$ecurve$Point; + + declare var Curve: typeof $npm$ecurve$Curve; + + declare function getCurveByName(name: string): ?Curve; +} +// ---------- copypasta end ---- + +declare module 'bitcoinjs-lib-zcash' { + declare type Network = { + messagePrefix: string, + bip32: { + public: number, + private: number, + }, + pubKeyHash: number, + scriptHash: number, + wif: number, + dustThreshold: number, + bech32: ?string, + coin: string, + } + + declare type Output = { + script: Buffer, + value: number, + }; + + declare type Input = { + script: Buffer, + hash: Buffer, + index: number, + sequence: number, + }; + + declare var address: { + fromBase58Check(address: string): {hash: Buffer, version: number}, + fromBech32(address: string): {data: Buffer, version: number, prefix: string}, + fromOutputScript(script: Buffer, network?: Network): string, + toBase58Check(hash: Buffer, version: number): string, + toOutputScript(address: string, network?: Network): Buffer, + }; + + declare type Stack = Array; + + declare var script: { + scriptHash: { + input: { + check: (script: Buffer, allowIncomplete: boolean) => boolean, + decode: (script: Buffer) => { + redeemScriptStack: Stack, + redeemScript: Buffer, + }, + encode: (redeemScriptSig: Buffer, redeemScript: Buffer) => Buffer, + }, + output: { + check: (script: Stack) => boolean, + encode: (scriptHash: Buffer) => Buffer, + decode: (script: Buffer) => Buffer, + }, + }, + pubKeyHash: { + input: { + check: (script: Buffer, allowIncomplete: boolean) => boolean, + decode: (script: Buffer) => { + signature: Buffer, + pubKey: Buffer, + }, + encode: (signature: Buffer, pubKey: Buffer) => Buffer, + }, + output: { + check: (script: Stack) => boolean, + encode: (pubKeyHash: Buffer) => Buffer, + decode: (script: Buffer) => Buffer, + }, + }, + witnessPubKeyHash: { + input: { + check: (script: Buffer) => boolean, + }, + output: { + check: (script: Stack) => boolean, + encode: (pubkeyHash: Buffer) => Buffer, + decode: (buffer: Buffer) => Buffer, + }, + }, + witnessScriptHash: { + input: { + check: (script: Buffer, allowIncomplete: boolean) => boolean, + }, + output: { + check: (script: Stack) => boolean, + encode: (scriptHash: Buffer) => Buffer, + decode: (script: Buffer) => Buffer, + }, + }, + }; + + declare var crypto: { + sha1(buffer: Buffer): Buffer, + sha256(buffer: Buffer): Buffer, + hash256(buffer: Buffer): Buffer, + hash160(buffer: Buffer): Buffer, + ripemd160(buffer: Buffer): Buffer, + } + + declare type ECPairOptions = { + compressed?: boolean, + network?: Network, + } + + declare class ECPair { + d: ?$npm$bigi$BigInteger, + Q: $npm$ecurve$Point, + compressed: boolean, + network: Network, + getNetwork(): Network, + + constructor(d: ?$npm$bigi$BigInteger, Q: ?$npm$ecurve$Point, options: ECPairOptions): void, + getAddress(): string, + getPublicKeyBuffer(): Buffer, + static fromPublicKeyBuffer(buffer: Buffer): ECPair, + verify: (hash: Buffer, signature: ECSignature) => boolean, + sign(hash: Buffer): Buffer, + toWIF(): string, + static fromWIF(string: string, network: Network): ECPair, + static makeRandom(): ECPair, + } + + declare class HDNode { + depth: number, + parentFingerprint: number, + index: number, + keyPair: ECPair, + chainCode: Buffer, + static fromBase58( + str: string, + networks: ?(Array | Network) + ): HDNode, + derive(index: number): HDNode, + deriveHardened(index: number): HDNode, + derivePath(path: string): HDNode, + toBase58(): string, + getAddress(): string, + getFingerprint(): Buffer, + getIdentifier(): Buffer, + getNetwork(): Network, + constructor(keyPair: ECPair, chainCode: Buffer): void, + + static fromBase58(base: string, network?: ?(Network | Array), skipValidation?: boolean): HDNode, + static fromSeedHex(seed: string, network?: ?Network): HDNode, + static fromSeedBuffer(seed: Buffer, network?: ?Network): HDNode, + getPublicKeyBuffer(): Buffer, + + sign(): ECSignature, + verify(hash: Buffer, signature: ECSignature): Buffer, + neutered(): HDNode, + isNeutered(): boolean, + constructor(keyPair: ECPair, chainCode: Buffer): void, + static HIGHEST_BIT: number, + } + + declare class Transaction { + version: number, + locktime: number, + timestamp?: number, + ins: Array, + outs: Array, + versionGroupId: string, + expiry: number, + dashType: number, + dashPayload: number, + invalidTransaction: boolean, + + constructor(network?: ?Network): void, + static fromHex(hex: string, network: ?Network): Transaction, + static fromBuffer(buffer: Buffer, network: ?Network, __noStrict?: boolean): Transaction, + toHex(): string, + addInput(hash: Buffer, index: number, sequence?: ?number, scriptSig?: Buffer): void, + addOutput(scriptPubKey: Buffer, value: number): void, + getHash(): Buffer, + toBuffer(): Buffer, + toHex(): string, + getId(): string, + + static isCoinbaseHash(buffer: Buffer): boolean, + isCoinbase(): boolean, + byteLength(): number, + + joinsplitByteLength(): number, + joinsplits: Array, + + clone(): Transaction, + hashForSignature(inIndex: number, prevOutScript: Buffer, hashType: number): Buffer, + setInputScript(index: number, scriptSig: Buffer): void, + getExtraData(): ?Buffer, + isDashSpecialTransaction(): boolean, + isZcashTransaction(): boolean, + } + + declare class TransactionBuilder { + network: Network, + inputs: Array, + tx: Transaction, + + setLockTime(locktime: number): void, + setVersion(version: number): void, + addInput(txhash: string | Transaction | Buffer, vout: number, sequence?: number, prevOutScript?: Buffer): void, + addOutput(scriptPubKey: string | Buffer, value: number): void, + build(): Transaction, + buildIncomplete(): Transaction, + sign(index: number, keyPair: ECPair, redeemScript: Buffer, hashType: number): void, + + static fromTransaction(transaction: Transaction, network: ?Network): TransactionBuilder, + + } + + declare var networks: {[key: string]: Network} + declare var opcodes: {[key: string]: number} + + declare class ECSignature { + r: $npm$bigi$BigInteger, + s: $npm$bigi$BigInteger, + constructor(r: $npm$bigi$BigInteger, s: $npm$bigi$BigInteger): void, + + static parseCompact(buffer: Buffer): { + compressed: boolean, + i: number, + signature: Buffer, + }, + + static fromDER(buffer: Buffer): ECSignature, + static parseScriptSignature(buffer: Buffer): { + signature: ECSignature, + hashType: number, + }, + + toCompact(i: number, compressed: boolean): Buffer, + toDER(): Buffer, + toScriptSignature(hashType: number): Buffer, + } + + declare class Block { + version: number, + prevHash: Buffer, + merkleRoot: Buffer, + timestamp: number, + bits: number, + nonce: number, + + getHash(): Buffer, + getId(): string, + getUTCDate(): Date, + toBuffer(headersOnly?: boolean): Buffer, + toHex(headersOnly?: boolean): string, + calculateTarget(bits: number): Buffer, + checkProofOfWork(): boolean, + + static fromBuffer(buffer: Buffer): Block, + static fromHex(hex: string): Block, + } + + declare var bufferutils: { + equal(a: Buffer, b: Buffer): boolean, + pushDataSize(i: number): number, + readPushDataInt(buffer: Buffer, offset: number): { + opcode: number, + number: number, + size: number, + }, + readUInt64LE(buffer: Buffer, offset: number): number, + readVarInt(buffer: Buffer, offset: number): { + number: number, + size: number, + }, + varIntBuffer(i: number): Buffer, + varIntSize(i: number): number, + writePushDataInt(buffer: Buffer, number: number, offset: number): number, + writeUInt64LE(buffer: Buffer, value: number, offset: number): void, + writeVarInt(buffer: Buffer, number: number, offset: number): number, + } + + declare var message: { + magicHash(message: Buffer | string, network: Network): Buffer, + sign(pair: ECPair, message: Buffer | string, network: Network): Buffer, + verify(address: string, signature: Buffer, message: Buffer | string, network: Network): boolean, + } +} \ No newline at end of file