rm ecdsa, add new ECPair using secp256k1

This commit is contained in:
Daniel Cousens 2018-05-22 16:33:43 +10:00
commit fba0699dd3
18 changed files with 227 additions and 576 deletions

View file

@ -1,163 +0,0 @@
var Buffer = require('safe-buffer').Buffer
var createHmac = require('create-hmac')
var typeforce = require('typeforce')
var types = require('./types')
var BigInteger = require('bigi')
var ZERO = Buffer.alloc(1, 0)
var ONE = Buffer.alloc(1, 1)
var ecurve = require('ecurve')
var secp256k1 = ecurve.getCurveByName('secp256k1')
// https://tools.ietf.org/html/rfc6979#section-3.2
function deterministicGenerateK (hash, x, checkSig) {
typeforce(types.tuple(
types.Hash256bit,
types.Buffer256bit,
types.Function
), arguments)
// Step A, ignored as hash already provided
// Step B
// Step C
var k = Buffer.alloc(32, 0)
var v = Buffer.alloc(32, 1)
// Step D
k = createHmac('sha256', k)
.update(v)
.update(ZERO)
.update(x)
.update(hash)
.digest()
// Step E
v = createHmac('sha256', k).update(v).digest()
// Step F
k = createHmac('sha256', k)
.update(v)
.update(ONE)
.update(x)
.update(hash)
.digest()
// Step G
v = createHmac('sha256', k).update(v).digest()
// Step H1/H2a, ignored as tlen === qlen (256 bit)
// Step H2b
v = createHmac('sha256', k).update(v).digest()
var T = BigInteger.fromBuffer(v)
// Step H3, repeat until T is within the interval [1, n - 1] and is suitable for ECDSA
while (T.signum() <= 0 || T.compareTo(secp256k1.n) >= 0 || !checkSig(T)) {
k = createHmac('sha256', k)
.update(v)
.update(ZERO)
.digest()
v = createHmac('sha256', k).update(v).digest()
// Step H1/H2a, again, ignored as tlen === qlen (256 bit)
// Step H2b again
v = createHmac('sha256', k).update(v).digest()
T = BigInteger.fromBuffer(v)
}
return T
}
var N_OVER_TWO = secp256k1.n.shiftRight(1)
function sign (hash, d) {
typeforce(types.tuple(types.Hash256bit, types.BigInt), arguments)
var x = d.toBuffer(32)
var e = BigInteger.fromBuffer(hash)
var n = secp256k1.n
var G = secp256k1.G
var r, s
deterministicGenerateK(hash, x, function (k) {
var Q = G.multiply(k)
if (secp256k1.isInfinity(Q)) return false
r = Q.affineX.mod(n)
if (r.signum() === 0) return false
s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n)
if (s.signum() === 0) return false
return true
})
// enforce low S values, see bip62: 'low s values in signatures'
if (s.compareTo(N_OVER_TWO) > 0) {
s = n.subtract(s)
}
return {
r: r,
s: s
}
}
function verify (hash, signature, Q) {
typeforce(types.tuple(
types.Hash256bit,
types.ECSignature,
types.ECPoint
), arguments)
var n = secp256k1.n
var G = secp256k1.G
var r = signature.r
var s = signature.s
// 1.4.1 Enforce r and s are both integers in the interval [1, n 1]
if (r.signum() <= 0 || r.compareTo(n) >= 0) return false
if (s.signum() <= 0 || s.compareTo(n) >= 0) return false
// 1.4.2 H = Hash(M), already done by the user
// 1.4.3 e = H
var e = BigInteger.fromBuffer(hash)
// Compute s^-1
var sInv = s.modInverse(n)
// 1.4.4 Compute u1 = es^1 mod n
// u2 = rs^1 mod n
var u1 = e.multiply(sInv).mod(n)
var u2 = r.multiply(sInv).mod(n)
// 1.4.5 Compute R = (xR, yR)
// R = u1G + u2Q
var R = G.multiplyTwo(u1, Q, u2)
// 1.4.5 (cont.) Enforce R is not at infinity
if (secp256k1.isInfinity(R)) return false
// 1.4.6 Convert the field element R.x to an integer
var xR = R.affineX
// 1.4.7 Set v = xR mod n
var v = xR.mod(n)
// 1.4.8 If v = r, output "valid", and if v != r, output "invalid"
return v.equals(r)
}
module.exports = {
deterministicGenerateK: deterministicGenerateK,
sign: sign,
verify: verify,
// TODO: remove
__curve: secp256k1
}

View file

