UNPKG

lotus-sdk

Version:

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

186 lines (185 loc) 6.08 kB
import { BN } from './bn.js'; import { Point } from './point.js'; import { Signature } from './signature.js'; import { Hash } from './hash.js'; export class Schnorr { hashbuf; endian; privkey; pubkey; sig; verified; constructor(obj) { if (obj) { this.set(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.verified = obj.verified || this.verified; return this; } privkey2pubkey() { this.pubkey = this.privkey.toPublicKey(); return this; } toPublicKey() { return this.privkey.toPublicKey(); } 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, 'be'); const obj = this._findSignature(d, e); obj.compressed = this.pubkey.compressed; obj.isSchnorr = true; this.sig = new Signature(obj); return this; } _findSignature(d, e) { const n = Point.getN(); const G = Point.getG(); if (d.lte(new BN(0))) { throw new Error('privkey out of field of curve'); } if (d.gte(n)) { throw new Error('privkey out of field of curve'); } let k = this.nonceFunctionRFC6979(d.toArrayLike(Buffer, 'be', 32), e.toArrayLike(Buffer, 'be', 32)); const P = G.mul(d); const R = G.mul(k); if (R.hasSquare()) { } else { k = n.sub(k); } const r = R.getX(); const rBuffer = this.getrBuffer(r); const e0 = new BN(Hash.sha256(Buffer.concat([ rBuffer, Point.pointToCompressed(P), e.toArrayLike(Buffer, 'be', 32), ])), 'be'); const s = e0.mul(d).add(k).mod(n); return { r, s, compressed: this.pubkey.compressed, isSchnorr: true }; } getrBuffer(r) { const rNaturalLength = r.toArrayLike(Buffer, 'be').length; if (rNaturalLength < 32) { return r.toArrayLike(Buffer, 'be', 32); } return r.toArrayLike(Buffer, 'be'); } getsBuffer(s) { const sNaturalLength = s.toArrayLike(Buffer, 'be').length; if (sNaturalLength < 32) { return s.toArrayLike(Buffer, 'be', 32); } return s.toArrayLike(Buffer, 'be'); } sigError() { if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { return true; } const sigLength = this.getrBuffer(this.sig.r).length + this.getsBuffer(this.sig.s).length; if (!(sigLength === 64 || sigLength === 65)) { return true; } const hashbuf = this.endian === 'little' ? this.reverseBuffer(this.hashbuf) : this.hashbuf; const P = this.pubkey.point; const G = Point.getG(); if (P.isInfinity()) { return true; } const r = this.sig.r; const s = this.sig.s; const p = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16); const n = Point.getN(); if (r.gte(p) || s.gte(n)) { return true; } const Br = this.getrBuffer(this.sig.r); const Bp = Point.pointToCompressed(P); const hash = Hash.sha256(Buffer.concat([Br, Bp, hashbuf])); const e = new BN(hash, 'be').mod(n); const sG = G.mul(s); const eP = P.mul(n.sub(e)); const R = sG.add(eP); if (R.isInfinity() || !R.hasSquare() || !R.getX().eq(r)) { return true; } return false; } verify() { this.verified = !!!this.sigError(); return this; } nonceFunctionRFC6979(privkey, msgbuf) { let V = Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'); let K = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); const blob = Buffer.concat([ privkey, msgbuf, Buffer.from('', 'ascii'), Buffer.from('Schnorr+SHA256 ', 'ascii'), ]); K = Hash.sha256hmac(Buffer.concat([V, Buffer.from('00', 'hex'), blob]), K); V = Hash.sha256hmac(V, K); K = Hash.sha256hmac(Buffer.concat([V, Buffer.from('01', 'hex'), blob]), K); V = Hash.sha256hmac(V, K); let k = new BN(0); let T; while (true) { V = Hash.sha256hmac(V, K); T = new BN(V, 'be'); k = T; if (V.length < 32) { throw new Error('V length should be >= 32'); } if (k.gt(new BN(0)) && k.lt(Point.getN())) { break; } K = Hash.sha256hmac(Buffer.concat([V, Buffer.from('00', 'hex')]), K); V = Hash.sha256hmac(V, K); } return k; } static sign(hashbuf, privkey, endian) { return new Schnorr() .set({ hashbuf: hashbuf, endian: endian, privkey: privkey, }) .sign().sig; } static verify(hashbuf, sig, pubkey, endian) { return new Schnorr() .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; } }