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
JavaScript
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));
}
}