commit
96240b636d
5 changed files with 30 additions and 293 deletions
|
@ -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)
|
||||
|
|
40
README.md
40
README.md
|
@ -23,9 +23,10 @@ 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
|
||||
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,12 +43,37 @@ 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](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://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.
|
||||
* Lastly, if you can, use [Typescript](https://www.typescriptlang.org/) or similar.
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
|
@ -83,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)
|
||||
|
@ -114,13 +139,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)
|
||||
- [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)
|
||||
|
||||
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)!
|
||||
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
|
@ -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))
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue