libnexa-ts
Version:
A pure and powerful Nexa SDK library.
166 lines (131 loc) • 5.51 kB
text/typescript
import type { IPrivateKey, IPublicKey, ISignature } from "../common/interfaces";
import type { EndianType } from "../common/types";
import BufferUtils from "../utils/buffer.utils";
import ValidationUtils from "../utils/validation.utils";
import BN from "./bn.extension";
import DigitalSignature from "./digital-signature";
import Point from "./point";
import Hash from "./hash";
export default class Schnorr extends DigitalSignature {
public override sigError(): boolean | string {
if (!BufferUtils.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) {
return 'hashbuf must be a 32 byte buffer';
}
let sigLength = Schnorr.getProperSizeBuffer(this.sig!.r).length + Schnorr.getProperSizeBuffer(this.sig!.s).length;
if(!(sigLength === 64 || sigLength === 65)) {
return 'signature must be a 64 byte or 65 byte array';
}
let hashbuf = this.endian === 'little' ? BufferUtils.reverse(this.hashbuf) : this.hashbuf
let P = this.pubkey.point;
let G = Point.getG();
if(P.ecPoint.isInfinity()) return true; /* c8 ignore next */
let r = this.sig!.r;
let s = this.sig!.s;
let p = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 'hex');
let n = Point.getN();
if(r.gte(p) || s.gte(n)) {
// ("Failed >= condition")
return true;
}
let Br = Schnorr.getProperSizeBuffer(this.sig!.r);
let Bp = Point.pointToCompressed(P);
let hash = Hash.sha256(BufferUtils.concat([Br, Bp, hashbuf]));
let e = BN.fromBuffer(hash, { endian: 'big' }).umod(n);
let sG = G.mul(s);
let eP = P.mul(n.sub(e));
let R = sG.add(eP);
if(R.ecPoint.isInfinity() || !R.hasSquare() || !R.getX().eq(r)) {
return true;
}
return false;
}
/**
* RFC6979 deterministic nonce generation used from https://reviews.bitcoinabc.org/D2501
*
* @param privkeybuf
* @param msgbuf
* @return BN nonce
*/
public nonceFunctionRFC6979(privkeybuf: Uint8Array, msgbuf: Uint8Array): BN {
let V: Uint8Array = BufferUtils.hexToBuffer("0101010101010101010101010101010101010101010101010101010101010101");
let K: Uint8Array = BufferUtils.hexToBuffer("0000000000000000000000000000000000000000000000000000000000000000");
let blob = BufferUtils.concat([privkeybuf, msgbuf, new Uint8Array(0), Uint8Array.from("Schnorr+SHA256 ", c => c.charCodeAt(0))]);
K = Hash.sha256hmac(BufferUtils.concat([V, BufferUtils.hexToBuffer('00'), blob]), K);
V = Hash.sha256hmac(V,K);
K = Hash.sha256hmac(BufferUtils.concat([V, BufferUtils.hexToBuffer('01'), blob]), K);
V = Hash.sha256hmac(V,K);
let k = new BN(0);
let T;
while (true) {
V = Hash.sha256hmac(V,K);
T = BN.fromBuffer(V);
k = T;
ValidationUtils.validateState(V.length >= 32, "V length should be >= 32");
if (k.gt(new BN(0)) && k.lt(Point.getN())) {
break;
}/* c8 ignore start */
K = Hash.sha256hmac(BufferUtils.concat([V, BufferUtils.hexToBuffer('00')]), K);
V = Hash.sha256hmac(V, K);
}/* c8 ignore stop */
return k;
}
/**
* @remarks
* Important references for schnorr implementation {@link https://spec.nexa.org/forks/2019-05-15-schnorr/}
*
* @param d the private key
* @param e the message to be signed
*/
protected override _findSignature(d: BN, e: BN): Partial<ISignature> {
let n = Point.getN();
let G = Point.getG();
ValidationUtils.validateState(!d.lte(new BN(0)), 'privkey out of field of curve');
ValidationUtils.validateState(!d.gte(n), 'privkey out of field of curve');
let k = this.nonceFunctionRFC6979(d.toByteArray({ size: 32 }), e.toByteArray({ size: 32 }));
let P = G.mul(d);
let R = G.mul(k);
// Find deterministic k
if(R.hasSquare()) {
k = k;
} else {
k = n.sub(k);
}
let r = R.getX();
let e0 = BN.fromBuffer(Hash.sha256(BufferUtils.concat([Schnorr.getProperSizeBuffer(r), Point.pointToCompressed(P), e.toByteArray({ size: 32 })])));
let s = ((e0.mul(d)).add(k)).mod(n);
return { r: r, s: s };
}
/**
* Function written to ensure s or r parts of signature is at least 32 bytes, when converting
* from a BN to type Uint8Array.
* The BN type naturally cuts off leading zeros, e.g.
* <BN: 4f92d8094f710bc11b93935ac157730dda26c5c2a856650dbd8ebcd730d2d4> 31 bytes
* Buffer <00 4f 92 d8 09 4f 71 0b c1 1b 93 93 5a c1 57 73 0d da 26 c5 c2 a8 56 65 0d bd 8e bc d7 30 d2 d4> 32 bytes
* Both types are equal, however Schnorr signatures must be a minimum of 64 bytes.
* In a previous implementation of this schnorr module, was resulting in 63 byte signatures.
* (Although it would have been verified, it's proper to ensure the min requirement)
*
* @param bn the r or s signature part
*/
private static getProperSizeBuffer(bn: BN): Uint8Array {
if (bn.toByteArray().length < 32) {
return bn.toByteArray({size: 32});
}
return bn.toByteArray();
}
public static sign(hashbuf: Uint8Array, privkey: IPrivateKey, endian: EndianType): ISignature {
return new Schnorr({
hashbuf: hashbuf,
endian: endian,
privkey: privkey
}).sign().sig!;
}
public static verify(hashbuf: Uint8Array, sig: ISignature, pubkey: IPublicKey, endian: EndianType): boolean {
return new Schnorr({
hashbuf: hashbuf,
endian: endian,
sig: sig,
pubkey: pubkey
}).verify().verified!;
}
}