2014-10-17 04:31:01 +02:00
|
|
|
/* global describe, it, beforeEach */
|
|
|
|
/* eslint-disable no-new */
|
|
|
|
|
2018-06-25 08:24:37 +02:00
|
|
|
const assert = require('assert')
|
|
|
|
const proxyquire = require('proxyquire')
|
|
|
|
const hoodwink = require('hoodwink')
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-06-25 08:24:37 +02:00
|
|
|
const ECPair = require('../src/ecpair')
|
|
|
|
const tinysecp = require('tiny-secp256k1')
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-06-25 08:24:37 +02:00
|
|
|
const fixtures = require('./fixtures/ecpair.json')
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-06-25 08:24:37 +02:00
|
|
|
const NETWORKS = require('../src/networks')
|
|
|
|
const NETWORKS_LIST = [] // Object.values(NETWORKS)
|
2018-05-22 08:33:43 +02:00
|
|
|
for (let networkName in NETWORKS) {
|
2015-07-28 08:42:57 +02:00
|
|
|
NETWORKS_LIST.push(NETWORKS[networkName])
|
|
|
|
}
|
|
|
|
|
2018-06-25 08:24:37 +02:00
|
|
|
const ZERO = Buffer.alloc(32, 0)
|
|
|
|
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')
|
|
|
|
const GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')
|
|
|
|
const GROUP_ORDER_LESS_1 = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', 'hex')
|
2018-05-22 08:33:43 +02:00
|
|
|
|
2014-10-17 04:31:01 +02:00
|
|
|
describe('ECPair', function () {
|
2018-07-26 09:35:31 +02:00
|
|
|
describe('getPublicKey', function () {
|
|
|
|
let keyPair
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
keyPair = ECPair.fromPrivateKey(ONE)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('calls pointFromScalar lazily', hoodwink(function () {
|
|
|
|
assert.strictEqual(keyPair.__Q, null)
|
|
|
|
|
|
|
|
// .publicKey forces the memoization
|
|
|
|
assert.strictEqual(keyPair.publicKey.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
|
|
|
assert.strictEqual(keyPair.__Q.toString('hex'), '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('fromPrivateKey', function () {
|
2014-10-17 04:31:01 +02:00
|
|
|
it('defaults to compressed', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.fromPrivateKey(ONE)
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(keyPair.compressed, true)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
it('supports the uncompressed option', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.fromPrivateKey(ONE, {
|
2014-10-17 04:31:01 +02:00
|
|
|
compressed: false
|
|
|
|
})
|
|
|
|
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(keyPair.compressed, false)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
it('supports the network option', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.fromPrivateKey(ONE, {
|
2014-10-17 04:31:01 +02:00
|
|
|
compressed: false,
|
2015-07-28 08:42:57 +02:00
|
|
|
network: NETWORKS.testnet
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
2015-07-28 08:42:57 +02:00
|
|
|
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
fixtures.valid.forEach(function (f) {
|
2018-05-22 08:33:43 +02:00
|
|
|
it('derives public key for ' + f.WIF, function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const d = Buffer.from(f.d, 'hex')
|
|
|
|
const keyPair = ECPair.fromPrivateKey(d, {
|
2014-10-17 04:31:01 +02:00
|
|
|
compressed: f.compressed
|
|
|
|
})
|
|
|
|
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(keyPair.publicKey.toString('hex'), f.Q)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-07-26 09:35:31 +02:00
|
|
|
fixtures.invalid.fromPrivateKey.forEach(function (f) {
|
2015-09-12 07:17:45 +02:00
|
|
|
it('throws ' + f.exception, function () {
|
2018-07-26 09:35:31 +02:00
|
|
|
const d = Buffer.from(f.d, 'hex')
|
|
|
|
assert.throws(function () {
|
|
|
|
ECPair.fromPrivateKey(d, f.options)
|
|
|
|
}, new RegExp(f.exception))
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-07-26 09:35:31 +02:00
|
|
|
describe('fromPublicKey', function () {
|
|
|
|
fixtures.invalid.fromPublicKey.forEach(function (f) {
|
|
|
|
it('throws ' + f.exception, function () {
|
|
|
|
const Q = Buffer.from(f.Q, 'hex')
|
|
|
|
assert.throws(function () {
|
|
|
|
ECPair.fromPublicKey(Q, f.options)
|
|
|
|
}, new RegExp(f.exception))
|
|
|
|
})
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('fromWIF', function () {
|
|
|
|
fixtures.valid.forEach(function (f) {
|
2015-07-28 08:42:57 +02:00
|
|
|
it('imports ' + f.WIF + ' (' + f.network + ')', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const network = NETWORKS[f.network]
|
|
|
|
const keyPair = ECPair.fromWIF(f.WIF, network)
|
2015-07-28 08:42:57 +02:00
|
|
|
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
|
2015-07-28 08:42:57 +02:00
|
|
|
assert.strictEqual(keyPair.compressed, f.compressed)
|
|
|
|
assert.strictEqual(keyPair.network, network)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
fixtures.valid.forEach(function (f) {
|
|
|
|
it('imports ' + f.WIF + ' (via list of networks)', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(keyPair.privateKey.toString('hex'), f.d)
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(keyPair.compressed, f.compressed)
|
2015-07-28 08:42:57 +02:00
|
|
|
assert.strictEqual(keyPair.network, NETWORKS[f.network])
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
fixtures.invalid.fromWIF.forEach(function (f) {
|
2015-08-20 12:16:57 +02:00
|
|
|
it('throws on ' + f.WIF, function () {
|
2014-10-17 04:31:01 +02:00
|
|
|
assert.throws(function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const networks = f.network ? NETWORKS[f.network] : NETWORKS_LIST
|
2016-02-25 03:26:05 +01:00
|
|
|
|
|
|
|
ECPair.fromWIF(f.WIF, networks)
|
2014-10-17 04:31:01 +02:00
|
|
|
}, new RegExp(f.exception))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('toWIF', function () {
|
|
|
|
fixtures.valid.forEach(function (f) {
|
2015-07-28 08:42:57 +02:00
|
|
|
it('exports ' + f.WIF, function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
|
|
|
const result = keyPair.toWIF()
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(result, f.WIF)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('makeRandom', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const d = Buffer.alloc(32, 4)
|
|
|
|
const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'
|
2014-10-17 04:31:01 +02:00
|
|
|
|
|
|
|
describe('uses randombytes RNG', function () {
|
|
|
|
it('generates a ECPair', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const stub = { randombytes: function () { return d } }
|
|
|
|
const ProxiedECPair = proxyquire('../src/ecpair', stub)
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ProxiedECPair.makeRandom()
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(keyPair.toWIF(), exWIF)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it('allows a custom RNG to be used', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.makeRandom({
|
2014-10-17 04:31:01 +02:00
|
|
|
rng: function (size) { return d.slice(0, size) }
|
|
|
|
})
|
|
|
|
|
2015-05-07 03:29:20 +02:00
|
|
|
assert.strictEqual(keyPair.toWIF(), exWIF)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
2015-09-08 15:39:31 +02:00
|
|
|
|
2016-10-10 04:01:51 +02:00
|
|
|
it('retains the same defaults as ECPair constructor', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.makeRandom()
|
2016-10-10 04:01:51 +02:00
|
|
|
|
|
|
|
assert.strictEqual(keyPair.compressed, true)
|
|
|
|
assert.strictEqual(keyPair.network, NETWORKS.bitcoin)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('supports the options parameter', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const keyPair = ECPair.makeRandom({
|
2016-10-10 04:01:51 +02:00
|
|
|
compressed: false,
|
|
|
|
network: NETWORKS.testnet
|
|
|
|
})
|
|
|
|
|
|
|
|
assert.strictEqual(keyPair.compressed, false)
|
|
|
|
assert.strictEqual(keyPair.network, NETWORKS.testnet)
|
|
|
|
})
|
|
|
|
|
2018-03-20 03:19:39 +01:00
|
|
|
it('throws if d is bad length', function () {
|
|
|
|
function rng () {
|
2018-05-22 08:33:43 +02:00
|
|
|
return Buffer.alloc(28)
|
2018-03-20 03:19:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
assert.throws(function () {
|
|
|
|
ECPair.makeRandom({ rng: rng })
|
|
|
|
}, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/)
|
|
|
|
})
|
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
it('loops until d is within interval [1, n) : 1', hoodwink(function () {
|
2018-09-13 09:34:50 +02:00
|
|
|
const rng = this.stub(function () {
|
|
|
|
if (rng.calls === 0) return ZERO // 0
|
2018-05-22 08:33:43 +02:00
|
|
|
return ONE // >0
|
2018-03-20 03:19:39 +01:00
|
|
|
}, 2)
|
2016-12-13 23:42:21 +01:00
|
|
|
|
|
|
|
ECPair.makeRandom({ rng: rng })
|
|
|
|
}))
|
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
it('loops until d is within interval [1, n) : n - 1', hoodwink(function () {
|
2018-09-13 09:34:50 +02:00
|
|
|
const rng = this.stub(function () {
|
|
|
|
if (rng.calls === 0) return ZERO // <1
|
|
|
|
if (rng.calls === 1) return GROUP_ORDER // >n-1
|
2018-05-22 08:33:43 +02:00
|
|
|
return GROUP_ORDER_LESS_1 // n-1
|
2018-03-20 03:19:39 +01:00
|
|
|
}, 3)
|
2015-09-08 15:39:31 +02:00
|
|
|
|
|
|
|
ECPair.makeRandom({ rng: rng })
|
|
|
|
}))
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
2018-05-30 03:19:46 +02:00
|
|
|
describe('.network', function () {
|
2015-09-21 09:37:21 +02:00
|
|
|
fixtures.valid.forEach(function (f) {
|
|
|
|
it('returns ' + f.network + ' for ' + f.WIF, function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
const network = NETWORKS[f.network]
|
|
|
|
const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST)
|
2015-09-21 09:37:21 +02:00
|
|
|
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(keyPair.network, network)
|
2015-09-21 09:37:21 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
describe('tinysecp wrappers', function () {
|
2018-06-25 08:37:45 +02:00
|
|
|
let keyPair
|
|
|
|
let hash
|
|
|
|
let signature
|
2014-10-17 04:31:01 +02:00
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
keyPair = ECPair.makeRandom()
|
2018-05-22 08:33:43 +02:00
|
|
|
hash = ZERO
|
|
|
|
signature = Buffer.alloc(64, 1)
|
2014-10-17 04:31:01 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('signing', function () {
|
2018-05-22 08:33:43 +02:00
|
|
|
it('wraps tinysecp.sign', hoodwink(function () {
|
|
|
|
this.mock(tinysecp, 'sign', function (h, d) {
|
2018-03-20 03:19:39 +01:00
|
|
|
assert.strictEqual(h, hash)
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(d, keyPair.privateKey)
|
2018-05-22 08:33:43 +02:00
|
|
|
return signature
|
2018-03-20 03:19:39 +01:00
|
|
|
}, 1)
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
assert.strictEqual(keyPair.sign(hash), signature)
|
2014-10-17 04:31:01 +02:00
|
|
|
}))
|
|
|
|
|
|
|
|
it('throws if no private key is found', function () {
|
2018-05-22 08:33:43 +02:00
|
|
|
delete keyPair.__d
|
2014-10-17 04:31:01 +02:00
|
|
|
|
|
|
|
assert.throws(function () {
|
|
|
|
keyPair.sign(hash)
|
|
|
|
}, /Missing private key/)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('verify', function () {
|
2018-05-22 08:33:43 +02:00
|
|
|
it('wraps tinysecp.verify', hoodwink(function () {
|
|
|
|
this.mock(tinysecp, 'verify', function (h, q, s) {
|
2018-03-20 03:19:39 +01:00
|
|
|
assert.strictEqual(h, hash)
|
2018-05-30 03:19:46 +02:00
|
|
|
assert.strictEqual(q, keyPair.publicKey)
|
2018-05-22 08:33:43 +02:00
|
|
|
assert.strictEqual(s, signature)
|
|
|
|
return true
|
2018-03-20 03:19:39 +01:00
|
|
|
}, 1)
|
2014-10-17 04:31:01 +02:00
|
|
|
|
2018-05-22 08:33:43 +02:00
|
|
|
assert.strictEqual(keyPair.verify(hash, signature), true)
|
2014-10-17 04:31:01 +02:00
|
|
|
}))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|