UNPKG

bitcore-lib-cash

Version:

A pure and powerful JavaScript Bitcoin Cash library.

441 lines (362 loc) 12.7 kB
'use strict'; var BN = require('./bn'); var _ = require('lodash'); var $ = require('../util/preconditions'); var BufferUtil = require('../util/buffer'); var JSUtil = require('../util/js'); var Signature = function Signature(r, s, isSchnorr) { if (!(this instanceof Signature)) { return new Signature(r, s, isSchnorr); } if (r instanceof BN) { this.set({ r: r, s: s, isSchnorr: isSchnorr, }); } else if (r) { var obj = r; this.set(obj); } }; /* jshint maxcomplexity: 7 */ Signature.prototype.set = function(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; //public key recovery parameter in range [0, 3] this.compressed = typeof obj.compressed !== 'undefined' ? obj.compressed : this.compressed; // whether the recovered pubkey is compressed this.isSchnorr = obj.isSchnorr || this.isSchnorr; this.nhashtype = obj.nhashtype || this.nhashtype || undefined; return this; }; Signature.fromCompact = function(buf) { $.checkArgument(BufferUtil.isBuffer(buf), 'Argument is expected to be a Buffer'); var sig = new Signature(); var compressed = true; var i = buf.slice(0, 1)[0] - 27 - 4; if (i < 0) { compressed = false; i = i + 4; } var b2 = buf.slice(1, 33); var b3 = buf.slice(33, 65); $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be 0, 1, 2, or 3')); $.checkArgument(b2.length === 32, new Error('r must be 32 bytes')); $.checkArgument(b3.length === 32, new Error('s must be 32 bytes')); sig.compressed = compressed; sig.i = i; sig.r = BN.fromBuffer(b2); sig.s = BN.fromBuffer(b3); return sig; }; Signature.fromDER = Signature.fromBuffer = function(buf, strict) { // Schnorr signatures are 64 bytes: r [len] 32 || s [len] 32 // There can be a 65th byte that is the nhashtype. It needs to be trimmed before calling this. if (buf.length === 64) { let obj = Signature.parseSchnorrEncodedSig(buf); let sig = new Signature(); sig.r = obj.r; sig.s = obj.s; sig.isSchnorr = true; return sig; } var obj = Signature.parseDER(buf, strict); var sig = new Signature(); sig.r = obj.r; sig.s = obj.s; return sig; }; // The format used in a tx Signature.fromTxFormat = function(buf) { var nhashtype = buf.readUInt8(buf.length - 1); var derbuf = buf.slice(0, buf.length - 1); var sig = new Signature.fromDER(derbuf, false); sig.nhashtype = nhashtype; return sig; }; // The format used in a tx Signature.fromDataFormat = function(buf) { var derbuf = buf.slice(0, buf.length); var sig = new Signature.fromDER(derbuf, false); return sig; }; // This assumes the str is a raw signature DER and does not have nhashtype // Use Signature.fromTxString when decoding a tx Signature.fromString = function(str) { var buf = Buffer.from(str, 'hex'); return Signature.fromDER(buf); }; // Use this when decoding a tx signature string Signature.fromTxString = function(str, encoding = 'hex') { return Signature.fromTxFormat(Buffer.from(str, encoding)) } Signature.parseSchnorrEncodedSig = function(buf) { let r = buf.slice(0,32); let s = buf.slice(32, 64); let hashtype; if (buf.length === 65) { hashtype = buf.slice(64,65); this.nhashtype = hashtype; } var obj = { r: BN.fromBuffer(r), s: BN.fromBuffer(s), nhashtype: hashtype }; return obj; }; /** * In order to mimic the non-strict DER encoding of OpenSSL, set strict = false. */ Signature.parseDER = function(buf, strict) { $.checkArgument(BufferUtil.isBuffer(buf), new Error('DER formatted signature should be a buffer')); if (_.isUndefined(strict)) { strict = true; } var header = buf[0]; $.checkArgument(header === 0x30, new Error('Header byte should be 0x30')); var length = buf[1]; var buflength = buf.slice(2).length; $.checkArgument(!strict || length === buflength, new Error('Length byte should length of what follows')); length = length < buflength ? length : buflength; var rheader = buf[2 + 0]; $.checkArgument(rheader === 0x02, new Error('Integer byte for r should be 0x02')); var rlength = buf[2 + 1]; var rbuf = buf.slice(2 + 2, 2 + 2 + rlength); var r = BN.fromBuffer(rbuf); var rneg = buf[2 + 1 + 1] === 0x00 ? true : false; $.checkArgument(rlength === rbuf.length, new Error('Length of r incorrect')); var sheader = buf[2 + 2 + rlength + 0]; $.checkArgument(sheader === 0x02, new Error('Integer byte for s should be 0x02')); var slength = buf[2 + 2 + rlength + 1]; var sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); var s = BN.fromBuffer(sbuf); var sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false; $.checkArgument(slength === sbuf.length, new Error('Length of s incorrect')); var sumlength = 2 + 2 + rlength + 2 + slength; $.checkArgument(length === sumlength - 2, new Error('Length of signature incorrect')); var 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; }; Signature.prototype.toCompact = function(i, compressed) { i = typeof i === 'number' ? i : this.i; compressed = typeof compressed === 'boolean' ? compressed : this.compressed; if (!(i === 0 || i === 1 || i === 2 || i === 3)) { throw new Error('i must be equal to 0, 1, 2, or 3'); } var val = i + 27 + 4; if (compressed === false) { val = val - 4; } var b1 = Buffer.from([val]); var b2 = this.r.toBuffer({ size: 32 }); var b3 = this.s.toBuffer({ size: 32 }); return Buffer.concat([b1, b2, b3]); }; Signature.prototype.toBuffer = Signature.prototype.toDER = function() { // Schnorr signatures use a 64 byte r,s format, where as ECDSA takes the form decribed // below, above the isDER function signature. if(this.isSchnorr) { return Buffer.concat([this.r.toBuffer({size: 32}), this.s.toBuffer({size: 32})]); } var rnbuf = this.r.toBuffer(); var snbuf = this.s.toBuffer(); var rneg = rnbuf[0] & 0x80 ? true : false; var sneg = snbuf[0] & 0x80 ? true : false; var rbuf = rneg ? Buffer.concat([Buffer.from([0x00]), rnbuf]) : rnbuf; var sbuf = sneg ? Buffer.concat([Buffer.from([0x00]), snbuf]) : snbuf; var rlength = rbuf.length; var slength = sbuf.length; var length = 2 + rlength + 2 + slength; var rheader = 0x02; var sheader = 0x02; var header = 0x30; var der = Buffer.concat([Buffer.from([header, length, rheader, rlength]), rbuf, Buffer.from([sheader, slength]), sbuf]); return der; }; Signature.prototype.toString = function() { var buf = this.toDER(); return buf.toString('hex'); }; Signature.isTxDER = function(buf) { return Signature.isDER(buf.slice(0, buf.length-1)); } /** * This function is translated from bitcoind's IsDERSignature and is used in * the script interpreter. This "DER" format actually includes an extra byte, * the nhashtype, at the end. It is really the tx format, not DER format. * * A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] * Where R and S are not negative (their first byte has its highest bit not set), and not * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows, * in which case a single 0 byte is necessary and even required). * * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623 */ Signature.isDER = function(buf) { // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] // * total-length: 1-byte length descriptor of everything that follows, // excluding the sighash byte. // * R-length: 1-byte length descriptor of the R value that follows. // * R: arbitrary-length big-endian encoded R value. It must use the // shortest possible encoding for a positive integers (which means no null // bytes at the start, except a single one when the next byte has its // highest bit set). // * S-length: 1-byte length descriptor of the S value that follows. // * S: arbitrary-length big-endian encoded S value. The same rules apply. // Minimum and maximum size constraints. if (buf.length < 8 || buf.length > 72) { return false; } // // Check that the signature is a compound structure of proper size. // // A signature is of type 0x30 (compound). if (buf[0] != 0x30) { return false; } // Make sure the length covers the entire signature. // Remove: // * 1 byte for the coupound type. // * 1 byte for the length of the signature. if (buf[1] != buf.length - 2) { return false; } // // Check that R is an positive integer of sensible size. // // Check whether the R element is an integer. if (buf[2] != 0x02) { return false; } // Extract the length of the R element. var lenR = buf[3]; // Zero-length integers are not allowed for R. if (lenR == 0) { return false; } // Negative numbers are not allowed for R. if (buf[4] & 0x80) { return false; } // Make sure the length of the R element is consistent with the signature // size. // Remove: // * 1 byte for the coumpound type. // * 1 byte for the length of the signature. // * 2 bytes for the integer type of R and S. // * 2 bytes for the size of R and S. // * 1 byte for S itself. if (lenR > (buf.length - 7)) { return false; } // Null bytes at the start of R are not allowed, unless R would otherwise be // interpreted as a negative number. // // /!\ This check can only be performed after we checked that lenR is // consistent with the size of the signature or we risk to access out of // bound elements. if (lenR > 1 && (buf[4] == 0x00) && !(buf[5] & 0x80)) { return false; } // // Check that S is an positive integer of sensible size. // // S's definition starts after R's definition: // * 1 byte for the coumpound type. // * 1 byte for the length of the signature. // * 1 byte for the size of R. // * lenR bytes for R itself. // * 1 byte to get to S. var startS = lenR + 4; // Check whether the S element is an integer. if (buf[startS] != 0x02) { return false; } // Extract the length of the S element. var lenS = buf[startS + 1]; // Zero-length integers are not allowed for S. if (lenS == 0) { return false; } // Negative numbers are not allowed for S. if (buf[startS + 2] & 0x80) { return false; } // Verify that the length of S is consistent with the size of the signature // including metadatas: // * 1 byte for the integer type of S. // * 1 byte for the size of S. if (startS + lenS + 2 != buf.length) { return false; } // Null bytes at the start of S are not allowed, unless S would otherwise be // interpreted as a negative number. // // /!\ This check can only be performed after we checked that lenR and lenS // are consistent with the size of the signature or we risk to access // out of bound elements. if (lenS > 1 && (buf[startS + 2] == 0x00) && !(buf[startS + 3] & 0x80)) { return false; } return true; }; /** * Compares to bitcoind's IsLowDERSignature * See also ECDSA signature algorithm which enforces this. * See also BIP 62, "low S values in signatures" */ Signature.prototype.hasLowS = function() { if (this.s.lt(new BN(1)) || this.s.gt(new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))) { return false; } return true; }; /** * @returns true if the nhashtype is exactly equal to one of the standard options or combinations thereof. * Translated from bitcoind's IsDefinedHashtypeSignature */ Signature.prototype.hasDefinedHashtype = function() { if (!JSUtil.isNaturalNumber(this.nhashtype)) { return false; } // accept with or without Signature.SIGHASH_ANYONECANPAY by ignoring the bit // base mask was 1F var mask = ~(Signature.SIGHASH_FORKID | Signature.SIGHASH_ANYONECANPAY) >>>0; var temp = this.nhashtype & mask; if (temp < Signature.SIGHASH_ALL || temp > Signature.SIGHASH_SINGLE) { return false; } return true; }; Signature.prototype.toTxFormat = function(signingMethod) { var derbuf = this.toDER(signingMethod); var buf = Buffer.alloc(1); buf.writeUInt8(this.nhashtype, 0); return Buffer.concat([derbuf, buf]); }; Signature.SIGHASH_ALL = 0x01; Signature.SIGHASH_NONE = 0x02; Signature.SIGHASH_SINGLE = 0x03; Signature.SIGHASH_FORKID = 0x40; Signature.SIGHASH_ANYONECANPAY = 0x80; module.exports = Signature;