Skip to content

Commit 34c8534

Browse files
authored
fix: signature verification due to leading zeros
According to FIPS 186-5, section 6.4.2 ECDSA Signature Verification Algorithm, the hash of the message must be adjusted based on the order n of the base point of the elliptic curve: If log2(n) ≥ hashlen, set E = H. Otherwise, set E equal to the leftmost log2(n) bits of H. Unfortunately because elliptic converts messages to BN instances the reported `byteLength()` for the message can be incorrect if the message has 8 or more leading zero bits. Here we fix it by: 1. Counting leading zeroes in hex strings provided as messages 2. Counting all array entries in Array-like (e.g. Buffer) messages 3. Providing an `msgBitLength` option to both `.sign`/`.verify` to let user override the behavior Original PR: #322 Credit: @Markus-MS
1 parent 3e46a48 commit 34c8534

File tree

3 files changed

+73
-7
lines changed

3 files changed

+73
-7
lines changed

lib/elliptic/ec/index.js

+27-5
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,27 @@ EC.prototype.genKeyPair = function genKeyPair(options) {
7878
}
7979
};
8080

81-
EC.prototype._truncateToN = function _truncateToN(msg, truncOnly) {
82-
var delta = msg.byteLength() * 8 - this.n.bitLength();
81+
EC.prototype._truncateToN = function _truncateToN(msg, truncOnly, bitLength) {
82+
var byteLength;
83+
if (BN.isBN(msg) || typeof msg === 'number') {
84+
msg = new BN(msg, 16);
85+
byteLength = msg.byteLength();
86+
} else if (typeof msg === 'object') {
87+
// BN assumes an array-like input and asserts length
88+
byteLength = msg.length;
89+
msg = new BN(msg, 16);
90+
} else {
91+
// BN converts the value to string
92+
var str = msg.toString();
93+
// HEX encoding
94+
byteLength = (str.length + 1) >>> 1;
95+
msg = new BN(str, 16);
96+
}
97+
// Allow overriding
98+
if (typeof bitLength !== 'number') {
99+
bitLength = byteLength * 8;
100+
}
101+
var delta = bitLength - this.n.bitLength();
83102
if (delta > 0)
84103
msg = msg.ushrn(delta);
85104
if (!truncOnly && msg.cmp(this.n) >= 0)
@@ -97,7 +116,7 @@ EC.prototype.sign = function sign(msg, key, enc, options) {
97116
options = {};
98117

99118
key = this.keyFromPrivate(key, enc);
100-
msg = this._truncateToN(new BN(msg, 16));
119+
msg = this._truncateToN(msg, false, options.msgBitLength);
101120

102121
// Zero-extend key to provide enough entropy
103122
var bytes = this.n.byteLength();
@@ -153,8 +172,11 @@ EC.prototype.sign = function sign(msg, key, enc, options) {
153172
}
154173
};
155174

156-
EC.prototype.verify = function verify(msg, signature, key, enc) {
157-
msg = this._truncateToN(new BN(msg, 16));
175+
EC.prototype.verify = function verify(msg, signature, key, enc, options) {
176+
if (!options)
177+
options = {};
178+
179+
msg = this._truncateToN(msg, false, options.msgBitLength);
158180
key = this.keyFromPublic(key, enc);
159181
signature = new Signature(signature, 'hex');
160182

lib/elliptic/ec/key.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ KeyPair.prototype.sign = function sign(msg, enc, options) {
111111
return this.ec.sign(msg, this, enc, options);
112112
};
113113

114-
KeyPair.prototype.verify = function verify(msg, signature) {
115-
return this.ec.verify(msg, signature, this);
114+
KeyPair.prototype.verify = function verify(msg, signature, options) {
115+
return this.ec.verify(msg, signature, this, undefined, options);
116116
};
117117

118118
KeyPair.prototype.inspect = function inspect() {

test/ecdsa-test.js

+44
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,50 @@ describe('ECDSA', function() {
489489
});
490490
});
491491

492+
it('Wycheproof special hash case with hex', function() {
493+
var curve = new elliptic.ec('p192');
494+
var msg =
495+
'00000000690ed426ccf17803ebe2bd0884bcd58a1bb5e7477ead3645f356e7a9';
496+
var sig = '303502186f20676c0d04fc40ea55d5702f798355787363a9' +
497+
'1e97a7e50219009d1c8c171b2b02e7d791c204c17cea4cf5' +
498+
'56a2034288885b';
499+
var pub = '04cd35a0b18eeb8fcd87ff019780012828745f046e785deb' +
500+
'a28150de1be6cb4376523006beff30ff09b4049125ced29723';
501+
var pubKey = curve.keyFromPublic(pub, 'hex');
502+
assert(pubKey.verify(msg, sig) === true);
503+
});
504+
505+
it('Wycheproof special hash case with Array', function() {
506+
var curve = new elliptic.ec('p192');
507+
var msg = [
508+
0x00, 0x00, 0x00, 0x00, 0x69, 0x0e, 0xd4, 0x26, 0xcc, 0xf1, 0x78,
509+
0x03, 0xeb, 0xe2, 0xbd, 0x08, 0x84, 0xbc, 0xd5, 0x8a, 0x1b, 0xb5,
510+
0xe7, 0x47, 0x7e, 0xad, 0x36, 0x45, 0xf3, 0x56, 0xe7, 0xa9,
511+
];
512+
var sig = '303502186f20676c0d04fc40ea55d5702f798355787363a9' +
513+
'1e97a7e50219009d1c8c171b2b02e7d791c204c17cea4cf5' +
514+
'56a2034288885b';
515+
var pub = '04cd35a0b18eeb8fcd87ff019780012828745f046e785deb' +
516+
'a28150de1be6cb4376523006beff30ff09b4049125ced29723';
517+
var pubKey = curve.keyFromPublic(pub, 'hex');
518+
assert(pubKey.verify(msg, sig) === true);
519+
});
520+
521+
it('Wycheproof special hash case with BN', function() {
522+
var curve = new elliptic.ec('p192');
523+
var msg = new BN(
524+
'00000000690ed426ccf17803ebe2bd0884bcd58a1bb5e7477ead3645f356e7a9',
525+
16,
526+
);
527+
var sig = '303502186f20676c0d04fc40ea55d5702f798355787363a9' +
528+
'1e97a7e50219009d1c8c171b2b02e7d791c204c17cea4cf5' +
529+
'56a2034288885b';
530+
var pub = '04cd35a0b18eeb8fcd87ff019780012828745f046e785deb' +
531+
'a28150de1be6cb4376523006beff30ff09b4049125ced29723';
532+
var pubKey = curve.keyFromPublic(pub, 'hex');
533+
assert(pubKey.verify(msg, sig, { msgBitLength: 32 * 8 }) === true);
534+
});
535+
492536
describe('Signature', function () {
493537
it('recoveryParam is 0', function () {
494538
var sig = new Signature({ r: '00', s: '00', recoveryParam: 0 });

0 commit comments

Comments
 (0)