UNPKG

libnexa-ts

Version:

A pure and powerful Nexa SDK library.

215 lines (177 loc) 6.85 kB
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 }); }; }