123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- const BN = require('bn.js')
- const EC = require('elliptic').ec
- const secp256k1 = new EC('secp256k1')
- const deterministicGenerateK = require('./rfc6979')
-
- const ZERO32 = Buffer.alloc(32, 0)
- const EC_GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex')
- const EC_P = Buffer.from('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 'hex')
-
- const n = secp256k1.curve.n
- const nDiv2 = n.shrn(1)
- const G = secp256k1.curve.g
-
- const THROW_BAD_PRIVATE = 'Expected Private'
- const THROW_BAD_POINT = 'Expected Point'
- const THROW_BAD_TWEAK = 'Expected Tweak'
- const THROW_BAD_HASH = 'Expected Hash'
- const THROW_BAD_SIGNATURE = 'Expected Signature'
- const THROW_BAD_EXTRA_DATA = 'Expected Extra Data (32 bytes)'
-
- function isScalar (x) {
- return Buffer.isBuffer(x) && x.length === 32
- }
-
- function isOrderScalar (x) {
- if (!isScalar(x)) return false
- return x.compare(EC_GROUP_ORDER) < 0 // < G
- }
-
- function isPoint (p) {
- if (!Buffer.isBuffer(p)) return false
- if (p.length < 33) return false
-
- const t = p[0]
- const x = p.slice(1, 33)
- if (x.compare(ZERO32) === 0) return false
- if (x.compare(EC_P) >= 0) return false
- if ((t === 0x02 || t === 0x03) && p.length === 33) {
- try { decodeFrom(p) } catch (e) { return false } // TODO: temporary
- return true
- }
-
- const y = p.slice(33)
- if (y.compare(ZERO32) === 0) return false
- if (y.compare(EC_P) >= 0) return false
- if (t === 0x04 && p.length === 65) return true
- return false
- }
-
- function __isPointCompressed (p) {
- return p[0] !== 0x04
- }
-
- function isPointCompressed (p) {
- if (!isPoint(p)) return false
- return __isPointCompressed(p)
- }
-
- function isPrivate (x) {
- if (!isScalar(x)) return false
- return x.compare(ZERO32) > 0 && // > 0
- x.compare(EC_GROUP_ORDER) < 0 // < G
- }
-
- function isSignature (value) {
- const r = value.slice(0, 32)
- const s = value.slice(32, 64)
- return Buffer.isBuffer(value) && value.length === 64 &&
- r.compare(EC_GROUP_ORDER) < 0 &&
- s.compare(EC_GROUP_ORDER) < 0
- }
-
- function assumeCompression (value, pubkey) {
- if (value === undefined && pubkey !== undefined) return __isPointCompressed(pubkey)
- if (value === undefined) return true
- return value
- }
-
- function fromBuffer (d) { return new BN(d) }
- function toBuffer (d) { return d.toArrayLike(Buffer, 'be', 32) }
- function decodeFrom (P) { return secp256k1.curve.decodePoint(P) }
- function getEncoded (P, compressed) { return Buffer.from(P._encode(compressed)) }
-
- function pointAdd (pA, pB, __compressed) {
- if (!isPoint(pA)) throw new TypeError(THROW_BAD_POINT)
- if (!isPoint(pB)) throw new TypeError(THROW_BAD_POINT)
-
- const a = decodeFrom(pA)
- const b = decodeFrom(pB)
- const pp = a.add(b)
- if (pp.isInfinity()) return null
-
- const compressed = assumeCompression(__compressed, pA)
- return getEncoded(pp, compressed)
- }
-
- function pointAddScalar (p, tweak, __compressed) {
- if (!isPoint(p)) throw new TypeError(THROW_BAD_POINT)
- if (!isOrderScalar(tweak)) throw new TypeError(THROW_BAD_TWEAK)
-
- const compressed = assumeCompression(__compressed, p)
- const pp = decodeFrom(p)
- if (tweak.compare(ZERO32) === 0) return getEncoded(pp, compressed)
-
- const tt = fromBuffer(tweak)
- const qq = G.mul(tt)
- const uu = pp.add(qq)
- if (uu.isInfinity()) return null
-
- return getEncoded(uu, compressed)
- }
-
- function pointCompress (p, __compressed) {
- if (!isPoint(p)) throw new TypeError(THROW_BAD_POINT)
-
- const pp = decodeFrom(p)
- if (pp.isInfinity()) throw new TypeError(THROW_BAD_POINT)
-
- const compressed = assumeCompression(__compressed, p)
-
- return getEncoded(pp, compressed)
- }
-
- function pointFromScalar (d, __compressed) {
- if (!isPrivate(d)) throw new TypeError(THROW_BAD_PRIVATE)
-
- const dd = fromBuffer(d)
- const pp = G.mul(dd)
- if (pp.isInfinity()) return null
-
- const compressed = assumeCompression(__compressed)
- return getEncoded(pp, compressed)
- }
-
- function pointMultiply (p, tweak, __compressed) {
- if (!isPoint(p)) throw new TypeError(THROW_BAD_POINT)
- if (!isOrderScalar(tweak)) throw new TypeError(THROW_BAD_TWEAK)
-
- const compressed = assumeCompression(__compressed, p)
- const pp = decodeFrom(p)
- const tt = fromBuffer(tweak)
- const qq = pp.mul(tt)
- if (qq.isInfinity()) return null
-
- return getEncoded(qq, compressed)
- }
-
- function privateAdd (d, tweak) {
- if (!isPrivate(d)) throw new TypeError(THROW_BAD_PRIVATE)
- if (!isOrderScalar(tweak)) throw new TypeError(THROW_BAD_TWEAK)
-
- const dd = fromBuffer(d)
- const tt = fromBuffer(tweak)
- const dt = toBuffer(dd.add(tt).umod(n))
- if (!isPrivate(dt)) return null
-
- return dt
- }
-
- function privateSub (d, tweak) {
- if (!isPrivate(d)) throw new TypeError(THROW_BAD_PRIVATE)
- if (!isOrderScalar(tweak)) throw new TypeError(THROW_BAD_TWEAK)
-
- const dd = fromBuffer(d)
- const tt = fromBuffer(tweak)
- const dt = toBuffer(dd.sub(tt).umod(n))
- if (!isPrivate(dt)) return null
-
- return dt
- }
-
- function sign (hash, x) {
- return __sign(hash, x)
- }
-
- function signWithEntropy (hash, x, addData) {
- return __sign(hash, x, addData)
- }
-
- function __sign (hash, x, addData) {
- if (!isScalar(hash)) throw new TypeError(THROW_BAD_HASH)
- if (!isPrivate(x)) throw new TypeError(THROW_BAD_PRIVATE)
- if (addData !== undefined && !isScalar(addData)) throw new TypeError(THROW_BAD_EXTRA_DATA)
-
- const d = fromBuffer(x)
- const e = fromBuffer(hash)
-
- let r, s
- const checkSig = function (k) {
- const kI = fromBuffer(k)
- const Q = G.mul(kI)
-
- if (Q.isInfinity()) return false
-
- r = Q.x.umod(n)
- if (r.isZero() === 0) return false
-
- s = kI
- .invm(n)
- .mul(e.add(d.mul(r)))
- .umod(n)
- if (s.isZero() === 0) return false
-
- return true
- }
-
- deterministicGenerateK(hash, x, checkSig, isPrivate, addData)
-
- // enforce low S values, see bip62: 'low s values in signatures'
- if (s.cmp(nDiv2) > 0) {
- s = n.sub(s)
- }
-
- const buffer = Buffer.allocUnsafe(64)
- toBuffer(r).copy(buffer, 0)
- toBuffer(s).copy(buffer, 32)
- return buffer
- }
-
- function verify (hash, q, signature, strict) {
- if (!isScalar(hash)) throw new TypeError(THROW_BAD_HASH)
- if (!isPoint(q)) throw new TypeError(THROW_BAD_POINT)
-
- // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (1, isSignature enforces '< n - 1')
- if (!isSignature(signature)) throw new TypeError(THROW_BAD_SIGNATURE)
-
- const Q = decodeFrom(q)
- const r = fromBuffer(signature.slice(0, 32))
- const s = fromBuffer(signature.slice(32, 64))
-
- if (strict && s.cmp(nDiv2) > 0) {
- return false
- }
-
- // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (2, enforces '> 0')
- if (r.gtn(0) <= 0 /* || r.compareTo(n) >= 0 */) return false
- if (s.gtn(0) <= 0 /* || s.compareTo(n) >= 0 */) return false
-
- // 1.4.2 H = Hash(M), already done by the user
- // 1.4.3 e = H
- const e = fromBuffer(hash)
-
- // Compute s^-1
- const sInv = s.invm(n)
-
- // 1.4.4 Compute u1 = es^−1 mod n
- // u2 = rs^−1 mod n
- const u1 = e.mul(sInv).umod(n)
- const u2 = r.mul(sInv).umod(n)
-
- // 1.4.5 Compute R = (xR, yR)
- // R = u1G + u2Q
- const R = G.mulAdd(u1, Q, u2)
-
- // 1.4.5 (cont.) Enforce R is not at infinity
- if (R.isInfinity()) return false
-
- // 1.4.6 Convert the field element R.x to an integer
- const xR = R.x
-
- // 1.4.7 Set v = xR mod n
- const v = xR.umod(n)
-
- // 1.4.8 If v = r, output "valid", and if v != r, output "invalid"
- return v.eq(r)
- }
-
- module.exports = {
- isPoint,
- isPointCompressed,
- isPrivate,
- pointAdd,
- pointAddScalar,
- pointCompress,
- pointFromScalar,
- pointMultiply,
- privateAdd,
- privateSub,
- sign,
- signWithEntropy,
- verify
- }
|