@ -1,63 +1,73 @@
let ecdsa = require('./ecdsa')
let ecc = require('tiny-secp256k1')
let randomBytes = require('randombytes')
let typeforce = require('typeforce')
let types = require('./types')
let wif = require('wif')
let NETWORKS = require('./networks')
let BigInteger = require('bigi')
let ecurve = require('ecurve')
let secp256k1 = ecdsa.__curve
// TODO: why is the function name toJSON weird?
function isPoint (x) { return ecc.isPoint(x) }
let isOptions = typeforce.maybe(typeforce.compile({
compressed: types.maybe(types.Boolean),
network: types.maybe(types.Network)
}))
function ECPair (d, Q, options) {
if (options) {
typeforce({
compressed: types.maybe(types.Boolean),
network: types.maybe(types.Network)
}, options)
}
options = options || {}
if (d) {
if (d.signum() <= 0) throw new Error('Private key must be greater than 0')
if (d.compareTo(secp256k1.n) >= 0) throw new Error('Private key must be less than the curve order')
if (Q) throw new TypeError('Unexpected publicKey parameter')
this.d = d
} else {
typeforce(types.ECPoint, Q)
this.__Q = Q
}
this.compressed = options.compressed === undefined ? true : options.compressed
this.network = options.network || NETWORKS.bitcoin
this.__d = d || null
this.__Q = null
if (Q) this.__Q = ecc.pointCompress(Q, this.compressed)
}
Object.defineProperty(ECPair.prototype, 'Q', {
get: function () {
if (!this.__Q && this.d) {
this.__Q = secp256k1.G.multiply(this.d)
}
return this.__Q
}
})
ECPair.fromPublicKeyBuffer = function (buffer, network) {
var Q = ecurve.Point.decodeFrom(secp256k1, buffer)
return new ECPair(null, Q, {
compressed: Q.compressed,
network: network
})
ECPair.prototype.getNetwork = function () {
return this.network
}
ECPair.fromWIF = function (string, network) {
var decoded = wif.decode(string)
var version = decoded.version
ECPair.prototype.getPrivateKey = function () {
return this.__d
}
ECPair.prototype.getPublicKey = function () {
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed)
return this.__Q
}
ECPair.prototype.toWIF = function () {
if (!this.__d) throw new Error('Missing private key')
return wif.encode(this.network.wif, this.__d, this.compressed)
}
ECPair.prototype.sign = function (hash) {
if (!this.__d) throw new Error('Missing private key')
return ecc.sign(hash, this.__d)
}
ECPair.prototype.verify = function (hash, signature) {
return ecc.verify(hash, this.getPublicKey(), signature)
}
function fromPrivateKey (buffer, options) {
typeforce(types.Buffer256bit, buffer)
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)')
typeforce(isOptions, options)
return new ECPair(buffer, null, options)
}
function fromPublicKey (buffer, options) {
typeforce(isPoint, buffer)
typeforce(isOptions, options)
return new ECPair(null, buffer, options)
}
function fromWIF (string, network) {
let decoded = wif.decode(string)
let version = decoded.version
// list of networks?
if (types.Array(network)) {
@ -74,58 +84,29 @@ ECPair.fromWIF = function (string, network) {
if (version !== network.wif) throw new Error('Invalid network version')
}
var d = BigInteger.fromBuffer(decoded.privateKey)
return new ECPair(d, null, {
return fromPrivateKey(decoded.privateKey, {
compressed: decoded.compressed,
network: network
})
}
ECPair.makeRandom = function (options) {
function makeRandom (options) {
typeforce(isOptions, options)
options = options || {}
let rng = options.rng || randomBytes
var rng = options.rng || randomBytes
var d
let d
do {
var buffer = rng(32)
typeforce(types.Buffer256bit, buffer)
d = rng(32)
typeforce(types.Buffer256bit, d)
} while (!ecc.isPrivate(d))
d = BigInteger.fromBuffer(buffer)
} while (d.signum() <= 0 || d.compareTo(secp256k1.n) >= 0)
return new ECPair(d, null, options)
return fromPrivateKey(d, options)
}
ECPair.prototype.getNetwork = function () {
return this.network
module.exports = {
makeRandom,
fromPrivateKey,
fromPublicKey,
fromWIF
}
ECPair.prototype.getPublicKeyBuffer = function () {
return this.Q.getEncoded(this.compressed)
}
ECPair.prototype.sign = function (hash) {
if (!this.d) throw new Error('Missing private key')
let signature = ecdsa.sign(hash, this.d)
return Buffer.concat([signature.r.toBuffer(32), signature.s.toBuffer(32)], 64)
}
ECPair.prototype.toWIF = function () {
if (!this.d) throw new Error('Missing private key')
return wif.encode(this.network.wif, this.d.toBuffer(32), this.compressed)
}
ECPair.prototype.verify = function (hash, signature) {
signature = {
r: BigInteger.fromBuffer(signature.slice(0, 32)),
s: BigInteger.fromBuffer(signature.slice(32, 64))
}
return ecdsa.verify(hash, signature, this.Q)
}
module.exports = ECPair

View file

@ -180,7 +180,7 @@ function fixMultisigOrder (input, transaction, vin) {
var unmatched = input.signatures.concat()
input.signatures = input.pubKeys.map(function (pubKey) {
var keyPair = ECPair.fromPublicKeyBuffer(pubKey)
var keyPair = ECPair.fromPublicKey(pubKey)
var match
// check for a signature
@ -686,7 +686,7 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
throw new Error('Inconsistent redeemScript')
}
var kpPubKey = keyPair.publicKey || keyPair.getPublicKeyBuffer()
var kpPubKey = keyPair.publicKey || keyPair.getPublicKey()
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue')

View file

@ -16,11 +16,9 @@ function Satoshi (value) {
}
// external dependent types
var BigInt = typeforce.quacksLike('BigInteger')
var ECPoint = typeforce.quacksLike('Point')
// exposed, external API
var ECSignature = typeforce.compile({ r: BigInt, s: BigInt })
var Network = typeforce.compile({
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
bip32: {
@ -34,11 +32,9 @@ var Network = typeforce.compile({
// extend typeforce types with ours
var types = {
BigInt: BigInt,
BIP32Path: BIP32Path,
Buffer256bit: typeforce.BufferN(32),
ECPoint: ECPoint,
ECSignature: ECSignature,
Hash160bit: typeforce.BufferN(20),
Hash256bit: typeforce.BufferN(32),
Network: Network,