From 1b5d1fadba50f5ce795a58b38efb30db652fa693 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 10:30:28 +1000 Subject: [PATCH 1/7] README: use https --- CHANGELOG.md | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 907c6d5..451a168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ __added__ __changed__ - `ECPair.prototype.sign` now returns a 64-byte signature `Buffer`, not an `ECSignature` object (#1084) -- `ECPair` (and all ECDSA code) now uses [`tiny-secp256k1`](http://github.com/bitcoinjs/tiny-secp256k1), which uses the [`libsecp256k1` library](https://github.com/bitcoin-core/secp256k1) (#1070) +- `ECPair` (and all ECDSA code) now uses [`tiny-secp256k1`](https://github.com/bitcoinjs/tiny-secp256k1), which uses the [`libsecp256k1` library](https://github.com/bitcoin-core/secp256k1) (#1070) - `TransactionBuilder` internal variables are now `__` prefixed to discourage public usage (#1038) - `TransactionBuilder` now defaults to version 2 transaction versions (#1036) - `script.decompile` now returns `[Buffer]` or `null`, if decompilation failed (#1039) diff --git a/README.md b/README.md index e8866c9..07c5ca4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Mistakes and bugs happen, but with your help in resolving and reporting [issues] - Easy to audit and verify, - Tested, with test coverage >95%, - Advanced and feature rich, -- Standardized, using [standard](http://github.com/standard/standard) and Node `Buffer`'s throughout, and +- Standardized, using [standard](https://github.com/standard/standard) and Node `Buffer`'s throughout, and - Friendly, with a strong and helpful community, ready to answer questions. ## Documentation @@ -45,9 +45,9 @@ If in doubt, see the [.travis.yml](.travis.yml) for what versions are used by ou ### Browser The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify). -If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at http://browserify.org/. +If you're familiar with how to use browserify, ignore this and carry on, otherwise, it is recommended to read the tutorial at https://browserify.org/. -**NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](http://babeljs.io/docs/plugins/preset-es2015/) preset). +**NOTE**: We use Node Maintenance LTS features, if you need strict ES5, use [`--transform babelify`](https://github.com/babel/babelify) in conjunction with your `browserify` step (using an [`es2015`](https://babeljs.io/docs/plugins/preset-es2015/) preset). **WARNING**: iOS devices have [problems](https://github.com/feross/buffer/issues/136), use atleast [buffer@5.0.5](https://github.com/feross/buffer/pull/155) or greater, and enforce the test suites (for `Buffer`, and any other dependency) pass before use. From b273deb265c2f58cdc1c7fa3af2cf2c2ff27a6a3 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 11:03:21 +1000 Subject: [PATCH 2/7] README: add crypto is hard disclaimer --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 07c5ca4..2541b3b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Mistakes and bugs happen, but with your help in resolving and reporting [issues] - Standardized, using [standard](https://github.com/standard/standard) and Node `Buffer`'s throughout, and - Friendly, with a strong and helpful community, ready to answer questions. + ## Documentation Presently, we do not have any formal documentation other than our [examples](#examples), please [ask for help](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new) if our examples aren't enough to guide you. @@ -42,6 +43,27 @@ If in doubt, see the [.travis.yml](.travis.yml) for what versions are used by ou ## Usage +Crypto is hard. + +When working with private keys, the random number generator is fundamentally one of the most important parts of any software you write. +For random number generation, we *default* to the [`randombytes`](https://github.com/crypto-browserify/randombytes) module, which uses [`window.crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/window.crypto.getRandomValues) in the browser, or Node js' [`crypto.randomBytes`](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback), depending on your build system. +Although this default is ~OK, there is no simple way to detect if the underlying RNG provided is good enough, or if it is **catastrophically bad**. +You should always verify this yourself to your own standards. + +This library uses [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1), which uses [RFC6979](https://tools.ietf.org/html/rfc6979) to help prevent `k` re-use and exploitation. +Unfortunately, this isn't a silver bullet. +Often, Javascript itself is working against us by bypassing these counter-measures. + +Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in catastrophic fund loss without any warning. +It can do this through undermining your random number generation, accidentally producing a duplicate `k` value, sending Bitcoin to a malformed output script, or any of a million different ways. +Running tests in your target environment is important and a recommended step to verify continuously. + +Finally, **adhere to best practice**. We aren't an authorative source for best practice, but, at the very least: + +* Don't re-use addresses. Privacy is important, but, .... TODO +* Don't share BIP32 extended public keys. They are a liability, and [as shown in our examples](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68), it only takes 1 mistake until **catastrophic failure**. +* TODO, anythign else of importance here? + ### Browser The recommended method of using `bitcoinjs-lib` in your browser is through [Browserify](https://github.com/substack/node-browserify). From e514bc73643ff63863c43896ea83f8a62fb72f8f Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 17:26:04 +1000 Subject: [PATCH 3/7] README: add extra suggestions for best practice --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2541b3b..f2b2c8d 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,18 @@ Unfortunately, this isn't a silver bullet. Often, Javascript itself is working against us by bypassing these counter-measures. Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in catastrophic fund loss without any warning. -It can do this through undermining your random number generation, accidentally producing a duplicate `k` value, sending Bitcoin to a malformed output script, or any of a million different ways. +It can do this through undermining your random number generation, [accidentally producing a duplicate `k` value](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L14), sending Bitcoin to a malformed output script, or any of a million different ways. Running tests in your target environment is important and a recommended step to verify continuously. -Finally, **adhere to best practice**. We aren't an authorative source for best practice, but, at the very least: +Finally, **adhere to best practice**. +We are not an authorative source of best practice, but, at the very least: -* Don't re-use addresses. Privacy is important, but, .... TODO -* Don't share BIP32 extended public keys. They are a liability, and [as shown in our examples](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68), it only takes 1 mistake until **catastrophic failure**. -* TODO, anythign else of importance here? +* [Don't re-use addresses](https://en.bitcoin.it/wiki/Address_reuse). +* Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**. +* [Don't use `Math.random`](https://security.stackexchange.com/questions/181580/why-is-math-random-not-designed-to-be-cryptographically-secure) - in any way - don't. +* Enforce that users always verify (manually) a freshly-decoded human-readable version of their intended transaction before broadcast. +* Don't *ask* users to generate mnemonics, or 'brain wallets', humans are terrible random number generators. +* Lastly, if you can, use [Typescript](https://www.typescriptlang.org/) or similar. ### Browser From 5fc673a8d6e7acd4b5767f1f0b633538d2c69429 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 17:28:15 +1000 Subject: [PATCH 4/7] README: link to external explanations --- README.md | 7 +-- test/integration/crypto.js | 103 ------------------------------------- 2 files changed, 2 insertions(+), 108 deletions(-) delete mode 100644 test/integration/crypto.js diff --git a/README.md b/README.md index f2b2c8d..8a0885c 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ Unfortunately, this isn't a silver bullet. Often, Javascript itself is working against us by bypassing these counter-measures. Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in catastrophic fund loss without any warning. -It can do this through undermining your random number generation, [accidentally producing a duplicate `k` value](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L14), sending Bitcoin to a malformed output script, or any of a million different ways. +It can do this through undermining your random number generation, [accidentally producing a duplicate `k` value](https://www.nilsschneider.net/2013/01/28/recovering-bitcoin-private-keys.html), sending Bitcoin to a malformed output script, or any of a million different ways. Running tests in your target environment is important and a recommended step to verify continuously. Finally, **adhere to best practice**. We are not an authorative source of best practice, but, at the very least: * [Don't re-use addresses](https://en.bitcoin.it/wiki/Address_reuse). -* Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**. +* Don't share BIP32 extended public keys ('xpubs'). [They are a liability](https://bitcoin.stackexchange.com/questions/56916/derivation-of-parent-private-key-from-non-hardened-child), and it only takes 1 misplaced private key (or a buggy implementation!) and you are vulnerable to **catastrophic fund loss**. * [Don't use `Math.random`](https://security.stackexchange.com/questions/181580/why-is-math-random-not-designed-to-be-cryptographically-secure) - in any way - don't. * Enforce that users always verify (manually) a freshly-decoded human-readable version of their intended transaction before broadcast. * Don't *ask* users to generate mnemonics, or 'brain wallets', humans are terrible random number generators. @@ -140,11 +140,8 @@ Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). - [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L88) - [Create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L144) - [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L190) -- [Recover a private key from duplicate R values](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L14) -- [Recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L68) - [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L72) - [Generate a single-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L91) -- [Recover parent recipient.d, if a derived private key is leaked (and nonce was revealed)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L107) - [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L124) - [Generate a dual-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L147) diff --git a/test/integration/crypto.js b/test/integration/crypto.js deleted file mode 100644 index 3a40f6c..0000000 --- a/test/integration/crypto.js +++ /dev/null @@ -1,103 +0,0 @@ -const { describe, it } = require('mocha') -const assert = require('assert') -const BN = require('bn.js') -const bitcoin = require('../../') -const bip32 = require('bip32') -const crypto = require('crypto') -const tinysecp = require('tiny-secp256k1') - -describe('bitcoinjs-lib (crypto)', function () { - it('can recover a private key from duplicate R values', function () { - // https://blockchain.info/tx/f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50 - const tx = bitcoin.Transaction.fromHex('01000000020b668015b32a6178d8524cfef6dc6fc0a4751915c2e9b2ed2d2eab02424341c8000000006a47304402205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022024bf5f506968f5f23f1835574d5afe0e9021b4a5b65cf9742332d5e4acb68f41012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffffa95fa69f11dc1cbb77ef64f25a95d4b12ebda57d19d843333819d95c9172ff89000000006b48304502205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022100832176b59e8f50c56631acbc824bcba936c9476c559c42a4468be98975d07562012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffff02b000eb04000000001976a91472956eed9a8ecb19ae7e3ebd7b06cae4668696a788ac303db000000000001976a9146c0bd55dd2592287cd9992ce3ba3fc1208fb76da88ac00000000') - - tx.ins.forEach(function (input, vin) { - const { output: prevOutput, pubkey, signature } = bitcoin.payments.p2pkh({ input: input.script }) - - const scriptSignature = bitcoin.script.signature.decode(signature) - const m = tx.hashForSignature(vin, prevOutput, scriptSignature.hashType) - assert(bitcoin.ECPair.fromPublicKey(pubkey).verify(m, scriptSignature.signature), 'Invalid m') - - // store the required information - input.signature = scriptSignature.signature - input.z = new BN(m) - }) - - const n = new BN('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 16) - - for (var i = 0; i < tx.ins.length; ++i) { - for (var j = i + 1; j < tx.ins.length; ++j) { - const inputA = tx.ins[i] - const inputB = tx.ins[j] - - // enforce matching r values - const r = inputA.signature.slice(0, 32) - const rB = inputB.signature.slice(0, 32) - assert.strictEqual(r.toString('hex'), rB.toString('hex')) - - const rInv = new BN(r).invm(n) - - const s1 = new BN(inputA.signature.slice(32, 64)) - const s2 = new BN(inputB.signature.slice(32, 64)) - const z1 = inputA.z - const z2 = inputB.z - - const zz = z1.sub(z2).mod(n) - const ss = s1.sub(s2).mod(n) - - // k = (z1 - z2) / (s1 - s2) - // d1 = (s1 * k - z1) / r - // d2 = (s2 * k - z2) / r - const k = zz.mul(ss.invm(n)).mod(n) - const d1 = ((s1.mul(k).mod(n)).sub(z1).mod(n)).mul(rInv).mod(n) - const d2 = ((s2.mul(k).mod(n)).sub(z2).mod(n)).mul(rInv).mod(n) - - // enforce matching private keys - assert.strictEqual(d1.toString(), d2.toString()) - } - } - }) - - it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () { - function recoverParent (master, child) { - assert(master.isNeutered(), 'You already have the parent private key') - assert(!child.isNeutered(), 'Missing child private key') - - const serQP = master.publicKey - const d1 = child.privateKey - const data = Buffer.alloc(37) - serQP.copy(data, 0) - - // search index space until we find it - let d2 - for (var i = 0; i < 0x80000000; ++i) { - data.writeUInt32BE(i, 33) - - // calculate I - const I = crypto.createHmac('sha512', master.chainCode).update(data).digest() - const IL = I.slice(0, 32) - - // See bip32.js:273 to understand - d2 = tinysecp.privateSub(d1, IL) - - const Qp = bip32.fromPrivateKey(d2, Buffer.alloc(32, 0)).publicKey - if (Qp.equals(serQP)) break - } - - const node = bip32.fromPrivateKey(d2, master.chainCode, master.network) - node.depth = master.depth - node.index = master.index - node.masterFingerprint = master.masterFingerprint - return node - } - - const seed = crypto.randomBytes(32) - const master = bip32.fromSeed(seed) - const child = master.derive(6) // m/6 - - // now for the recovery - const neuteredMaster = master.neutered() - const recovered = recoverParent(neuteredMaster, child) - assert.strictEqual(recovered.toBase58(), master.toBase58()) - }) -}) From a908e909d1841500b5eeed4bee7a44240fc53b4c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 17:40:45 +1000 Subject: [PATCH 5/7] README: rm stealth address examples --- README.md | 4 - test/integration/stealth.js | 167 ------------------------------------ 2 files changed, 171 deletions(-) delete mode 100644 test/integration/stealth.js diff --git a/README.md b/README.md index 8a0885c..23dce36 100644 --- a/README.md +++ b/README.md @@ -140,10 +140,6 @@ Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). - [Create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L88) - [Create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L144) - [Create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/cltv.js#L190) -- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L72) -- [Generate a single-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L91) -- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L124) -- [Generate a dual-key stealth address (randomly)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/stealth.js#L147) If you have a use case that you feel could be listed here, please [ask for it](https://github.com/bitcoinjs/bitcoinjs-lib/issues/new)! diff --git a/test/integration/stealth.js b/test/integration/stealth.js deleted file mode 100644 index dd99d63..0000000 --- a/test/integration/stealth.js +++ /dev/null @@ -1,167 +0,0 @@ -const { describe, it } = require('mocha') -const assert = require('assert') -const bitcoin = require('../../') -const ecc = require('tiny-secp256k1') - -function getAddress (node, network) { - return bitcoin.payments.p2pkh({ pubkey: node.publicKey, network }).address -} - -// vG = (dG \+ sha256(e * dG)G) -function stealthSend (e, Q) { - const eQ = ecc.pointMultiply(Q, e, true) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const Qc = ecc.pointAddScalar(Q, c) - const vG = bitcoin.ECPair.fromPublicKey(Qc) - - return vG -} - -// v = (d + sha256(eG * d)) -function stealthReceive (d, eG) { - const eQ = ecc.pointMultiply(eG, d) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const dc = ecc.privateAdd(d, c) - const v = bitcoin.ECPair.fromPrivateKey(dc) - - return v -} - -// d = (v - sha256(e * dG)) -function stealthRecoverLeaked (v, e, Q) { - const eQ = ecc.pointMultiply(Q, e) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const vc = ecc.privateSub(v, c) - const d = bitcoin.ECPair.fromPrivateKey(vc) - - return d -} - -// vG = (rG \+ sha256(e * dG)G) -function stealthDualSend (e, R, Q) { - const eQ = ecc.pointMultiply(Q, e) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const Rc = ecc.pointAddScalar(R, c) - const vG = bitcoin.ECPair.fromPublicKey(Rc) - - return vG -} - -// vG = (rG \+ sha256(eG * d)G) -function stealthDualScan (d, R, eG) { - const eQ = ecc.pointMultiply(eG, d) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const Rc = ecc.pointAddScalar(R, c) - const vG = bitcoin.ECPair.fromPublicKey(Rc) - - return vG -} - -// v = (r + sha256(eG * d)) -function stealthDualReceive (d, r, eG) { - const eQ = ecc.pointMultiply(eG, d) // shared secret - const c = bitcoin.crypto.sha256(eQ) - const rc = ecc.privateAdd(r, c) - const v = bitcoin.ECPair.fromPrivateKey(rc) - - return v -} - -describe('bitcoinjs-lib (crypto)', function () { - it('can generate a single-key stealth address', function () { - // XXX: should be randomly generated, see next test for example - const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient - const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender - - // ... recipient reveals public key (recipient.Q) to sender - const forSender = stealthSend(nonce.privateKey, recipient.publicKey) - assert.equal(getAddress(forSender), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE') - assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/) - - // ... sender reveals nonce public key (nonce.Q) to recipient - const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey) - assert.equal(getAddress(forRecipient), '1CcZWwCpACJL3AxqoDbwEt4JgDFuTHUspE') - assert.equal(forRecipient.toWIF(), 'L1yjUN3oYyCXV3LcsBrmxCNTa62bZKWCybxVJMvqjMmmfDE8yk7n') - - // sender and recipient, both derived same address - assert.equal(getAddress(forSender), getAddress(forRecipient)) - }) - - it('can generate a single-key stealth address (randomly)', function () { - const recipient = bitcoin.ECPair.makeRandom() // private to recipient - const nonce = bitcoin.ECPair.makeRandom() // private to sender - - // ... recipient reveals public key (recipient.Q) to sender - const forSender = stealthSend(nonce.privateKey, recipient.publicKey) - assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/) - - // ... sender reveals nonce public key (nonce.Q) to recipient - const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey) - assert.doesNotThrow(function () { forRecipient.toWIF() }) - - // sender and recipient, both derived same address - assert.equal(getAddress(forSender), getAddress(forRecipient)) - }) - - it('can recover parent recipient.d, if a derived private key is leaked [and nonce was revealed]', function () { - const recipient = bitcoin.ECPair.makeRandom() // private to recipient - const nonce = bitcoin.ECPair.makeRandom() // private to sender - - // ... recipient reveals public key (recipient.Q) to sender - const forSender = stealthSend(nonce.privateKey, recipient.publicKey) - assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/) - - // ... sender reveals nonce public key (nonce.Q) to recipient - const forRecipient = stealthReceive(recipient.privateKey, nonce.publicKey) - assert.doesNotThrow(function () { forRecipient.toWIF() }) - - // ... recipient accidentally leaks forRecipient.d on the blockchain - const leaked = stealthRecoverLeaked(forRecipient.privateKey, nonce.privateKey, recipient.publicKey) - assert.equal(leaked.toWIF(), recipient.toWIF()) - }) - - it('can generate a dual-key stealth address', function () { - // XXX: should be randomly generated, see next test for example - const recipient = bitcoin.ECPair.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss') // private to recipient - const scan = bitcoin.ECPair.fromWIF('L5DkCk3xLLoGKncqKsWQTdaPSR4V8gzc14WVghysQGkdryRudjBM') // private to scanner/recipient - const nonce = bitcoin.ECPair.fromWIF('KxVqB96pxbw1pokzQrZkQbLfVBjjHFfp2mFfEp8wuEyGenLFJhM9') // private to sender - - // ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender - const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey) - assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/) - - // ... sender reveals nonce public key (nonce.Q) to scanner - const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey) - assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/) - - // ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient - const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey) - assert.doesNotThrow(function () { forRecipient.toWIF() }) - - // scanner, sender and recipient, all derived same address - assert.equal(getAddress(forSender), getAddress(forScanner)) - assert.equal(getAddress(forSender), getAddress(forRecipient)) - }) - - it('can generate a dual-key stealth address (randomly)', function () { - const recipient = bitcoin.ECPair.makeRandom() // private to recipient - const scan = bitcoin.ECPair.makeRandom() // private to scanner/recipient - const nonce = bitcoin.ECPair.makeRandom() // private to sender - - // ... recipient reveals public key(s) (recipient.Q, scan.Q) to sender - const forSender = stealthDualSend(nonce.privateKey, recipient.publicKey, scan.publicKey) - assert.throws(function () { forSender.toWIF() }, /Error: Missing private key/) - - // ... sender reveals nonce public key (nonce.Q) to scanner - const forScanner = stealthDualScan(scan.privateKey, recipient.publicKey, nonce.publicKey) - assert.throws(function () { forScanner.toWIF() }, /Error: Missing private key/) - - // ... scanner reveals relevant transaction + nonce public key (nonce.Q) to recipient - const forRecipient = stealthDualReceive(scan.privateKey, recipient.privateKey, nonce.publicKey) - assert.doesNotThrow(function () { forRecipient.toWIF() }) - - // scanner, sender and recipient, all derived same address - assert.equal(getAddress(forSender), getAddress(forScanner)) - assert.equal(getAddress(forSender), getAddress(forRecipient)) - }) -}) From aac228011f9c8c75b17bc9d100bb67534172e366 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 17:44:31 +1000 Subject: [PATCH 6/7] README: rm bad sha256 hash example --- README.md | 1 - test/integration/addresses.js | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/README.md b/README.md index 23dce36..28c1b21 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,6 @@ Otherwise, pull requests are appreciated. Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L22) -- [Generate an address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L29) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L40) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L47) - [Generate a SegWit address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.js#L60) diff --git a/test/integration/addresses.js b/test/integration/addresses.js index 4bd71c8..0024410 100644 --- a/test/integration/addresses.js +++ b/test/integration/addresses.js @@ -25,17 +25,6 @@ describe('bitcoinjs-lib (addresses)', function () { assert.strictEqual(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64') }) - it('can generate an address from a SHA256 hash', function () { - const hash = bitcoin.crypto.sha256(Buffer.from('correct horse battery staple')) - - const keyPair = bitcoin.ECPair.fromPrivateKey(hash) - const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) - - // Generating addresses from SHA256 hashes is not secure if the input to the hash function is predictable - // Do not use with predictable inputs - assert.strictEqual(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8') - }) - it('can import an address via WIF', function () { const keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct') const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) From e1049c1090b329a60e5d31679b77bb568e594b9f Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 26 Sep 2018 17:59:04 +1000 Subject: [PATCH 7/7] README: fix emphasis --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28c1b21..9342696 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ This library uses [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1), Unfortunately, this isn't a silver bullet. Often, Javascript itself is working against us by bypassing these counter-measures. -Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in catastrophic fund loss without any warning. -It can do this through undermining your random number generation, [accidentally producing a duplicate `k` value](https://www.nilsschneider.net/2013/01/28/recovering-bitcoin-private-keys.html), sending Bitcoin to a malformed output script, or any of a million different ways. +Problems in [`Buffer (UInt8Array)`](https://github.com/feross/buffer), for example, can trivially result in **catastrophic fund loss** without any warning. +It can do this through undermining your random number generation, accidentally producing a [duplicate `k` value](https://www.nilsschneider.net/2013/01/28/recovering-bitcoin-private-keys.html), sending Bitcoin to a malformed output script, or any of a million different ways. Running tests in your target environment is important and a recommended step to verify continuously. Finally, **adhere to best practice**.