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
JavaScript
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;
}
}