UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

301 lines (300 loc) 10.2 kB
import { BN } from './bn.js'; export class Signature { r; s; i; compressed; isSchnorr; nhashtype; static SIGHASH_ALL = 0x01; static SIGHASH_NONE = 0x02; static SIGHASH_SINGLE = 0x03; static SIGHASH_FORKID = 0x40; static SIGHASH_LOTUS = 0x60; static SIGHASH_ANYONECANPAY = 0x80; constructor(r, s, isSchnorr) { if (r instanceof BN) { this.set({ r: r, s: s, isSchnorr: isSchnorr, }); } else if (r) { this.set(r); } } static create(r, s, isSchnorr) { return new Signature(r, s, isSchnorr); } set(obj) { this.r = obj.r || this.r || undefined; this.s = obj.s || this.s || undefined; this.i = typeof obj.i !== 'undefined' ? obj.i : this.i; this.compressed = typeof obj.compressed !== 'undefined' ? obj.compressed : this.compressed; this.isSchnorr = obj.isSchnorr; this.nhashtype = obj.nhashtype || this.nhashtype || undefined; return this; } static fromCompact(buf) { if (!Buffer.isBuffer(buf)) { throw new Error('Argument is expected to be a Buffer'); } const sig = new Signature(new BN(0), new BN(0)); let compressed = true; let i = buf.subarray(0, 1)[0] - 27 - 4; if (i < 0) { compressed = false; i = i + 4; } const b2 = buf.subarray(1, 33); const b3 = buf.subarray(33, 65); if (!(i === 0 || i === 1 || i === 2 || i === 3)) { throw new Error('i must be 0, 1, 2, or 3'); } if (b2.length !== 32) { throw new Error('r must be 32 bytes'); } if (b3.length !== 32) { throw new Error('s must be 32 bytes'); } sig.compressed = compressed; sig.i = i; sig.r = new BN(b2, 'le'); sig.s = new BN(b3, 'le'); return sig; } static fromDER(buf, strict = true) { if ((buf.length === 64 || buf.length === 65) && buf[0] !== 0x30) { const obj = Signature.parseSchnorrEncodedSig(buf); const sig = new Signature(new BN(0), new BN(0)); sig.r = obj.r; sig.s = obj.s; sig.isSchnorr = true; return sig; } if (buf.length === 64 && buf[0] === 0x30) { throw new Error('64 DER (ecdsa) signatures not allowed'); } const obj = Signature.parseDER(buf, strict); const sig = new Signature(new BN(0), new BN(0)); sig.r = obj.r; sig.s = obj.s; return sig; } static fromBuffer(buf, strict = true) { return Signature.fromDER(buf, strict); } static fromTxFormat(buf) { const nhashtype = buf.readUInt8(buf.length - 1); const derbuf = buf.subarray(0, buf.length - 1); const sig = Signature.fromDER(derbuf, false); sig.nhashtype = nhashtype; return sig; } static fromDataFormat(buf) { const derbuf = buf.subarray(0, buf.length); return Signature.fromDER(derbuf, false); } static fromString(str) { const buf = Buffer.from(str, 'hex'); return Signature.fromDER(buf); } static fromSchnorr(buf) { const parsed = Signature.parseSchnorrEncodedSig(buf); const sig = new Signature(parsed.r, parsed.s); sig.isSchnorr = true; if (parsed.nhashtype) { sig.nhashtype = parsed.nhashtype.readUInt8(0); } return sig; } static parseSchnorrEncodedSig(buf) { if (buf.length !== 64 && buf.length !== 65) { throw new Error(`Invalid Schnorr signature length: ${buf.length} bytes (expected 64 or 65)`); } const r = buf.subarray(0, 32); const s = buf.subarray(32, 64); let hashtype; if (buf.length === 65) { hashtype = buf.subarray(64, 65); } return { r: new BN(r, 'be'), s: new BN(s, 'be'), nhashtype: hashtype, }; } static parseDER(buf, strict = true) { if (!Buffer.isBuffer(buf)) { throw new Error('DER formatted signature should be a buffer'); } const header = buf[0]; if (header !== 0x30) { throw new Error('Header byte should be 0x30'); } let length = buf[1]; const buflength = buf.subarray(2).length; if (strict && length !== buflength) { throw new Error('Length byte should length of what follows'); } length = length < buflength ? length : buflength; const rheader = buf[2 + 0]; if (rheader !== 0x02) { throw new Error('Integer byte for r should be 0x02'); } const rlength = buf[2 + 1]; const rbuf = buf.subarray(2 + 2, 2 + 2 + rlength); const r = new BN(rbuf, 'be'); if (rlength !== rbuf.length) { throw new Error('Length of r incorrect'); } const sheader = buf[2 + 2 + rlength + 0]; if (sheader !== 0x02) { throw new Error('Integer byte for s should be 0x02'); } const slength = buf[2 + 2 + rlength + 1]; const sbuf = buf.subarray(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); const s = new BN(sbuf, 'be'); if (slength !== sbuf.length) { throw new Error('Length of s incorrect'); } const sumlength = 2 + 2 + rlength + 2 + slength; if (length !== sumlength - 2) { throw new Error('Length of signature incorrect'); } return { r, s }; } toCompact(i, compressed) { const recoveryId = typeof i === 'number' ? i : this.i; const isCompressed = typeof compressed === 'boolean' ? compressed : this.compressed; if (!(recoveryId === 0 || recoveryId === 1 || recoveryId === 2 || recoveryId === 3)) { throw new Error('i must be equal to 0, 1, 2, or 3'); } let val = recoveryId + 27 + 4; if (isCompressed === false) { val = val - 4; } const b1 = Buffer.from([val]); const b2 = this.r.toArrayLike(Buffer, 'le', 32); const b3 = this.s.toArrayLike(Buffer, 'le', 32); return Buffer.concat([b1, b2, b3]); } toDER(signingMethod = 'ecdsa') { if (signingMethod === 'schnorr') { return Buffer.concat([ this.r.toArrayLike(Buffer, 'be', 32), this.s.toArrayLike(Buffer, 'be', 32), ]); } const rnbuf = this.r.toArrayLike(Buffer, 'be'); const snbuf = this.s.toArrayLike(Buffer, 'be'); const rneg = (rnbuf[0] & 0x80) !== 0; const sneg = (snbuf[0] & 0x80) !== 0; const rbuf = rneg ? Buffer.concat([Buffer.from([0x00]), rnbuf]) : rnbuf; const sbuf = sneg ? Buffer.concat([Buffer.from([0x00]), snbuf]) : snbuf; const rlength = rbuf.length; const slength = sbuf.length; const length = 2 + rlength + 2 + slength; const rheader = 0x02; const sheader = 0x02; const header = 0x30; return Buffer.concat([ Buffer.from([header, length, rheader, rlength]), rbuf, Buffer.from([sheader, slength]), sbuf, ]); } toBuffer(signingMethod) { const method = signingMethod || (this.isSchnorr ? 'schnorr' : 'ecdsa'); return this.toDER(method); } toString() { const method = this.isSchnorr ? 'schnorr' : 'ecdsa'; const buf = this.toDER(method); return buf.toString('hex'); } toTxFormat(signingMethod) { const derbuf = this.toDER(signingMethod); const buf = Buffer.alloc(1); buf.writeUInt8(this.nhashtype || 0, 0); return Buffer.concat([derbuf, buf]); } static isDER(buf) { if (buf.length < 8 || buf.length > 72) { return false; } if (buf[0] !== 0x30) { return false; } if (buf[1] !== buf.length - 2) { return false; } if (buf[2] !== 0x02) { return false; } const lenR = buf[3]; if (lenR === 0) { return false; } if (buf[4] & 0x80) { return false; } if (lenR > buf.length - 7) { return false; } if (lenR > 1 && buf[4] === 0x00 && !(buf[5] & 0x80)) { return false; } const startS = lenR + 4; if (buf[startS] !== 0x02) { return false; } const lenS = buf[startS + 1]; if (lenS === 0) { return false; } if (buf[startS + 2] & 0x80) { return false; } if (startS + lenS + 2 !== buf.length) { return false; } if (lenS > 1 && buf[startS + 2] === 0x00 && !(buf[startS + 3] & 0x80)) { return false; } return true; } hasLowS() { const lowSThreshold = new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 16); if (this.s.lt(new BN(1)) || this.s.gt(lowSThreshold)) { return false; } return true; } hasDefinedHashtype() { if (typeof this.nhashtype !== 'number') { return false; } if (this.nhashtype & Signature.SIGHASH_LOTUS) { const baseMask = ~(Signature.SIGHASH_LOTUS | Signature.SIGHASH_ANYONECANPAY) >>> 0; const baseType = this.nhashtype & baseMask; return (baseType >= Signature.SIGHASH_ALL && baseType <= Signature.SIGHASH_SINGLE); } const mask = ~(Signature.SIGHASH_FORKID | Signature.SIGHASH_ANYONECANPAY) >>> 0; const temp = this.nhashtype & mask; if (temp < Signature.SIGHASH_ALL || temp > Signature.SIGHASH_SINGLE) { return false; } return true; } static isTxDER(buf) { return Signature.isDER(buf.subarray(0, buf.length - 1)); } }