libnexa-ts
Version:
A pure and powerful Nexa SDK library.
215 lines (177 loc) • 6.85 kB
text/typescript
import type { ISignature } from "../common/interfaces";
import BufferUtils from "../utils/buffer.utils";
import ValidationUtils from "../utils/validation.utils";
import BN from "./bn.extension";
export default class Signature implements ISignature {
public r: BN;
public s: BN;
public i?: number;
public compressed?: boolean;
constructor(params: Partial<ISignature>) {
this.r = params.r!;
this.s = params.s!;
this.i = params.i;
this.compressed = params.compressed;
}
public toBuffer(isSchnorr = true): Uint8Array {
if (isSchnorr) {
// Schnorr signatures use a 64 byte r,s format.
return BufferUtils.concat([this.r.toByteArray({size: 32}), this.s.toByteArray({size: 32})]);
}
let rnbuf = this.r.toByteArray();
let snbuf = this.s.toByteArray();
let rneg = rnbuf[0] & 0x80 ? true : false;
/* c8 ignore next */
let sneg = snbuf[0] & 0x80 ? true : false;
let rbuf = rneg ? BufferUtils.concat([Uint8Array.from([0x00]), rnbuf]) : rnbuf;
/* c8 ignore next */
let sbuf = sneg ? BufferUtils.concat([Uint8Array.from([0x00]), snbuf]) : snbuf;
let rlength = rbuf.length;
let slength = sbuf.length;
let length = 2 + rlength + 2 + slength;
let rheader = 0x02;
let sheader = 0x02;
let header = 0x30;
let der = BufferUtils.concat([Uint8Array.from([header, length, rheader, rlength]), rbuf, Uint8Array.from([sheader, slength]), sbuf]);
return der;
}
public toTxFormat(sighashBuf?: Uint8Array): Uint8Array {
let sigbuf = this.toBuffer();
if (BufferUtils.isBuffer(sighashBuf)) {
return BufferUtils.concat([sigbuf, sighashBuf]);
}
return sigbuf;
}
public toString(isSchnorr = true): string {
return BufferUtils.bufferToHex(this.toBuffer(isSchnorr));
}
/**
* Schnorr signatures are 64 bytes: r [len] 32 || s [len] 32.
*
* There can be a few more bytes that is the sighashtype. It needs to be trimmed before calling this.
*/
public static fromBuffer(buf: Uint8Array, strict?: boolean): Signature {
if (buf.length === 64) {
let params = this.parseSchnorrEncodedSig(buf);
return new Signature(params);
}
let obj = Signature.parseDER(buf, strict);
return new Signature({ r: obj.r as BN, s: obj.s as BN });
}
/**
* The format used in a tx.
* schnorr is 64 bytes, the rest are sighashtype bytes
*
* @param buf
*/
public static fromTxFormat(buf: Uint8Array): Signature {
let sigbuf = buf.subarray(0, 64);
return Signature.fromBuffer(sigbuf);
}
/**
* This assumes the str is a raw signature and does not have sighashtype.
* Use {@link Signature.fromTxString} when decoding a tx
*
* @param str the signature hex string
* @see fromTxString
*/
public static fromString(str: string): Signature {
let buf = BufferUtils.hexToBuffer(str);
return Signature.fromBuffer(buf);
}
/**
* This assumes the str might have sighashtype bytes and will trim it if needed.
* Use this when decoding a tx signature string
*
* @param str the tx signature hex string
*/
public static fromTxString(str: string): Signature {
return Signature.fromTxFormat(BufferUtils.hexToBuffer(str))
}
private static parseSchnorrEncodedSig(buf: Uint8Array): Partial<ISignature> {
let r = buf.subarray(0,32);
let s = buf.subarray(32, 64);
return {
r: BN.fromBuffer(r),
s: BN.fromBuffer(s),
};
}
/**
* For ECDSA. In order to mimic the non-strict DER encoding of OpenSSL, set strict = false.
*/
public static parseDER(buf: Uint8Array, strict?: boolean): Record<string, unknown> {
ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), 'DER formatted signature should be a buffer');
if (strict == null) {
strict = true;
}
let header = buf[0];
ValidationUtils.validateArgument(header === 0x30, 'Header byte should be 0x30');
let length = buf[1];
let buflength = buf.subarray(2).length;
ValidationUtils.validateArgument(!strict || length === buflength, 'Length byte should length of what follows');
length = length < buflength ? length : buflength;
let rheader = buf[2 + 0];
ValidationUtils.validateArgument(rheader === 0x02, 'Integer byte for r should be 0x02');
let rlength = buf[2 + 1];
let rbuf = buf.subarray(2 + 2, 2 + 2 + rlength);
let r = BN.fromBuffer(rbuf);
let rneg = buf[2 + 1 + 1] === 0x00 ? true : false;
ValidationUtils.validateArgument(rlength === rbuf.length, 'Length of r incorrect');
let sheader = buf[2 + 2 + rlength + 0];
ValidationUtils.validateArgument(sheader === 0x02, 'Integer byte for s should be 0x02');
let slength = buf[2 + 2 + rlength + 1];
let sbuf = buf.subarray(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength);
let s = BN.fromBuffer(sbuf);
/* c8 ignore next */
let sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false;
ValidationUtils.validateArgument(slength === sbuf.length, 'Length of s incorrect');
let sumlength = 2 + 2 + rlength + 2 + slength;
ValidationUtils.validateArgument(length === sumlength - 2, 'Length of signature incorrect');
let obj = {
header: header,
length: length,
rheader: rheader,
rlength: rlength,
rneg: rneg,
rbuf: rbuf,
r: r,
sheader: sheader,
slength: slength,
sneg: sneg,
sbuf: sbuf,
s: s
};
return obj;
}
/**
* ECDSA format. used for sign messages
*/
public toCompact(i?: number, compressed?: boolean): Uint8Array {
i = typeof i === 'number' ? i : this.i;
compressed = typeof compressed === 'boolean' ? compressed : this.compressed;
ValidationUtils.validateArgument(i === 0 || i === 1 || i === 2 || i === 3, 'i must be equal to 0, 1, 2, or 3');
let val = i! + 27 + 4;
if (compressed === false) {
val = val - 4;
}
let b1 = Uint8Array.from([val]);
let b2 = this.r.toByteArray({ size: 32 });
let b3 = this.s.toByteArray({ size: 32 });
return BufferUtils.concat([b1, b2, b3]);
}
public static fromCompact(buf: Uint8Array): Signature {
ValidationUtils.validateArgument(BufferUtils.isBuffer(buf), 'Argument is expected to be a Buffer');
let compressed = true;
let i = buf.subarray(0, 1)[0] - 27 - 4;
if (i < 0) {
compressed = false;
i = i + 4;
}
let b2 = buf.subarray(1, 33);
let b3 = buf.subarray(33, 65);
ValidationUtils.validateArgument(i === 0 || i === 1 || i === 2 || i === 3, 'i must be 0, 1, 2, or 3');
ValidationUtils.validateArgument(b2.length === 32, 'r must be 32 bytes');
ValidationUtils.validateArgument(b3.length === 32, 's must be 32 bytes');
return new Signature({ r: BN.fromBuffer(b2), s: BN.fromBuffer(b3), i: i, compressed: compressed });
};
}