UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

248 lines (247 loc) 7.54 kB
import { BN } from './bn.js'; import { Point } from './point.js'; import { Signature } from './signature.js'; import { Hash } from './hash.js'; import { Random } from './random.js'; import { PublicKey } from '../publickey.js'; export class ECDSA { hashbuf; endian; privkey; pubkey; sig; k; verified; constructor(obj) { if (obj) { this.set(obj); } } static create(obj) { return new ECDSA(obj); } set(obj) { this.hashbuf = obj.hashbuf || this.hashbuf; this.endian = obj.endian || this.endian; this.privkey = obj.privkey || this.privkey; this.pubkey = obj.pubkey || (this.privkey ? this.privkey.toPublicKey() : this.pubkey); this.sig = obj.sig || this.sig; this.k = obj.k || this.k; this.verified = obj.verified || this.verified; return this; } privkey2pubkey() { this.pubkey = this.privkey.toPublicKey(); return this; } calci() { for (let i = 0; i < 4; i++) { this.sig.i = i; let Qprime; try { Qprime = this.toPublicKey(); } catch (e) { console.error(e); continue; } if (Qprime.point.eq(this.pubkey.point)) { this.sig.compressed = this.pubkey.compressed; return this; } } this.sig.i = undefined; throw new Error('Unable to find valid recovery factor'); } static fromString(str) { const obj = JSON.parse(str); return new ECDSA(obj); } randomK() { const N = Point.getN(); let k; do { k = new BN(Random.getPseudoRandomBuffer(32), 'be'); } while (!(k.lt(N) && k.gt(new BN(0)))); this.k = k; return this; } deterministicK(badrs = 0) { let v = Buffer.alloc(32); v.fill(0x01); let k = Buffer.alloc(32); k.fill(0x00); const x = this.privkey.toBuffer(); const hashbuf = this.endian === 'little' ? this.reverseBuffer(this.hashbuf) : this.hashbuf; k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00]), x, hashbuf]), k); v = Hash.sha256hmac(v, k); k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x01]), x, hashbuf]), k); v = Hash.sha256hmac(v, k); v = Hash.sha256hmac(v, k); let T = new BN(v, 'be'); const N = Point.getN(); for (let i = 0; i < badrs || !(T.lt(N) && T.gt(new BN(0))); i++) { k = Hash.sha256hmac(Buffer.concat([v, Buffer.from([0x00])]), k); v = Hash.sha256hmac(v, k); v = Hash.sha256hmac(v, k); T = new BN(v, 'be'); } this.k = T; return this; } toPublicKey() { const i = this.sig.i; if (!(i === 0 || i === 1 || i === 2 || i === 3)) { throw new Error('i must be equal to 0, 1, 2, or 3'); } const e = new BN(this.hashbuf, 'be'); const r = this.sig.r; const s = this.sig.s; const isYOdd = (i & 1) !== 0; const isSecondKey = i >> 1 !== 0; const n = Point.getN(); const G = Point.getG(); const x = isSecondKey ? r.add(n) : r; const R = Point.fromX(isYOdd, x); const nR = R.mul(n); if (!nR.isInfinity()) { throw new Error('nR is not a valid curve point'); } const eNeg = e.neg().mod(n); const rInv = r.invm(n); const Q = R.mul(s).add(G.mul(eNeg)).mul(rInv); const pubkey = PublicKey.fromPoint(Q, this.sig.compressed); return pubkey; } sigError() { if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { return 'hashbuf must be a 32 byte buffer'; } const r = this.sig.r; const s = this.sig.s; const N = Point.getN(); if (!(r.gt(new BN(0)) && r.lt(N)) || !(s.gt(new BN(0)) && s.lt(N))) { return 'r and s not in range'; } const e = new BN(this.hashbuf, this.endian === 'little' ? 'le' : 'be'); const n = Point.getN(); const sinv = s.invm(n); const u1 = sinv.mul(e).mod(n); const u2 = sinv.mul(r).mod(n); const p = Point.getG().mulAdd(u1, this.pubkey.point, u2); if (p.isInfinity()) { return 'p is infinity'; } if (p.getX().mod(n).cmp(r) !== 0) { return 'Invalid signature'; } else { return false; } } static toLowS(s) { const lowSThreshold = new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 16); if (s.gt(lowSThreshold)) { s = Point.getN().sub(s); } return s; } _findSignature(d, e) { const N = Point.getN(); const G = Point.getG(); let badrs = 0; let k, Q, r, s; do { if (!this.k || badrs > 0) { this.deterministicK(badrs); } badrs++; k = this.k; Q = G.mul(k); r = Q.getX().mod(N); s = k .invm(N) .mul(e.add(d.mul(r))) .mod(N); } while (r.cmp(new BN(0)) <= 0 || s.cmp(new BN(0)) <= 0); s = ECDSA.toLowS(s); return { s, r, compressed: this.pubkey.compressed }; } sign() { const hashbuf = this.hashbuf; const privkey = this.privkey; const d = privkey.bn; if (!hashbuf || !privkey || !d) { throw new Error('invalid parameters'); } if (!Buffer.isBuffer(hashbuf) || hashbuf.length !== 32) { throw new Error('hashbuf must be a 32 byte buffer'); } const e = new BN(hashbuf, this.endian === 'little' ? 'le' : 'be'); const obj = this._findSignature(d, e); obj.compressed = this.pubkey.compressed; this.sig = new Signature(obj); return this; } signRandomK() { this.randomK(); return this.sign(); } toString() { const obj = {}; if (this.hashbuf) { obj.hashbuf = this.hashbuf.toString('hex'); } if (this.privkey) { obj.privkey = this.privkey.toString(); } if (this.pubkey) { obj.pubkey = this.pubkey.toString(); } if (this.sig) { obj.sig = this.sig.toString(); } if (this.k) { obj.k = this.k.toString(); } return JSON.stringify(obj); } verify() { if (!this.sigError()) { this.verified = true; } else { this.verified = false; } return this; } static sign(hashbuf, privkey, endian) { return ECDSA.create() .set({ hashbuf: hashbuf, endian: endian, privkey: privkey, }) .sign().sig; } static verify(hashbuf, sig, pubkey, endian) { return ECDSA.create() .set({ hashbuf: hashbuf, endian: endian, sig: sig, pubkey: pubkey, }) .verify().verified; } reverseBuffer(buf) { const buf2 = Buffer.alloc(buf.length); for (let i = 0; i < buf.length; i++) { buf2[i] = buf[buf.length - 1 - i]; } return buf2; } }