bitcore-lib-zcash/lib/crypto/signature.js

289 lines
7.6 KiB
JavaScript
Raw Normal View History

2014-11-20 07:56:35 -08:00
'use strict';
2014-11-27 11:42:44 -08:00
var BN = require('./bn');
2014-08-09 14:42:23 -07:00
2014-09-01 21:08:16 -07:00
var Signature = function Signature(r, s) {
if (!(this instanceof Signature))
2014-09-02 11:59:42 -07:00
return new Signature(r, s);
2014-09-01 21:08:16 -07:00
if (r instanceof BN) {
this.set({
r: r,
s: s
});
} else if (r) {
2014-09-01 21:08:16 -07:00
var obj = r;
2014-08-28 16:38:21 -07:00
this.set(obj);
2014-09-01 21:08:16 -07:00
}
2014-08-28 16:38:21 -07:00
};
2014-08-28 16:38:21 -07:00
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
return this;
2014-08-09 14:42:23 -07:00
};
Signature.prototype.fromCompact = function(buf) {
2014-08-22 19:43:32 -07:00
var compressed = true;
2014-11-20 07:56:35 -08:00
var i = buf.slice(0, 1)[0] - 27 - 4;
//TODO: handle uncompressed pubkeys
/*
2014-08-22 19:43:32 -07:00
if (i < 0) {
2014-11-20 07:56:35 -08:00
compressed = false;
2014-08-22 19:43:32 -07:00
i = i + 4;
}
2014-11-20 07:56:35 -08:00
*/
2014-08-22 19:43:32 -07:00
2014-08-09 14:42:23 -07:00
var b2 = buf.slice(1, 33);
var b3 = buf.slice(33, 65);
if (!(i === 0 || i === 1 || i === 2 || i === 3))
throw new Error('i must be 0, 1, 2, or 3');
2014-08-09 14:42:23 -07:00
if (b2.length !== 32)
throw new Error('r must be 32 bytes');
2014-08-09 14:42:23 -07:00
if (b3.length !== 32)
throw new Error('s must be 32 bytes');
2014-08-09 14:42:23 -07:00
2014-08-22 19:43:32 -07:00
this.compressed = compressed;
this.i = i;
this.r = BN().fromBuffer(b2);
this.s = BN().fromBuffer(b3);
return this;
2014-08-09 14:42:23 -07:00
};
Signature.prototype.fromDER = function(buf) {
var obj = Signature.parseDER(buf);
this.r = obj.r;
this.s = obj.s;
return this;
2014-08-09 14:42:23 -07:00
};
Signature.prototype.fromString = function(str) {
var buf = new Buffer(str, 'hex');
this.fromDER(buf);
return this;
2014-08-09 14:42:23 -07:00
};
Signature.parseDER = function(buf) {
if (!Buffer.isBuffer(buf))
throw new Error('DER formatted signature should be a buffer');
2014-08-09 14:42:23 -07:00
var header = buf[0];
if (header !== 0x30)
throw new Error('Header byte should be 0x30');
2014-08-09 14:42:23 -07:00
var length = buf[1];
if (length !== buf.slice(2).length)
throw new Error('Length byte should length of what follows');
2014-08-09 14:42:23 -07:00
var rheader = buf[2 + 0];
if (rheader !== 0x02)
throw new Error('Integer byte for r should be 0x02');
2014-08-09 14:42:23 -07:00
var rlength = buf[2 + 1];
var rbuf = buf.slice(2 + 2, 2 + 2 + rlength);
var r = BN().fromBuffer(rbuf);
2014-08-09 14:42:23 -07:00
var rneg = buf[2 + 1 + 1] === 0x00 ? true : false;
if (rlength !== rbuf.length)
throw new Error('Length of r incorrect');
2014-08-09 14:42:23 -07:00
var sheader = buf[2 + 2 + rlength + 0];
if (sheader !== 0x02)
throw new Error('Integer byte for s should be 0x02');
2014-08-09 14:42:23 -07:00
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);
2014-08-09 14:42:23 -07:00
var sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false;
if (slength !== sbuf.length)
throw new Error('Length of s incorrect');
2014-08-09 14:42:23 -07:00
var sumlength = 2 + 2 + rlength + 2 + slength;
if (length !== sumlength - 2)
throw new Error('Length of signature incorrect');
2014-08-09 14:42:23 -07:00
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;
};
2014-08-22 19:43:32 -07:00
Signature.prototype.toCompact = function(i, compressed) {
i = typeof i === 'number' ? i : this.i;
2014-08-22 19:43:32 -07:00
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');
2014-08-22 19:43:32 -07:00
var val = i + 27 + 4;
if (compressed === false)
val = val - 4;
var b1 = new Buffer([val]);
var b2 = this.r.toBuffer({
size: 32
});
var b3 = this.s.toBuffer({
size: 32
});
2014-08-09 14:42:23 -07:00
return Buffer.concat([b1, b2, b3]);
};
Signature.prototype.toDER = function() {
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([new Buffer([0x00]), rnbuf]) : rnbuf;
var sbuf = sneg ? Buffer.concat([new Buffer([0x00]), snbuf]) : snbuf;
var rlength = rbuf.length;
var slength = sbuf.length;
var length = 2 + rlength + 2 + slength;
2014-08-09 14:42:23 -07:00
var rheader = 0x02;
var sheader = 0x02;
var header = 0x30;
var der = Buffer.concat([new Buffer([header, length, rheader, rlength]), rbuf, new Buffer([sheader, slength]), sbuf]);
return der;
};
Signature.prototype.toString = function() {
var buf = this.toDER();
return buf.toString('hex');
};
2014-08-19 12:02:34 -07:00
/**
* 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] [hashtype]
* 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.isTxDER = function(buf) {
if (buf.length < 9) {
// Non-canonical signature: too short
return false;
}
if (buf.length > 73) {
// Non-canonical signature: too long
return false;
}
if (buf[0] !== 0x30) {
// Non-canonical signature: wrong type
return false;
}
if (buf[1] !== buf.length - 3) {
// Non-canonical signature: wrong length marker
return false;
}
var nLenR = buf[3];
if (5 + nLenR >= buf.length) {
// Non-canonical signature: S length misplaced
return false;
}
var nLenS = buf[5 + nLenR];
if ((nLenR + nLenS + 7) !== buf.length) {
// Non-canonical signature: R+S length mismatch
return false;
}
var R = buf.slice(4);
if (buf[4 - 2] !== 0x02) {
// Non-canonical signature: R value type mismatch
return false;
}
if (nLenR === 0) {
// Non-canonical signature: R length is zero
return false;
}
if (R[0] & 0x80) {
// Non-canonical signature: R value negative
return false;
}
if (nLenR > 1 && (R[0] === 0x00) && !(R[1] & 0x80)) {
// Non-canonical signature: R value excessively padded
return false;
}
var S = buf.slice(6 + nLenR);
if (buf[6 + nLenR - 2] !== 0x02) {
// Non-canonical signature: S value type mismatch
return false;
}
if (nLenS === 0) {
// Non-canonical signature: S length is zero
return false;
}
if (S[0] & 0x80) {
// Non-canonical signature: S value negative
return false;
}
if (nLenS > 1 && (S[0] === 0x00) && !(S[1] & 0x80)) {
// Non-canonical signature: S value excessively padded
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(1) ||
this.s.gt(BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0'))) {
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 (this.nhashtype < Signature.SIGHASH_ALL || this.nhashtype > Signature.SIGHASH_SINGLE) {
return false;
}
return true;
};
Signature.prototype.toTxFormat = function() {
var derbuf = this.toDER();
var buf = new Buffer(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_ANYONECANPAY = 0x80;
2014-08-09 14:42:23 -07:00
module.exports = Signature;