Skip to content

Commit

Permalink
change secp256k1 to secp256r1
Browse files Browse the repository at this point in the history
Signed-off-by: Matus Zamborsky <[email protected]>
  • Loading branch information
backslash47 committed Jul 4, 2018
1 parent fdce425 commit 597008d
Show file tree
Hide file tree
Showing 6 changed files with 3,454 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
hdkey
hdkey - secp256r1 version
=====

[![NPM Package](https://img.shields.io/npm/v/hdkey.svg?style=flat-square)](https://www.npmjs.org/package/hdkey)
Expand Down
20 changes: 10 additions & 10 deletions lib/hdkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ var assert = require('assert')
var Buffer = require('safe-buffer').Buffer
var crypto = require('crypto')
var cs = require('coinstring')
var secp256k1 = require('secp256k1')
var secp256r1 = require('./secp256r1');

var MASTER_SECRET = Buffer.from('Bitcoin seed', 'utf8')
var MASTER_SECRET = Buffer.from('Nist256p1 seed', 'utf8')
var HARDENED_OFFSET = 0x80000000
var LEN = 78

Expand Down Expand Up @@ -32,10 +32,10 @@ Object.defineProperty(HDKey.prototype, 'privateKey', {
},
set: function (value) {
assert.equal(value.length, 32, 'Private key must be 32 bytes.')
assert(secp256k1.privateKeyVerify(value) === true, 'Invalid private key')
assert(secp256r1.privateKeyVerify(value) === true, 'Invalid private key')

this._privateKey = value
this._publicKey = secp256k1.publicKeyCreate(value, true)
this._publicKey = secp256r1.publicKeyCreate(value, true)
this._identifier = hash160(this.publicKey)
this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0)
}
Expand All @@ -47,9 +47,9 @@ Object.defineProperty(HDKey.prototype, 'publicKey', {
},
set: function (value) {
assert(value.length === 33 || value.length === 65, 'Public key must be 33 or 65 bytes.')
assert(secp256k1.publicKeyVerify(value) === true, 'Invalid public key')
assert(secp256r1.publicKeyVerify(value) === true, 'Invalid public key')

this._publicKey = secp256k1.publicKeyConvert(value, true) // force compressed point
this._publicKey = secp256r1.publicKeyConvert(value, true) // force compressed point
this._identifier = hash160(this.publicKey)
this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0)
this._privateKey = null
Expand Down Expand Up @@ -125,7 +125,7 @@ HDKey.prototype.deriveChild = function (index) {
if (this.privateKey) {
// ki = parse256(IL) + kpar (mod n)
try {
hd.privateKey = secp256k1.privateKeyTweakAdd(this.privateKey, IL)
hd.privateKey = secp256r1.privateKeyTweakAdd(this.privateKey, IL)
// throw if IL >= n || (privateKey + IL) === 0
} catch (err) {
// In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i
Expand All @@ -136,7 +136,7 @@ HDKey.prototype.deriveChild = function (index) {
// Ki = point(parse256(IL)) + Kpar
// = G*IL + Kpar
try {
hd.publicKey = secp256k1.publicKeyTweakAdd(this.publicKey, IL, true)
hd.publicKey = secp256r1.publicKeyTweakAdd(this.publicKey, IL, true)
// throw if IL >= n || (g**IL + publicKey) is infinity
} catch (err) {
// In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i
Expand All @@ -153,11 +153,11 @@ HDKey.prototype.deriveChild = function (index) {
}

HDKey.prototype.sign = function (hash) {
return secp256k1.sign(hash, this.privateKey).signature
return secp256r1.sign(hash, this.privateKey).signature
}

HDKey.prototype.verify = function (hash, signature) {
return secp256k1.verify(hash, signature, this.publicKey)
return secp256r1.verify(hash, signature, this.publicKey)
}

HDKey.prototype.toJSON = function () {
Expand Down
38 changes: 38 additions & 0 deletions lib/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"COMPRESSED_TYPE_INVALID": "compressed should be a boolean",
"EC_PRIVATE_KEY_TYPE_INVALID": "private key should be a Buffer",
"EC_PRIVATE_KEY_LENGTH_INVALID": "private key length is invalid",
"EC_PRIVATE_KEY_RANGE_INVALID": "private key range is invalid",
"EC_PRIVATE_KEY_TWEAK_ADD_FAIL": "tweak out of range or resulting private key is invalid",
"EC_PRIVATE_KEY_TWEAK_MUL_FAIL": "tweak out of range",
"EC_PRIVATE_KEY_EXPORT_DER_FAIL": "couldn't export to DER format",
"EC_PRIVATE_KEY_IMPORT_DER_FAIL": "couldn't import from DER format",
"EC_PUBLIC_KEYS_TYPE_INVALID": "public keys should be an Array",
"EC_PUBLIC_KEYS_LENGTH_INVALID": "public keys Array should have at least 1 element",
"EC_PUBLIC_KEY_TYPE_INVALID": "public key should be a Buffer",
"EC_PUBLIC_KEY_LENGTH_INVALID": "public key length is invalid",
"EC_PUBLIC_KEY_PARSE_FAIL": "the public key could not be parsed or is invalid",
"EC_PUBLIC_KEY_CREATE_FAIL": "private was invalid, try again",
"EC_PUBLIC_KEY_TWEAK_ADD_FAIL": "tweak out of range or resulting public key is invalid",
"EC_PUBLIC_KEY_TWEAK_MUL_FAIL": "tweak out of range",
"EC_PUBLIC_KEY_COMBINE_FAIL": "the sum of the public keys is not valid",
"ECDH_FAIL": "scalar was invalid (zero or overflow)",
"ECDSA_SIGNATURE_TYPE_INVALID": "signature should be a Buffer",
"ECDSA_SIGNATURE_LENGTH_INVALID": "signature length is invalid",
"ECDSA_SIGNATURE_PARSE_FAIL": "couldn't parse signature",
"ECDSA_SIGNATURE_PARSE_DER_FAIL": "couldn't parse DER signature",
"ECDSA_SIGNATURE_SERIALIZE_DER_FAIL": "couldn't serialize signature to DER format",
"ECDSA_SIGN_FAIL": "nonce generation function failed or private key is invalid",
"ECDSA_RECOVER_FAIL": "couldn't recover public key from signature",
"MSG32_TYPE_INVALID": "message should be a Buffer",
"MSG32_LENGTH_INVALID": "message length is invalid",
"OPTIONS_TYPE_INVALID": "options should be an Object",
"OPTIONS_DATA_TYPE_INVALID": "options.data should be a Buffer",
"OPTIONS_DATA_LENGTH_INVALID": "options.data length is invalid",
"OPTIONS_NONCEFN_TYPE_INVALID": "options.noncefn should be a Function",
"RECOVERY_ID_TYPE_INVALID": "recovery should be a Number",
"RECOVERY_ID_VALUE_INVALID": "recovery should have value between -1 and 4",
"TWEAK_TYPE_INVALID": "tweak should be a Buffer",
"TWEAK_LENGTH_INVALID": "tweak length is invalid"
}

260 changes: 260 additions & 0 deletions lib/secp256r1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
'use strict'
var Buffer = require('safe-buffer').Buffer
var createHash = require('create-hash')
var BN = require('bn.js')
var EC = require('elliptic').ec

var messages = require('./messages.json')

var ec = new EC('p256')
var ecparams = ec.curve

function loadCompressedPublicKey (first, xBuffer) {
var x = new BN(xBuffer)

// overflow
if (x.cmp(ecparams.p) >= 0) return null
x = x.toRed(ecparams.red)

// compute corresponding Y
var y = x.redSqr().redIMul(x).redIAdd(ecparams.b).redSqrt()
if ((first === 0x03) !== y.isOdd()) y = y.redNeg()

return ec.keyPair({ pub: { x: x, y: y } })
}

function loadUncompressedPublicKey (first, xBuffer, yBuffer) {
var x = new BN(xBuffer)
var y = new BN(yBuffer)

// overflow
if (x.cmp(ecparams.p) >= 0 || y.cmp(ecparams.p) >= 0) return null

x = x.toRed(ecparams.red)
y = y.toRed(ecparams.red)

// is odd flag
if ((first === 0x06 || first === 0x07) && y.isOdd() !== (first === 0x07)) return null

// x*x*x + b = y*y
var x3 = x.redSqr().redIMul(x)
if (!y.redSqr().redISub(x3.redIAdd(ecparams.b)).isZero()) return null

return ec.keyPair({ pub: { x: x, y: y } })
}

function loadPublicKey (publicKey) {
var first = publicKey[0]
switch (first) {
case 0x02:
case 0x03:
if (publicKey.length !== 33) return null
return loadCompressedPublicKey(first, publicKey.slice(1, 33))
case 0x04:
case 0x06:
case 0x07:
if (publicKey.length !== 65) return null
return loadUncompressedPublicKey(first, publicKey.slice(1, 33), publicKey.slice(33, 65))
default:
return null
}
}

exports.privateKeyVerify = function (privateKey) {
var bn = new BN(privateKey)
return bn.cmp(ecparams.n) < 0 && !bn.isZero()
}

exports.privateKeyExport = function (privateKey, compressed) {
var d = new BN(privateKey)
if (d.cmp(ecparams.n) >= 0 || d.isZero()) throw new Error(messages.EC_PRIVATE_KEY_EXPORT_DER_FAIL)

return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, true))
}

exports.privateKeyNegate = function (privateKey) {
var bn = new BN(privateKey)
return bn.isZero() ? Buffer.alloc(32) : ecparams.n.sub(bn).umod(ecparams.n).toArrayLike(Buffer, 'be', 32)
}

exports.privateKeyModInverse = function (privateKey) {
var bn = new BN(privateKey)
if (bn.cmp(ecparams.n) >= 0 || bn.isZero()) throw new Error(messages.EC_PRIVATE_KEY_RANGE_INVALID)

return bn.invm(ecparams.n).toArrayLike(Buffer, 'be', 32)
}

exports.privateKeyTweakAdd = function (privateKey, tweak) {
var bn = new BN(tweak)
if (bn.cmp(ecparams.n) >= 0) throw new Error(messages.EC_PRIVATE_KEY_TWEAK_ADD_FAIL)

bn.iadd(new BN(privateKey))
if (bn.cmp(ecparams.n) >= 0) bn.isub(ecparams.n)
if (bn.isZero()) throw new Error(messages.EC_PRIVATE_KEY_TWEAK_ADD_FAIL)

return bn.toArrayLike(Buffer, 'be', 32)
}

exports.privateKeyTweakMul = function (privateKey, tweak) {
var bn = new BN(tweak)
if (bn.cmp(ecparams.n) >= 0 || bn.isZero()) throw new Error(messages.EC_PRIVATE_KEY_TWEAK_MUL_FAIL)

bn.imul(new BN(privateKey))
if (bn.cmp(ecparams.n)) bn = bn.umod(ecparams.n)

return bn.toArrayLike(Buffer, 'be', 32)
}

exports.publicKeyCreate = function (privateKey, compressed) {
var d = new BN(privateKey)
if (d.cmp(ecparams.n) >= 0 || d.isZero()) throw new Error(messages.EC_PUBLIC_KEY_CREATE_FAIL)

return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, true))
}

exports.publicKeyConvert = function (publicKey, compressed) {
var pair = loadPublicKey(publicKey)
if (pair === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)

return Buffer.from(pair.getPublic(compressed, true))
}

exports.publicKeyVerify = function (publicKey) {
return loadPublicKey(publicKey) !== null
}

exports.publicKeyTweakAdd = function (publicKey, tweak, compressed) {
var pair = loadPublicKey(publicKey)
if (pair === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)

tweak = new BN(tweak)
if (tweak.cmp(ecparams.n) >= 0) throw new Error(messages.EC_PUBLIC_KEY_TWEAK_ADD_FAIL)

return Buffer.from(ecparams.g.mul(tweak).add(pair.pub).encode(true, compressed))
}

exports.publicKeyTweakMul = function (publicKey, tweak, compressed) {
var pair = loadPublicKey(publicKey)
if (pair === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)

tweak = new BN(tweak)
if (tweak.cmp(ecparams.n) >= 0 || tweak.isZero()) throw new Error(messages.EC_PUBLIC_KEY_TWEAK_MUL_FAIL)

return Buffer.from(pair.pub.mul(tweak).encode(true, compressed))
}

exports.publicKeyCombine = function (publicKeys, compressed) {
var pairs = new Array(publicKeys.length)
for (var i = 0; i < publicKeys.length; ++i) {
pairs[i] = loadPublicKey(publicKeys[i])
if (pairs[i] === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)
}

var point = pairs[0].pub
for (var j = 1; j < pairs.length; ++j) point = point.add(pairs[j].pub)
if (point.isInfinity()) throw new Error(messages.EC_PUBLIC_KEY_COMBINE_FAIL)

return Buffer.from(point.encode(true, compressed))
}

exports.signatureNormalize = function (signature) {
var r = new BN(signature.slice(0, 32))
var s = new BN(signature.slice(32, 64))
if (r.cmp(ecparams.n) >= 0 || s.cmp(ecparams.n) >= 0) throw new Error(messages.ECDSA_SIGNATURE_PARSE_FAIL)

var result = Buffer.from(signature)
if (s.cmp(ec.nh) === 1) ecparams.n.sub(s).toArrayLike(Buffer, 'be', 32).copy(result, 32)

return result
}

exports.signatureExport = function (signature) {
var r = signature.slice(0, 32)
var s = signature.slice(32, 64)
if (new BN(r).cmp(ecparams.n) >= 0 || new BN(s).cmp(ecparams.n) >= 0) throw new Error(messages.ECDSA_SIGNATURE_PARSE_FAIL)

return { r: r, s: s }
}

exports.signatureImport = function (sigObj) {
var r = new BN(sigObj.r)
if (r.cmp(ecparams.n) >= 0) r = new BN(0)

var s = new BN(sigObj.s)
if (s.cmp(ecparams.n) >= 0) s = new BN(0)

return Buffer.concat([
r.toArrayLike(Buffer, 'be', 32),
s.toArrayLike(Buffer, 'be', 32)
])
}

exports.sign = function (message, privateKey, noncefn, data) {
if (typeof noncefn === 'function') {
var getNonce = noncefn
noncefn = function (counter) {
var nonce = getNonce(message, privateKey, null, data, counter)
if (!Buffer.isBuffer(nonce) || nonce.length !== 32) throw new Error(messages.ECDSA_SIGN_FAIL)

return new BN(nonce)
}
}

var d = new BN(privateKey)
if (d.cmp(ecparams.n) >= 0 || d.isZero()) throw new Error(messages.ECDSA_SIGN_FAIL)

var result = ec.sign(message, privateKey, { canonical: true, k: noncefn, pers: data })
return {
signature: Buffer.concat([
result.r.toArrayLike(Buffer, 'be', 32),
result.s.toArrayLike(Buffer, 'be', 32)
]),
recovery: result.recoveryParam
}
}

exports.verify = function (message, signature, publicKey) {
var sigObj = {r: signature.slice(0, 32), s: signature.slice(32, 64)}

var sigr = new BN(sigObj.r)
var sigs = new BN(sigObj.s)
if (sigr.cmp(ecparams.n) >= 0 || sigs.cmp(ecparams.n) >= 0) throw new Error(messages.ECDSA_SIGNATURE_PARSE_FAIL)
if (sigs.cmp(ec.nh) === 1 || sigr.isZero() || sigs.isZero()) return false

var pair = loadPublicKey(publicKey)
if (pair === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)

return ec.verify(message, sigObj, {x: pair.pub.x, y: pair.pub.y})
}

exports.recover = function (message, signature, recovery, compressed) {
var sigObj = {r: signature.slice(0, 32), s: signature.slice(32, 64)}

var sigr = new BN(sigObj.r)
var sigs = new BN(sigObj.s)
if (sigr.cmp(ecparams.n) >= 0 || sigs.cmp(ecparams.n) >= 0) throw new Error(messages.ECDSA_SIGNATURE_PARSE_FAIL)

try {
if (sigr.isZero() || sigs.isZero()) throw new Error()

var point = ec.recoverPubKey(message, sigObj, recovery)
return Buffer.from(point.encode(true, compressed))
} catch (err) {
throw new Error(messages.ECDSA_RECOVER_FAIL)
}
}

exports.ecdh = function (publicKey, privateKey) {
var shared = exports.ecdhUnsafe(publicKey, privateKey, true)
return createHash('sha256').update(shared).digest()
}

exports.ecdhUnsafe = function (publicKey, privateKey, compressed) {
var pair = loadPublicKey(publicKey)
if (pair === null) throw new Error(messages.EC_PUBLIC_KEY_PARSE_FAIL)

var scalar = new BN(privateKey)
if (scalar.cmp(ecparams.n) >= 0 || scalar.isZero()) throw new Error(messages.ECDH_FAIL)

return Buffer.from(pair.pub.mul(scalar).encode(true, compressed))
}
Loading

0 comments on commit 597008d

Please sign in to comment.