From c69395d5d2c376820db0e569b15d9efe60c4123d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20B=C3=ADlek?= Date: Tue, 21 Aug 2018 12:46:15 +0700 Subject: [PATCH 01/26] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index b2c5e85cb..b29f716cb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +Deprecation +=== + +Just for information + +After some testing, I will deprecate this library and we will start using this one + +https://github.com/trezor/bitcoinjs-trezor + +Unlike this one, bitcoinjs-trezor has only the functions that we use in Trezor web wallet, but does NOT have functions that we don't use. Specifically, it does not have transaction signing, since that happens in Trezor. + +You are free to fork this library and keep maintaining it (if it is in npm, I will link it in readme) + +Original readme +=== + This is a fork of https://github.com/bitcoinjs/bitcoinjs-lib with very simple support for zcash (parsing transactions + multibyte addresses). 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. From 91b78266163f745c7053fae683f9a66a474a42b1 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sat, 27 Oct 2018 16:36:49 +0700 Subject: [PATCH 02/26] Add sapling support --- src/transaction.js | 236 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 193 insertions(+), 43 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 825ace070..667580872 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -31,6 +31,8 @@ function Transaction () { this.versionGroupId = '0x03c48270' this.expiry = 0 this.zcash = true + this.spendDescs = []; + this.outputDescs = []; } Transaction.DEFAULT_SEQUENCE = 0xffffffff @@ -64,6 +66,22 @@ 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')]) +// 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, zcash, __noStrict) { var offset = 0 function readSlice (n) { @@ -130,6 +148,29 @@ 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() if (zcash) { @@ -192,6 +233,23 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { tx.expiry = readUInt32() } + if (tx.version >= 4 && zcash) { + tx.valueBalance = readUInt64(); + var sizeSpendDescs = readVarInt(); + console.log(sizeSpendDescs); + for (var i = 0; i < sizeSpendDescs; i++) { + var spend = readSpentDesc(); + tx.spendDescs.push(spend); + } + + var sizeOutputDescs = readVarInt(); + console.log(sizeOutputDescs); + for (var i = 0; i < sizeOutputDescs; i++) { + var output = readOutputDesc(); + tx.outputDescs.push(output); + } + } + if (tx.version >= 2 && zcash) { var jsLen = readVarInt() for (i = 0; i < jsLen; ++i) { @@ -212,16 +270,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,6 +311,9 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { tx.joinsplitPubkey = readSlice(32) tx.joinsplitSig = readSlice(64) } + if (tx.version >= 4 && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { + tx.bindingSig = readSlice(64); + } } tx.zcash = !!zcash @@ -324,34 +393,76 @@ Transaction.prototype.byteLength = function () { } Transaction.prototype.joinsplitByteLength = function () { - if (this.version < 2) { - return 0 + var isSaplingCompatible = this.version >= 4 + + 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 (isSaplingCompatible) { + byteLength += 1698 * joinSplitsLen // vJoinSplit using JSDescriptionGroth16 + } else { + byteLength += 1802 * joinSplitsLen // vJoinSplit using JSDescriptionPHGR13 + } + byteLength += 32 // joinSplitPubKey + byteLength += 64 // joinSplitSig } - if (!this.zcash) { - return 0 - } + return byteLength +} - 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 - ) +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() { + var isOverwinterCompatible = this.version >= 3 + var isSaplingCompatible = this.version >= 4 + var byteLength = 0 + byteLength += 4 // Header + if (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 (isOverwinterCompatible) { + byteLength += 4 // nExpiryHeight + } + if (isSaplingCompatible) { + byteLength += 8 // valueBalance + byteLength += this.spendDescsByteLength() + byteLength += this.outputDescsByteLength() + } + byteLength += this.joinsplitByteLength() + if (isSaplingCompatible && + this.spendDescs.length + this.outputDescs.length > 0) { + byteLength += 64 // bindingSig + } + return byteLength } Transaction.prototype.__byteLength = function (__allowWitness) { + if (this.zcash) { + return this.zcashTransactionByteLength(); + } + var hasWitnesses = __allowWitness && this.hasWitnesses() return ( @@ -360,12 +471,11 @@ Transaction.prototype.__byteLength = function (__allowWitness) { 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) + (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 @@ -694,6 +804,25 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(i.x) } + // 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.version >= 3 && this.zcash) { writeInt32(this.version | (1 << 31)) writeUInt32(this.versionGroupId) @@ -740,6 +869,18 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt32(this.expiry) } + if (this.version >= 4 && this.zcash) { + 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.version >= 2 && this.zcash) { writeVarInt(this.joinsplits.length) this.joinsplits.forEach(function (joinsplit) { @@ -757,14 +898,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 (this.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 +920,9 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(this.joinsplitPubkey) writeSlice(this.joinsplitSig) } + if (this.version >= 4 && ((this.spendDescs.length + this.outputDescs.length) > 0)) { + writeSlice(this.bindingSig); + } } // avoid slicing unless necessary From e994d2cc6473fc19fd8de867cea343345caf8c41 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sat, 27 Oct 2018 16:38:24 +0700 Subject: [PATCH 03/26] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c85725492..bbb797251 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.4.2", + "version": "3.5.0", "description": "Client-side Bitcoin JavaScript library with simple zcash support", "main": "./src/index.js", "engines": { From 9770855920add4268716d2b0b6837754e8a67661 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sat, 27 Oct 2018 23:22:53 +0700 Subject: [PATCH 04/26] Fixing version bug --- src/transaction.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transaction.js b/src/transaction.js index 667580872..4fd67dc7e 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -883,6 +883,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne if (this.version >= 2 && this.zcash) { writeVarInt(this.joinsplits.length) + var version = this.version; this.joinsplits.forEach(function (joinsplit) { writeUInt64(joinsplit.vpubOld) writeUInt64(joinsplit.vpubNew) @@ -898,7 +899,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne joinsplit.macs.forEach(function (nullifier) { writeSlice(nullifier) }) - if (this.version <= 3) { + if (version <= 3) { writeCompressedG1(joinsplit.zproof.gA) writeCompressedG1(joinsplit.zproof.gAPrime) writeCompressedG2(joinsplit.zproof.gB) From bae0a5af51720fd8e99525597bbe2d635d26e6d2 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sat, 27 Oct 2018 23:23:20 +0700 Subject: [PATCH 05/26] version push --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bbb797251..4d4dcdc10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.5.0", + "version": "3.5.1", "description": "Client-side Bitcoin JavaScript library with simple zcash support", "main": "./src/index.js", "engines": { From 6ff88d292ea862e1f905aa71a4e5986228fdd351 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sun, 28 Oct 2018 01:41:19 +0700 Subject: [PATCH 06/26] Remove console logs --- src/transaction.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 4fd67dc7e..4bd99f0d7 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -236,14 +236,12 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { if (tx.version >= 4 && zcash) { tx.valueBalance = readUInt64(); var sizeSpendDescs = readVarInt(); - console.log(sizeSpendDescs); for (var i = 0; i < sizeSpendDescs; i++) { var spend = readSpentDesc(); tx.spendDescs.push(spend); } var sizeOutputDescs = readVarInt(); - console.log(sizeOutputDescs); for (var i = 0; i < sizeOutputDescs; i++) { var output = readOutputDesc(); tx.outputDescs.push(output); From 1f6b7d8689195638555b7c609e8e7af01ff517e1 Mon Sep 17 00:00:00 2001 From: Karel Bilek Date: Sun, 28 Oct 2018 01:45:31 +0700 Subject: [PATCH 07/26] new npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d4dcdc10..04f92feea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.5.1", + "version": "3.5.2", "description": "Client-side Bitcoin JavaScript library with simple zcash support", "main": "./src/index.js", "engines": { From b37e2c50760929ca94953e15496c14446c706aaa Mon Sep 17 00:00:00 2001 From: Adrian Matejov Date: Mon, 19 Nov 2018 10:13:56 +0100 Subject: [PATCH 08/26] update: added timestamp field to transaction hex * Capricoin needs timestamp of transaction to be included in hex and also in signature Signed-off-by: Adrian Matejov --- src/transaction.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 4bd99f0d7..4642a3d1a 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -25,6 +25,7 @@ function vectorSize (someVector) { function Transaction () { this.version = 3 this.locktime = 0 + this.timestamp = 0 this.ins = [] this.outs = [] this.joinsplits = [] @@ -82,7 +83,7 @@ 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, zcash, __noStrict) { +Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { var offset = 0 function readSlice (n) { offset += n @@ -199,6 +200,10 @@ Transaction.fromBuffer = function (buffer, zcash, __noStrict) { } } + if (hasTimestamp) { + tx.timestamp = readUInt32() + } + var vinLen = readVarInt() for (var i = 0; i < vinLen; ++i) { tx.ins.push({ @@ -322,8 +327,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, zcash, hasTimestamp) { + return Transaction.fromBuffer(new Buffer(hex, 'hex'), zcash, hasTimestamp) } Transaction.isCoinbaseHash = function (buffer) { @@ -465,6 +470,7 @@ Transaction.prototype.__byteLength = function (__allowWitness) { 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) + @@ -835,6 +841,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) { From 86eefef6a7a18fd015a7fa8c76c45cb37f9e788a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20B=C3=ADlek?= Date: Thu, 10 Jan 2019 19:49:24 +0700 Subject: [PATCH 09/26] Update README.md --- README.md | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b29f716cb..322b13397 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,6 @@ Deprecation === -Just for information +Do not use this -After some testing, I will deprecate this library and we will start using this one - -https://github.com/trezor/bitcoinjs-trezor - -Unlike this one, bitcoinjs-trezor has only the functions that we use in Trezor web wallet, but does NOT have functions that we don't use. Specifically, it does not have transaction signing, since that happens in Trezor. - -You are free to fork this library and keep maintaining it (if it is in npm, I will link it in readme) - -Original readme -=== - -This is a fork of https://github.com/bitcoinjs/bitcoinjs-lib with very simple support for zcash (parsing transactions + multibyte addresses). - -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. - -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/ From 19c41220f3a667167794a883eafc41d5189077a7 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 14 Feb 2019 09:00:23 +0100 Subject: [PATCH 10/26] add npmignore --- .npmignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..2cab4fb8f --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +# Dev directories +.gitignore +.travis.yml + +# Dependency directory +node_modules +yarn.lock \ No newline at end of file From 1c50fc0375d4b2b2d65fc037051adb356dc783a9 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 14 Feb 2019 09:00:48 +0100 Subject: [PATCH 11/26] version 3.6.0 --- CHANGELOG.md | 4 ++++ package.json | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bd846c9d..a59637992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.6.0 +__added__ +- Added timestamp to `Transaction` object (Capricoin) + # 3.2.0 __added__ - Added `address.fromBech32/toBech32` (#846) diff --git a/package.json b/package.json index 04f92feea..17d3b3ab0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.5.2", - "description": "Client-side Bitcoin JavaScript library with simple zcash support", + "version": "3.6.0", + "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" From a14d827b9049e55a100345349f1842b337a26520 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 14 Feb 2019 09:17:43 +0100 Subject: [PATCH 12/26] Update .npmignore --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index 2cab4fb8f..77d425745 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,8 @@ # Dev directories .gitignore .travis.yml +.nyc_output +coverage # Dependency directory node_modules From eef0e31f87f62396a455487aaeaa7f2251e891b0 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 6 Mar 2019 12:09:00 +0100 Subject: [PATCH 13/26] change "zcash" field to "network" --- src/coins.js | 62 +++++++++ src/networks.js | 82 ++++++++++- src/transaction.js | 333 +++++++++++++++++++++++++-------------------- 3 files changed, 328 insertions(+), 149 deletions(-) create mode 100644 src/coins.js diff --git a/src/coins.js b/src/coins.js new file mode 100644 index 000000000..55d5737ca --- /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 4642a3d1a..4a0cd1165 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,18 +24,20 @@ function vectorSize (someVector) { }, 0) } -function Transaction () { +function Transaction (network = networks.zcash) { this.version = 3 this.locktime = 0 - this.timestamp = 0 + this.timestamp = 0 // capricoin specific + this.network = network this.ins = [] this.outs = [] - this.joinsplits = [] - this.versionGroupId = '0x03c48270' - this.expiry = 0 - this.zcash = true - this.spendDescs = []; - this.outputDescs = []; + 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 } Transaction.DEFAULT_SEQUENCE = 0xffffffff @@ -61,6 +65,14 @@ 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') @@ -83,7 +95,7 @@ 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, zcash, hasTimestamp, __noStrict) { +Transaction.fromBuffer = function (buffer, network = networks.bitcoin, __noStrict) { var offset = 0 function readSlice (n) { offset += n @@ -174,7 +186,7 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { var tx = new Transaction() - if (zcash) { + if (coins.isZcash(network)) { var header = readUInt32() tx.version = header & 0x7fffffff var overwintered = header >>> 31 @@ -184,6 +196,13 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { } tx.versionGroupId = readUInt32() } + } else if(coins.isDash(network)){ + tx.version = readInt32() + 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') + } } else { tx.version = readInt32() } @@ -192,7 +211,7 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { var flag = buffer.readUInt8(offset + 1) var hasWitnesses = false - if (!zcash) { + if (!coins.isZcash(network)) { if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG) { offset += 2 @@ -200,7 +219,7 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { } } - if (hasTimestamp) { + if (coins.isCapricoin(network)) { tx.timestamp = readUInt32() } @@ -234,92 +253,97 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { tx.locktime = readUInt32() - if (tx.version >= 3 && zcash) { - tx.expiry = readUInt32() - } - - if (tx.version >= 4 && zcash) { - tx.valueBalance = readUInt64(); - var sizeSpendDescs = readVarInt(); - for (var i = 0; i < sizeSpendDescs; i++) { - var spend = readSpentDesc(); - tx.spendDescs.push(spend); + if (coins.isZcash(network)) { + if (tx.version >= 3) { + tx.expiry = readUInt32() } - var sizeOutputDescs = readVarInt(); - for (var i = 0; i < sizeOutputDescs; i++) { - var output = readOutputDesc(); - tx.outputDescs.push(output); - } - } - - if (tx.version >= 2 && zcash) { - var jsLen = readVarInt() - for (i = 0; i < jsLen; ++i) { - var vpubOld = readUInt64() - var vpubNew = readUInt64() - var anchor = readSlice(32) - var nullifiers = [] - for (var j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { - nullifiers.push(readSlice(32)) - } - var commitments = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { - commitments.push(readSlice(32)) + if (tx.version >= 4) { + tx.valueBalance = readUInt64(); + var sizeSpendDescs = readVarInt(); + for (var i = 0; i < sizeSpendDescs; i++) { + var spend = readSpentDesc(); + tx.spendDescs.push(spend); } - var ephemeralKey = readSlice(32) - var randomSeed = readSlice(32) - var macs = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { - macs.push(readSlice(32)) + + var sizeOutputDescs = readVarInt(); + for (var i = 0; i < sizeOutputDescs; i++) { + var output = readOutputDesc(); + tx.outputDescs.push(output); } - var zproof = {}; - if (tx.version <= 3) { - zproof = { - gA: readCompressedG1(), - gAPrime: readCompressedG1(), - gB: readCompressedG2(), - gBPrime: readCompressedG1(), - gC: readCompressedG1(), - gCPrime: readCompressedG1(), - gK: readCompressedG1(), - gH: readCompressedG1() + } + + if (tx.version >= 2) { + var jsLen = readVarInt() + for (i = 0; i < jsLen; ++i) { + var vpubOld = readUInt64() + var vpubNew = readUInt64() + var anchor = readSlice(32) + var nullifiers = [] + for (var j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { + nullifiers.push(readSlice(32)) + } + var commitments = [] + for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { + commitments.push(readSlice(32)) + } + var ephemeralKey = readSlice(32) + var randomSeed = readSlice(32) + var macs = [] + for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { + macs.push(readSlice(32)) } - } else { - zproof = { - sA: readSlice(48), - sB: readSlice(96), - sC: readSlice(48) + 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++) { + ciphertexts.push(readSlice(Transaction.ZCASH_NOTECIPHERTEXT_SIZE)) + } + + tx.joinsplits.push({ + vpubOld: vpubOld, + vpubNew: vpubNew, + anchor: anchor, + nullifiers: nullifiers, + commitments: commitments, + ephemeralKey: ephemeralKey, + randomSeed: randomSeed, + macs: macs, + zproof: zproof, + ciphertexts: ciphertexts + }) } - var ciphertexts = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { - ciphertexts.push(readSlice(Transaction.ZCASH_NOTECIPHERTEXT_SIZE)) + if (jsLen > 0) { + tx.joinsplitPubkey = readSlice(32) + tx.joinsplitSig = readSlice(64) + } + if (tx.version >= 4 && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { + tx.bindingSig = readSlice(64); } - - tx.joinsplits.push({ - vpubOld: vpubOld, - vpubNew: vpubNew, - anchor: anchor, - nullifiers: nullifiers, - commitments: commitments, - ephemeralKey: ephemeralKey, - randomSeed: randomSeed, - macs: macs, - zproof: zproof, - ciphertexts: ciphertexts - }) - } - if (jsLen > 0) { - tx.joinsplitPubkey = readSlice(32) - tx.joinsplitSig = readSlice(64) - } - if (tx.version >= 4 && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { - tx.bindingSig = readSlice(64); } } - tx.zcash = !!zcash + tx.network = network + if (tx.isDashSpecialTransaction()) { + tx.dashPayload = readVarSlice() + } if (__noStrict) return tx if (offset !== buffer.length) throw new Error('Transaction has unexpected data') @@ -327,8 +351,8 @@ Transaction.fromBuffer = function (buffer, zcash, hasTimestamp, __noStrict) { return tx } -Transaction.fromHex = function (hex, zcash, hasTimestamp) { - return Transaction.fromBuffer(new Buffer(hex, 'hex'), zcash, hasTimestamp) +Transaction.fromHex = function (hex, network) { + return Transaction.fromBuffer(new Buffer(hex, 'hex'), network) } Transaction.isCoinbaseHash = function (buffer) { @@ -461,8 +485,12 @@ Transaction.prototype.zcashTransactionByteLength = function() { return byteLength } +Transaction.prototype.isDashSpecialTransaction = function () { + return coins.isDash(this.network) && this.version === 3 && this.dashType !== Transaction.DASH_NORMAL +} + Transaction.prototype.__byteLength = function (__allowWitness) { - if (this.zcash) { + if (coins.isZcash(this.network)) { return this.zcashTransactionByteLength(); } @@ -475,6 +503,7 @@ Transaction.prototype.__byteLength = function (__allowWitness) { 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) + + (this.isDashSpecialTransaction() ? varSliceSize(this.dashPayload) : 0) + (hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0) ) } @@ -484,8 +513,10 @@ Transaction.prototype.clone = function () { var newTx = new Transaction() newTx.version = this.version newTx.locktime = this.locktime - newTx.zcash = this.zcash - if (newTx.zcash) { + newTx.network = this.network + newTx.dashType = this.dashType + newTx.dashPayload = this.dashPayload + if (coins.isZcash(newTx.network)) { newTx.versionGroupId = this.versionGroupId newTx.expiry = this.expiry } @@ -789,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) { @@ -827,9 +859,12 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(i.proof); } - if (this.version >= 3 && this.zcash) { + if (coins.isZcash(this.network) && this.version >= 3) { writeInt32(this.version | (1 << 31)) writeUInt32(this.versionGroupId) + } else if(this.isDashSpecialTransaction()) { + writeInt16(this.version) + writeInt16(this.dashType) } else { writeInt32(this.version) } @@ -873,67 +908,73 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt32(this.locktime) - if (this.version >= 3 && this.zcash) { - writeUInt32(this.expiry) - } - - if (this.version >= 4 && this.zcash) { - writeUInt64(this.valueBalance); - writeVarInt(this.spendDescs.length); - for (var i = 0; i < this.spendDescs.length; i++) { - writeSpentDesc(this.spendDescs[i]); + if (coins.isZcash(this.network)) { + if (this.version >= 3) { + writeUInt32(this.expiry) } - writeVarInt(this.outputDescs.length); - for (var i = 0; i < this.outputDescs.length; i++) { - writeOutputDesc(this.outputDescs[i]); + + if (this.version >= 4) { + 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.version >= 2 && this.zcash) { - writeVarInt(this.joinsplits.length) - var version = this.version; - this.joinsplits.forEach(function (joinsplit) { - writeUInt64(joinsplit.vpubOld) - writeUInt64(joinsplit.vpubNew) - writeSlice(joinsplit.anchor) - joinsplit.nullifiers.forEach(function (nullifier) { - writeSlice(nullifier) - }) - joinsplit.commitments.forEach(function (nullifier) { - writeSlice(nullifier) - }) - writeSlice(joinsplit.ephemeralKey) - writeSlice(joinsplit.randomSeed) - joinsplit.macs.forEach(function (nullifier) { - writeSlice(nullifier) + if (this.version >= 2) { + writeVarInt(this.joinsplits.length) + var version = this.version; + this.joinsplits.forEach(function (joinsplit) { + writeUInt64(joinsplit.vpubOld) + writeUInt64(joinsplit.vpubNew) + writeSlice(joinsplit.anchor) + joinsplit.nullifiers.forEach(function (nullifier) { + writeSlice(nullifier) + }) + joinsplit.commitments.forEach(function (nullifier) { + writeSlice(nullifier) + }) + writeSlice(joinsplit.ephemeralKey) + writeSlice(joinsplit.randomSeed) + joinsplit.macs.forEach(function (nullifier) { + writeSlice(nullifier) + }) + 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) + }) }) - 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) + if (this.joinsplits.length > 0) { + writeSlice(this.joinsplitPubkey) + writeSlice(this.joinsplitSig) + } + if (this.version >= 4 && ((this.spendDescs.length + this.outputDescs.length) > 0)) { + writeSlice(this.bindingSig); } - joinsplit.ciphertexts.forEach(function (ciphertext) { - writeSlice(ciphertext) - }) - }) - if (this.joinsplits.length > 0) { - writeSlice(this.joinsplitPubkey) - writeSlice(this.joinsplitSig) - } - if (this.version >= 4 && ((this.spendDescs.length + this.outputDescs.length) > 0)) { - writeSlice(this.bindingSig); } } + if (this.isDashSpecialTransaction()) { + writeVarSlice(this.dashPayload) + } + // avoid slicing unless necessary if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) return buffer From 93ac32509fe2079d75853b86531507e51a85e23a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 6 Mar 2019 16:08:38 +0100 Subject: [PATCH 14/26] freeze typeforce on current version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17d3b3ab0..06a5b1838 100644 --- a/package.json +++ b/package.json @@ -46,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" }, From b7aaa79dccdda48a2165e7d1b10de1be8dd9191a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 6 Mar 2019 16:08:53 +0100 Subject: [PATCH 15/26] remove default params from Transaction --- src/transaction.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 4a0cd1165..658e027ae 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -24,11 +24,11 @@ function vectorSize (someVector) { }, 0) } -function Transaction (network = networks.zcash) { +function Transaction (network) { this.version = 3 this.locktime = 0 this.timestamp = 0 // capricoin specific - this.network = network + this.network = network || networks.zcash this.ins = [] this.outs = [] this.joinsplits = [] // zcash specific @@ -95,8 +95,9 @@ 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 = networks.bitcoin, __noStrict) { +Transaction.fromBuffer = function (buffer, $network, __noStrict) { var offset = 0 + var network = $network || networks.bitcoin function readSlice (n) { offset += n return buffer.slice(offset - n, offset) From 0a4edb898b4715386a2fab01a73594cd1bb483d5 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Wed, 6 Mar 2019 16:15:41 +0100 Subject: [PATCH 16/26] Update coins.js removed comma to fix SyntaxError: Unexpected token: punc ()) while browserify in hd-wallet --- src/coins.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coins.js b/src/coins.js index 55d5737ca..3820f3fba 100644 --- a/src/coins.js +++ b/src/coins.js @@ -13,7 +13,7 @@ const coins = { DOGECOIN: 'doge', NAMECOIN: 'nmc', VERTCOIN: 'vtc', - CAPRICOIN: 'cpc', + CAPRICOIN: 'cpc' } coins.isBitcoin = function (network) { @@ -56,7 +56,7 @@ coins.isValidCoin = typeforce.oneOf( coins.isLitecoin, coins.isZcash, coins.isDash, - coins.isCapricoin, + coins.isCapricoin ) module.exports = coins From 59ac0645ceea82fbad85633efad4e002f48af396 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 7 Mar 2019 15:57:17 +0100 Subject: [PATCH 17/26] add "getExtraData" method + "noStrict" flag --- src/transaction.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/transaction.js b/src/transaction.js index 658e027ae..08aef5b21 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -38,6 +38,7 @@ function Transaction (network) { this.outputDescs = []; // zcash specific this.dashType = 0 // dash specific this.dashPayload = 0 // dash specific + this.noStrict = false; } Transaction.DEFAULT_SEQUENCE = 0xffffffff @@ -346,7 +347,10 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { tx.dashPayload = readVarSlice() } - if (__noStrict) return tx + if (__noStrict) { + tx.noStrict = true; + return tx + } if (offset !== buffer.length) throw new Error('Transaction has unexpected data') return tx @@ -517,6 +521,7 @@ Transaction.prototype.clone = function () { newTx.network = this.network newTx.dashType = this.dashType newTx.dashPayload = this.dashPayload + newTx.noStrict = this.noStrict if (coins.isZcash(newTx.network)) { newTx.versionGroupId = this.versionGroupId newTx.expiry = this.expiry @@ -997,4 +1002,22 @@ Transaction.prototype.setWitness = function (index, witness) { this.ins[index].witness = witness } +Transaction.prototype.getExtraData = function () { + if (coins.isZcash(this.network) && this.version >= 3) { + var buffer = this.toBuffer() + var joinsplitByteLength = this.joinsplitByteLength() + var res = buffer.slice(buffer.length - joinsplitByteLength) + return res + } + if (coins.isDash(this.network) && this.dashPayload) { + var extraDataLength = varuint.encode(this.dashPayload.length) + return Buffer.concat([extraDataLength, this.dashPayload]); + } + return null +} + +Transaction.prototype.isZcashTransaction = function () { + return coins.isZcash(this.network) +} + module.exports = Transaction From 0dda6896f6df0cae5e686da3677bf736e5070373 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 7 Mar 2019 17:42:42 +0100 Subject: [PATCH 18/26] fix for zcash: supportsJoinSplits --- src/transaction.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/transaction.js b/src/transaction.js index 08aef5b21..c387b2e96 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -462,6 +462,7 @@ Transaction.prototype.outputDescsByteLength = function () { } Transaction.prototype.zcashTransactionByteLength = function() { + var supportJoinsplits = this.version >= 2; var isOverwinterCompatible = this.version >= 3 var isSaplingCompatible = this.version >= 4 var byteLength = 0 @@ -482,7 +483,9 @@ Transaction.prototype.zcashTransactionByteLength = function() { byteLength += this.spendDescsByteLength() byteLength += this.outputDescsByteLength() } - byteLength += this.joinsplitByteLength() + if (supportJoinsplits) { + byteLength += this.joinsplitByteLength() + } if (isSaplingCompatible && this.spendDescs.length + this.outputDescs.length > 0) { byteLength += 64 // bindingSig From c76e410f04779898970da6110876b29e0d8880ee Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 7 Mar 2019 19:27:49 +0100 Subject: [PATCH 19/26] changes inspired by bitgo-utxo-lib --- src/transaction.js | 176 ++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index c387b2e96..52f05cd5b 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -59,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 @@ -187,11 +190,12 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { } var tx = new Transaction() + tx.network = network + tx.version = readInt32() if (coins.isZcash(network)) { - var header = readUInt32() - tx.version = header & 0x7fffffff - var overwintered = header >>> 31 + var overwintered = tx.version >>> 31 + tx.version = tx.version & 0x7fffffff if (tx.version >= 3) { if (!overwintered) { throw new Error('zcash tx v3+ not overwintered') @@ -205,8 +209,6 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { if (tx.version === 3 && (tx.dashType < Transaction.DASH_NORMAL || tx.dashType > Transaction.DASH_QUORUM_COMMITMENT)) { throw new Error('Unsupported Dash transaction type') } - } else { - tx.version = readInt32() } var marker = buffer.readUInt8(offset) @@ -256,11 +258,11 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { tx.locktime = readUInt32() if (coins.isZcash(network)) { - if (tx.version >= 3) { + if (tx.isOverwinterCompatible()) { tx.expiry = readUInt32() } - if (tx.version >= 4) { + if (tx.isSaplingCompatible()) { tx.valueBalance = readUInt64(); var sizeSpendDescs = readVarInt(); for (var i = 0; i < sizeSpendDescs; i++) { @@ -275,7 +277,7 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { } } - if (tx.version >= 2) { + if (tx.supportsJoinSplits()) { var jsLen = readVarInt() for (i = 0; i < jsLen; ++i) { var vpubOld = readUInt64() @@ -336,13 +338,12 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { tx.joinsplitPubkey = readSlice(32) tx.joinsplitSig = readSlice(64) } - if (tx.version >= 4 && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { + if (tx.isSaplingCompatible() && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { tx.bindingSig = readSlice(64); } } } - tx.network = network if (tx.isDashSpecialTransaction()) { tx.dashPayload = readVarSlice() } @@ -425,7 +426,6 @@ Transaction.prototype.byteLength = function () { } Transaction.prototype.joinsplitByteLength = function () { - var isSaplingCompatible = this.version >= 4 var joinSplitsLen = this.joinsplits.length var byteLength = 0 @@ -435,7 +435,7 @@ Transaction.prototype.joinsplitByteLength = function () { // 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 (isSaplingCompatible) { + if (this.isSaplingCompatible()) { byteLength += 1698 * joinSplitsLen // vJoinSplit using JSDescriptionGroth16 } else { byteLength += 1802 * joinSplitsLen // vJoinSplit using JSDescriptionPHGR13 @@ -462,12 +462,12 @@ Transaction.prototype.outputDescsByteLength = function () { } Transaction.prototype.zcashTransactionByteLength = function() { - var supportJoinsplits = this.version >= 2; - var isOverwinterCompatible = this.version >= 3 - var isSaplingCompatible = this.version >= 4 + if (!coins.isZcash(this.network)) { + throw new Error('zcashTransactionByteLength can only be called when using Zcash network') + } var byteLength = 0 byteLength += 4 // Header - if (isOverwinterCompatible) { + if (this.isOverwinterCompatible()) { byteLength += 4 // nVersionGroupId } byteLength += varuint.encodingLength(this.ins.length) // tx_in_count @@ -475,28 +475,24 @@ Transaction.prototype.zcashTransactionByteLength = function() { 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 (isOverwinterCompatible) { + if (this.isOverwinterCompatible()) { byteLength += 4 // nExpiryHeight } - if (isSaplingCompatible) { + if (this.isSaplingCompatible()) { byteLength += 8 // valueBalance byteLength += this.spendDescsByteLength() byteLength += this.outputDescsByteLength() } - if (supportJoinsplits) { + if (this.supportsJoinSplits()) { byteLength += this.joinsplitByteLength() } - if (isSaplingCompatible && + if (this.isSaplingCompatible() && this.spendDescs.length + this.outputDescs.length > 0) { byteLength += 64 // bindingSig } return byteLength } -Transaction.prototype.isDashSpecialTransaction = function () { - return coins.isDash(this.network) && this.version === 3 && this.dashType !== Transaction.DASH_NORMAL -} - Transaction.prototype.__byteLength = function (__allowWitness) { if (coins.isZcash(this.network)) { return this.zcashTransactionByteLength(); @@ -521,6 +517,7 @@ Transaction.prototype.clone = function () { var newTx = new Transaction() newTx.version = this.version newTx.locktime = this.locktime + newTx.timestamp = this.timestamp newTx.network = this.network newTx.dashType = this.dashType newTx.dashPayload = this.dashPayload @@ -868,7 +865,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(i.proof); } - if (coins.isZcash(this.network) && this.version >= 3) { + if (this.isOverwinterCompatible()) { writeInt32(this.version | (1 << 31)) writeUInt32(this.versionGroupId) } else if(this.isDashSpecialTransaction()) { @@ -917,66 +914,64 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt32(this.locktime) - if (coins.isZcash(this.network)) { - if (this.version >= 3) { - writeUInt32(this.expiry) - } + if (this.isOverwinterCompatible()) { + writeUInt32(this.expiry) + } - if (this.version >= 4) { - 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]); - } + var isSaplingCompatible = this.isSaplingCompatible(); + if (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.version >= 2) { - writeVarInt(this.joinsplits.length) - var version = this.version; - this.joinsplits.forEach(function (joinsplit) { - writeUInt64(joinsplit.vpubOld) - writeUInt64(joinsplit.vpubNew) - writeSlice(joinsplit.anchor) - joinsplit.nullifiers.forEach(function (nullifier) { - writeSlice(nullifier) - }) - joinsplit.commitments.forEach(function (nullifier) { - writeSlice(nullifier) - }) - writeSlice(joinsplit.ephemeralKey) - writeSlice(joinsplit.randomSeed) - joinsplit.macs.forEach(function (nullifier) { - writeSlice(nullifier) - }) - 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) - }) + if (this.supportsJoinSplits()) { + writeVarInt(this.joinsplits.length) + this.joinsplits.forEach(function (joinsplit) { + writeUInt64(joinsplit.vpubOld) + writeUInt64(joinsplit.vpubNew) + writeSlice(joinsplit.anchor) + joinsplit.nullifiers.forEach(function (nullifier) { + writeSlice(nullifier) }) - if (this.joinsplits.length > 0) { - writeSlice(this.joinsplitPubkey) - writeSlice(this.joinsplitSig) - } - if (this.version >= 4 && ((this.spendDescs.length + this.outputDescs.length) > 0)) { - writeSlice(this.bindingSig); + joinsplit.commitments.forEach(function (nullifier) { + writeSlice(nullifier) + }) + writeSlice(joinsplit.ephemeralKey) + writeSlice(joinsplit.randomSeed) + joinsplit.macs.forEach(function (nullifier) { + writeSlice(nullifier) + }) + if (isSaplingCompatible) { + 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) + }) + }) + if (this.joinsplits.length > 0) { + writeSlice(this.joinsplitPubkey) + writeSlice(this.joinsplitSig) + } + if (isSaplingCompatible && ((this.spendDescs.length + this.outputDescs.length) > 0)) { + writeSlice(this.bindingSig); } } @@ -1006,13 +1001,14 @@ Transaction.prototype.setWitness = function (index, witness) { } Transaction.prototype.getExtraData = function () { - if (coins.isZcash(this.network) && this.version >= 3) { + 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 (coins.isDash(this.network) && this.dashPayload) { + if (this.isDashSpecialTransaction()) { var extraDataLength = varuint.encode(this.dashPayload.length) return Buffer.concat([extraDataLength, this.dashPayload]); } @@ -1023,4 +1019,20 @@ 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 From f68ad0d87068a7600bf6f1a17d3ceab7c250c715 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 7 Mar 2019 19:41:34 +0100 Subject: [PATCH 20/26] cleanup and fixes --- src/transaction.js | 159 ++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 52f05cd5b..a7e76cf94 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -257,92 +257,91 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { tx.locktime = readUInt32() - if (coins.isZcash(network)) { - if (tx.isOverwinterCompatible()) { - tx.expiry = readUInt32() - } + if (tx.isOverwinterCompatible()) { + tx.expiry = readUInt32() + } - if (tx.isSaplingCompatible()) { - tx.valueBalance = readUInt64(); - var sizeSpendDescs = readVarInt(); - for (var i = 0; i < sizeSpendDescs; i++) { - var spend = readSpentDesc(); - tx.spendDescs.push(spend); - } + 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); - } + 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() - var vpubNew = readUInt64() - var anchor = readSlice(32) - var nullifiers = [] - for (var j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { - nullifiers.push(readSlice(32)) - } - var commitments = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { - commitments.push(readSlice(32)) - } - var ephemeralKey = readSlice(32) - var randomSeed = readSlice(32) - var macs = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { - macs.push(readSlice(32)) - } - 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) - } + if (tx.supportsJoinSplits()) { + var jsLen = readVarInt() + for (i = 0; i < jsLen; ++i) { + var vpubOld = readUInt64() + var vpubNew = readUInt64() + var anchor = readSlice(32) + var nullifiers = [] + for (var j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { + nullifiers.push(readSlice(32)) + } + var commitments = [] + for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { + commitments.push(readSlice(32)) + } + var ephemeralKey = readSlice(32) + var randomSeed = readSlice(32) + var macs = [] + for (j = 0; j < Transaction.ZCASH_NUM_JS_INPUTS; j++) { + macs.push(readSlice(32)) + } + var zproof = {}; + if (tx.version <= 3) { + zproof = { + gA: readCompressedG1(), + gAPrime: readCompressedG1(), + gB: readCompressedG2(), + gBPrime: readCompressedG1(), + gC: readCompressedG1(), + gCPrime: readCompressedG1(), + gK: readCompressedG1(), + gH: readCompressedG1() } - var ciphertexts = [] - for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { - ciphertexts.push(readSlice(Transaction.ZCASH_NOTECIPHERTEXT_SIZE)) + } else { + zproof = { + sA: readSlice(48), + sB: readSlice(96), + sC: readSlice(48) } - - tx.joinsplits.push({ - vpubOld: vpubOld, - vpubNew: vpubNew, - anchor: anchor, - nullifiers: nullifiers, - commitments: commitments, - ephemeralKey: ephemeralKey, - randomSeed: randomSeed, - macs: macs, - zproof: zproof, - ciphertexts: ciphertexts - }) - } - if (jsLen > 0) { - tx.joinsplitPubkey = readSlice(32) - tx.joinsplitSig = readSlice(64) } - if (tx.isSaplingCompatible() && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { - tx.bindingSig = readSlice(64); + var ciphertexts = [] + for (j = 0; j < Transaction.ZCASH_NUM_JS_OUTPUTS; j++) { + ciphertexts.push(readSlice(Transaction.ZCASH_NOTECIPHERTEXT_SIZE)) } + + tx.joinsplits.push({ + vpubOld: vpubOld, + vpubNew: vpubNew, + anchor: anchor, + nullifiers: nullifiers, + commitments: commitments, + ephemeralKey: ephemeralKey, + randomSeed: randomSeed, + macs: macs, + zproof: zproof, + ciphertexts: ciphertexts + }) + } + if (jsLen > 0) { + tx.joinsplitPubkey = readSlice(32) + tx.joinsplitSig = readSlice(64) + } + if (tx.isSaplingCompatible() && ((tx.spendDescs.length + tx.outputDescs.length) > 0)) { + tx.bindingSig = readSlice(64); } } + if (tx.isDashSpecialTransaction()) { tx.dashPayload = readVarSlice() @@ -918,8 +917,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeUInt32(this.expiry) } - var isSaplingCompatible = this.isSaplingCompatible(); - if (isSaplingCompatible) { + if (this.isSaplingCompatible()) { writeUInt64(this.valueBalance); writeVarInt(this.spendDescs.length); for (var i = 0; i < this.spendDescs.length; i++) { @@ -933,6 +931,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne if (this.supportsJoinSplits()) { writeVarInt(this.joinsplits.length) + var version = this.version; this.joinsplits.forEach(function (joinsplit) { writeUInt64(joinsplit.vpubOld) writeUInt64(joinsplit.vpubNew) @@ -948,7 +947,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne joinsplit.macs.forEach(function (nullifier) { writeSlice(nullifier) }) - if (isSaplingCompatible) { + if (version <= 3) { writeCompressedG1(joinsplit.zproof.gA) writeCompressedG1(joinsplit.zproof.gAPrime) writeCompressedG2(joinsplit.zproof.gB) @@ -970,7 +969,7 @@ Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitne writeSlice(this.joinsplitPubkey) writeSlice(this.joinsplitSig) } - if (isSaplingCompatible && ((this.spendDescs.length + this.outputDescs.length) > 0)) { + if (this.isSaplingCompatible() && ((this.spendDescs.length + this.outputDescs.length) > 0)) { writeSlice(this.bindingSig); } } From 36586aaca052f5e57261215baa18667845bc311a Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Thu, 7 Mar 2019 19:44:29 +0100 Subject: [PATCH 21/26] change assing of "network" parameter --- src/transaction.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index a7e76cf94..8bca4fe48 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -99,9 +99,8 @@ 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) { +Transaction.fromBuffer = function (buffer, network, __noStrict) { var offset = 0 - var network = $network || networks.bitcoin function readSlice (n) { offset += n return buffer.slice(offset - n, offset) @@ -190,10 +189,10 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { } var tx = new Transaction() - tx.network = network + tx.network = network || networks.bitcoin tx.version = readInt32() - if (coins.isZcash(network)) { + if (coins.isZcash(tx.network)) { var overwintered = tx.version >>> 31 tx.version = tx.version & 0x7fffffff if (tx.version >= 3) { @@ -202,7 +201,7 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { } tx.versionGroupId = readUInt32() } - } else if(coins.isDash(network)){ + } else if(coins.isDash(tx.network)){ tx.version = readInt32() tx.dashType = tx.version >> 16 tx.version = tx.version & 0xffff @@ -215,7 +214,7 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { var flag = buffer.readUInt8(offset + 1) var hasWitnesses = false - if (!coins.isZcash(network)) { + if (!coins.isZcash(tx.network)) { if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && flag === Transaction.ADVANCED_TRANSACTION_FLAG) { offset += 2 @@ -223,7 +222,7 @@ Transaction.fromBuffer = function (buffer, $network, __noStrict) { } } - if (coins.isCapricoin(network)) { + if (coins.isCapricoin(tx.network)) { tx.timestamp = readUInt32() } From d79a9853dbb676c404b58c243e62b75599b9e437 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 11 Mar 2019 14:31:34 +0100 Subject: [PATCH 22/26] "fromBuffer" dash version fix --- src/transaction.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 8bca4fe48..24ec446a2 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -201,8 +201,7 @@ Transaction.fromBuffer = function (buffer, network, __noStrict) { } tx.versionGroupId = readUInt32() } - } else if(coins.isDash(tx.network)){ - 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)) { From 650eccf187a479904d8aca07216bdb1e0d8dd470 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 11 Mar 2019 14:37:24 +0100 Subject: [PATCH 23/26] add "invalidTransaction" flag --- src/transaction.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 24ec446a2..97b24a8a5 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -38,7 +38,7 @@ function Transaction (network) { this.outputDescs = []; // zcash specific this.dashType = 0 // dash specific this.dashPayload = 0 // dash specific - this.noStrict = false; + this.invalidTransaction = false; } Transaction.DEFAULT_SEQUENCE = 0xffffffff @@ -345,10 +345,7 @@ Transaction.fromBuffer = function (buffer, network, __noStrict) { tx.dashPayload = readVarSlice() } - if (__noStrict) { - tx.noStrict = true; - return tx - } + if (__noStrict) return tx if (offset !== buffer.length) throw new Error('Transaction has unexpected data') return tx @@ -518,7 +515,7 @@ Transaction.prototype.clone = function () { newTx.network = this.network newTx.dashType = this.dashType newTx.dashPayload = this.dashPayload - newTx.noStrict = this.noStrict + newTx.invalidTransaction = this.invalidTransaction if (coins.isZcash(newTx.network)) { newTx.versionGroupId = this.versionGroupId newTx.expiry = this.expiry From ed3c19c620f210cda9f53b19de730d3759d1a52c Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 11 Mar 2019 14:38:16 +0100 Subject: [PATCH 24/26] add flowtype delarations --- types.js | 384 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 types.js 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 From c4d6f39b702d42a676840ebbbf820074d7c6ab69 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 11 Mar 2019 15:55:42 +0100 Subject: [PATCH 25/26] Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a59637992..95c3178db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 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) From bc12b7472bf8933c47eb1175058ec2bb837d6c91 Mon Sep 17 00:00:00 2001 From: Szymon Lesisz Date: Mon, 11 Mar 2019 15:55:52 +0100 Subject: [PATCH 26/26] version 3.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06a5b1838..eae16524f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bitcoinjs-lib-zcash", - "version": "3.6.0", + "version": "3.6.1", "description": "Client-side Bitcoin JavaScript library with simple zcash and capricoin support", "main": "./src/index.js", "engines": {