libnexa-ts
Version:
A pure and powerful Nexa SDK library.
247 lines (210 loc) • 7.1 kB
text/typescript
import type { IPrivateKey, IPublicKey, ISignature } from "../common/interfaces";
import type { EndianType } from "../common/types";
import BufferUtils from "../utils/buffer.utils";
import BN from "./bn.extension";
import DigitalSignature from "./digital-signature";
import Point from "./point";
import Hash from "./hash";
import ValidationUtils from "../utils/validation.utils";
import PublicKey from "../keys/publickey";
/**
* IMPORTANT: ECDSA only used for compact message signing.
* for transactions signing, use Schnorr.
*/
export default class ECDSA extends DigitalSignature {
k?: BN;
public override set(obj: Partial<ECDSA>): this {
this.k = obj.k || this.k;
return super.set(obj);
}
public override sigError(): boolean | string {
if (!BufferUtils.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) {
return 'hashbuf must be a 32 byte buffer';
}
let r = this.sig!.r;
let s = this.sig!.s;
if (!(r.gt(BN.Zero) && r.lt(Point.getN())) || !(s.gt(BN.Zero) && s.lt(Point.getN()))) {
return 'r and s not in range';
}
let e = BN.fromBuffer(this.hashbuf, this.endian ? { endian: this.endian } : undefined);
let n = Point.getN();
let sinv = s.invm(n);
let u1 = sinv.mul(e).umod(n);
let u2 = sinv.mul(r).umod(n);
let p = Point.getG().mulAdd(new BN(u1), this.pubkey.point, new BN(u2));
/* c8 ignore start */
if (p.ecPoint.isInfinity()) {
return 'p is infinity';
}
/* c8 ignore stop */
if (p.getX().umod(n).cmp(r) !== 0) {
return 'Invalid signature';
} else {
return false;
}
}
protected override _findSignature(d: BN, e: BN): Partial<ISignature> {
let N = Point.getN();
let G = Point.getG();
// try different values of k until r, s are valid
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().umod(N);
s = k!.invm(N).mul(e.add(d.mul(r))).umod(N);
} while (r.cmp(BN.Zero) <= 0 || s.cmp(BN.Zero) <= 0);
s = ECDSA.toLowS(new BN(s));
return { s: s, r: new BN(r) };
}
private static toLowS(s: BN): BN {
//enforce low s
//see BIP 62, "low S values in signatures"
if (s.gt(BN.fromBuffer(BufferUtils.hexToBuffer('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0')))) {
s = Point.getN().sub(s);
}
return s;
}
public calcI(): this {
for (let i = 0; i < 4; i++) {
this.sig!.i = i;
let Qprime;
try {
Qprime = this.toPublicKey();
/* c8 ignore next 3 */
} catch {
continue;
}
if (Qprime.point.eq(this.pubkey.point)) {
this.sig!.compressed = this.pubkey.compressed;
return this;
}
/* c8 ignore next 4 */
}
this.sig!.i = undefined;
throw new Error(`Unable to find valid recovery factor`);
}
public randomK(): this {
let N = Point.getN();
let k;
do {
k = BN.fromBuffer(BufferUtils.getRandomBuffer(32));
} while (!(k.lt(N) && k.gt(BN.Zero)));
this.k = k;
return this;
}
// https://tools.ietf.org/html/rfc6979#section-3.2
public deterministicK(badrs?: number): this {
// if r or s were invalid when this function was used in signing,
// we do not want to actually compute r, s here for efficiency, so,
// we can increment badrs. explained at end of RFC 6979 section 3.2
if (badrs == null) {
badrs = 0;
}
let v: Uint8Array = new Uint8Array(32);
v.fill(0x01);
let k: Uint8Array = new Uint8Array(32);
k.fill(0x00);
let x = this.privkey.bn.toByteArray({ size: 32 });
let hashbuf = this.endian === 'little' ? BufferUtils.reverse(this.hashbuf) : this.hashbuf
k = Hash.sha256hmac(BufferUtils.concat([v, Uint8Array.from([0x00]), x, hashbuf]), k);
v = Hash.sha256hmac(v, k);
k = Hash.sha256hmac(BufferUtils.concat([v, Uint8Array.from([0x01]), x, hashbuf]), k);
v = Hash.sha256hmac(v, k);
v = Hash.sha256hmac(v, k);
let T = BN.fromBuffer(v);
let N = Point.getN();
// also explained in 3.2, we must ensure T is in the proper range (0, N)
for (let i = 0; i < badrs || !(T.lt(N) && T.gt(BN.Zero)); i++) {
k = Hash.sha256hmac(BufferUtils.concat([v, Uint8Array.from([0x00])]), k);
v = Hash.sha256hmac(v, k);
v = Hash.sha256hmac(v, k);
T = BN.fromBuffer(v);
}
this.k = T;
return this;
}
public signRandomK(): this {
this.randomK();
return this.sign();
}
public toString(): string {
let obj: Record<string, string> = {};
if (this.hashbuf) {
obj.hashbuf = BufferUtils.bufferToHex(this.hashbuf);
}
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);
}
// Information about public key recovery:
// https://bitcointalk.org/index.php?topic=6430.0
// http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k
public override toPublicKey(): PublicKey {
let i = this.sig!.i!;
ValidationUtils.validateArgument(i === 0 || i === 1 || i === 2 || i === 3, 'i must be equal to 0, 1, 2, or 3');
let e = BN.fromBuffer(this.hashbuf);
let r = this.sig!.r;
let s = this.sig!.s;
// A set LSB signifies that the y-coordinate is odd
let isYOdd = i & 1;
// The more significant bit specifies whether we should use the
// first or second candidate key.
let isSecondKey = i >> 1;
let n = Point.getN();
let G = Point.getG();
// 1.1 Let x = r + jn
let x = isSecondKey ? r.add(n) : r;
let R = Point.ecPointFromX(!!isYOdd, x);
// 1.4 Check that nR is at infinity
let nR = R.mul(n);
/* c8 ignore start */
if (!nR.ecPoint.isInfinity()) {
throw new Error('nR is not a valid curve point');
}
/* c8 ignore stop */
// Compute -e from e
let eNeg = e.neg().umod(n);
// 1.6.1 Compute Q = r^-1 (sR - eG)
// Q = r^-1 (sR + -eG)
let rInv = r.invm(n);
//let Q = R.multiplyTwo(s, G, eNeg).mul(rInv);
let Q = R.mul(s).add(G.mul(new BN(eNeg))).mul(new BN(rInv));
let pubkey = PublicKey.fromPoint(Q, this.sig!.compressed);
return pubkey;
}
public static fromString(str: string): ECDSA {
let obj = JSON.parse(str);
return new ECDSA(obj);
}
public static sign(hashbuf: Uint8Array, privkey: IPrivateKey, endian?: EndianType): ISignature {
return new ECDSA({
hashbuf: hashbuf,
endian: endian,
privkey: privkey
}).sign().sig!;
}
public static verify(hashbuf: Uint8Array, sig: ISignature, pubkey: IPublicKey, endian?: EndianType): boolean {
return new ECDSA({
hashbuf: hashbuf,
endian: endian,
sig: sig,
pubkey: pubkey
}).verify().verified!;
}
}