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