Skip to content

Commit bb5cf3a

Browse files
authored
Merge pull request #125 from kripod/master
Add support for inverting and negating private keys
2 parents 6a0fc33 + 20a1235 commit bb5cf3a

10 files changed

Lines changed: 213 additions & 0 deletions

File tree

API.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
- [`.privateKeyVerify(Buffer privateKey)`](#privatekeyverifybuffer-privatekey---boolean)
44
- [`.privateKeyExport(Buffer privateKey [, Boolean compressed = true])`](#privatekeyexportbuffer-privatekey--boolean-compressed--true---buffer)
55
- [`.privateKeyImport(Buffer privateKey)`](#privatekeyimportbuffer-privatekey---buffer)
6+
- [`.privateKeyNegate(Buffer privateKey)`](#privatekeynegatebuffer-privatekey---buffer)
7+
- [`.privateKeyModInverse(Buffer privateKey)`](#privatekeymodinversebuffer-privatekey---buffer)
68
- [`.privateKeyTweakAdd(Buffer privateKey, Buffer tweak)`](#privatekeytweakaddbuffer-privatekey-buffer-tweak---buffer)
79
- [`.privateKeyTweakMul(Buffer privateKey, Buffer tweak)`](#privatekeytweakmulbuffer-privatekey-buffer-tweak---buffer)
810
- [`.publicKeyCreate(Buffer privateKey [, Boolean compressed = true])`](#publickeycreatebuffer-privatekey--boolean-compressed--true---buffer)
@@ -43,6 +45,18 @@ Import a *privateKey* in DER format.
4345

4446
<hr>
4547

48+
##### .privateKeyNegate(Buffer privateKey) -> Buffer
49+
50+
Negate a *privateKey* by subtracting it from the order of the curve's base point.
51+
52+
<hr>
53+
54+
##### .privateKeyModInverse(Buffer privateKey) -> Buffer
55+
56+
Compute the inverse of a *privateKey* (modulo the order of the curve's base point).
57+
58+
<hr>
59+
4660
##### .privateKeyTweakAdd(Buffer privateKey, Buffer tweak) -> Buffer
4761

4862
Tweak a *privateKey* by adding *tweak* to it.

lib/elliptic/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ exports.privateKeyExport = function (privateKey, compressed) {
7272
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, true))
7373
}
7474

75+
exports.privateKeyNegate = function (privateKey) {
76+
var bn = new BN(privateKey)
77+
return bn.isZero() ? Buffer.alloc(32) : ecparams.n.sub(bn).umod(ecparams.n).toArrayLike(Buffer, 'be', 32)
78+
}
79+
80+
exports.privateKeyModInverse = function (privateKey) {
81+
var bn = new BN(privateKey)
82+
if (bn.cmp(ecparams.n) >= 0 || bn.isZero()) throw new Error(messages.EC_PRIVATE_KEY_RANGE_INVALID)
83+
84+
return bn.invm(ecparams.n).toArrayLike(Buffer, 'be', 32)
85+
}
86+
7587
exports.privateKeyTweakAdd = function (privateKey, tweak) {
7688
var bn = new BN(tweak)
7789
if (bn.cmp(ecparams.n) >= 0) throw new Error(messages.EC_PRIVATE_KEY_TWEAK_ADD_FAIL)

lib/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ module.exports = function (secp256k1) {
3636
throw new Error(messages.EC_PRIVATE_KEY_IMPORT_DER_FAIL)
3737
},
3838

39+
privateKeyNegate: function (privateKey) {
40+
assert.isBuffer(privateKey, messages.EC_PRIVATE_KEY_TYPE_INVALID)
41+
assert.isBufferLength(privateKey, 32, messages.EC_PRIVATE_KEY_LENGTH_INVALID)
42+
43+
return secp256k1.privateKeyNegate(privateKey)
44+
},
45+
46+
privateKeyModInverse: function (privateKey) {
47+
assert.isBuffer(privateKey, messages.EC_PRIVATE_KEY_TYPE_INVALID)
48+
assert.isBufferLength(privateKey, 32, messages.EC_PRIVATE_KEY_LENGTH_INVALID)
49+
50+
return secp256k1.privateKeyModInverse(privateKey)
51+
},
52+
3953
privateKeyTweakAdd: function (privateKey, tweak) {
4054
assert.isBuffer(privateKey, messages.EC_PRIVATE_KEY_TYPE_INVALID)
4155
assert.isBufferLength(privateKey, 32, messages.EC_PRIVATE_KEY_LENGTH_INVALID)

lib/js/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@ exports.privateKeyExport = function (privateKey, compressed) {
1919
return g.mul(d).toPublicKey(compressed)
2020
}
2121

22+
exports.privateKeyNegate = function (privateKey) {
23+
var bn = BN.fromBuffer(privateKey)
24+
25+
if (bn.isZero()) {
26+
return Buffer.alloc(32)
27+
}
28+
29+
if (bn.ucmp(BN.n) !== 0) {
30+
while (bn.isOverflow()) {
31+
bn.isub(BN.n)
32+
}
33+
}
34+
35+
return BN.n.sub(bn).toBuffer()
36+
}
37+
38+
exports.privateKeyModInverse = function (privateKey) {
39+
var bn = BN.fromBuffer(privateKey)
40+
if (bn.isOverflow() || bn.isZero()) throw new Error(messages.EC_PRIVATE_KEY_RANGE_INVALID)
41+
42+
return bn.uinvm().toBuffer()
43+
}
44+
2245
exports.privateKeyTweakAdd = function (privateKey, tweak) {
2346
var bn = BN.fromBuffer(tweak)
2447
if (bn.isOverflow()) throw new Error(messages.EC_PRIVATE_KEY_TWEAK_ADD_FAIL)

lib/messages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"COMPRESSED_TYPE_INVALID": "compressed should be a boolean",
33
"EC_PRIVATE_KEY_TYPE_INVALID": "private key should be a Buffer",
44
"EC_PRIVATE_KEY_LENGTH_INVALID": "private key length is invalid",
5+
"EC_PRIVATE_KEY_RANGE_INVALID": "private key range is invalid",
56
"EC_PRIVATE_KEY_TWEAK_ADD_FAIL": "tweak out of range or resulting private key is invalid",
67
"EC_PRIVATE_KEY_TWEAK_MUL_FAIL": "tweak out of range",
78
"EC_PRIVATE_KEY_EXPORT_DER_FAIL": "couldn't export to DER format",

src/addon.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ NAN_MODULE_INIT(Init) {
1818
Nan::Export(target, "privateKeyVerify", privateKeyVerify);
1919
Nan::Export(target, "privateKeyExport", privateKeyExport);
2020
Nan::Export(target, "privateKeyImport", privateKeyImport);
21+
Nan::Export(target, "privateKeyNegate", privateKeyNegate);
22+
Nan::Export(target, "privateKeyModInverse", privateKeyModInverse);
2123
Nan::Export(target, "privateKeyTweakAdd", privateKeyTweakAdd);
2224
Nan::Export(target, "privateKeyTweakMul", privateKeyTweakMul);
2325

src/messages.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#define EC_PRIVATE_KEY_TYPE_INVALID "private key should be a Buffer"
77
#define EC_PRIVATE_KEY_LENGTH_INVALID "private key length is invalid"
8+
#define EC_PRIVATE_KEY_RANGE_INVALID "private key range is invalid"
89
#define EC_PRIVATE_KEY_TWEAK_ADD_FAIL "tweak out of range or resulting private key is invalid"
910
#define EC_PRIVATE_KEY_TWEAK_MUL_FAIL "tweak out of range"
1011
#define EC_PRIVATE_KEY_EXPORT_DER_FAIL "couldn't export to DER format"

src/privatekey.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <node.h>
22
#include <nan.h>
33
#include <secp256k1.h>
4+
#include <scalar_impl.h>
45
#include <lax_der_privatekey_parsing.h>
56

67
#include "messages.h"
@@ -60,6 +61,45 @@ NAN_METHOD(privateKeyImport) {
6061
info.GetReturnValue().Set(COPY_BUFFER(private_key, 32));
6162
}
6263

64+
NAN_METHOD(privateKeyNegate) {
65+
Nan::HandleScope scope;
66+
67+
v8::Local<v8::Object> private_key_buffer = info[0].As<v8::Object>();
68+
CHECK_TYPE_BUFFER(private_key_buffer, EC_PRIVATE_KEY_TYPE_INVALID);
69+
CHECK_BUFFER_LENGTH(private_key_buffer, 32, EC_PRIVATE_KEY_LENGTH_INVALID);
70+
unsigned char private_key[32];
71+
memcpy(&private_key[0], node::Buffer::Data(private_key_buffer), 32);
72+
73+
secp256k1_ec_privkey_negate(secp256k1ctx, &private_key[0]);
74+
75+
info.GetReturnValue().Set(COPY_BUFFER(&private_key[0], 32));
76+
}
77+
78+
NAN_METHOD(privateKeyModInverse) {
79+
Nan::HandleScope scope;
80+
81+
v8::Local<v8::Object> private_key_buffer = info[0].As<v8::Object>();
82+
CHECK_TYPE_BUFFER(private_key_buffer, EC_PRIVATE_KEY_TYPE_INVALID);
83+
CHECK_BUFFER_LENGTH(private_key_buffer, 32, EC_PRIVATE_KEY_LENGTH_INVALID);
84+
unsigned char private_key[32];
85+
memcpy(&private_key[0], node::Buffer::Data(private_key_buffer), 32);
86+
87+
secp256k1_scalar s;
88+
int overflow = 0;
89+
secp256k1_scalar_set_b32(&s, private_key, &overflow);
90+
if (overflow || secp256k1_scalar_is_zero(&s)) {
91+
secp256k1_scalar_clear(&s);
92+
return Nan::ThrowError(EC_PRIVATE_KEY_RANGE_INVALID);
93+
}
94+
95+
secp256k1_scalar_inverse(&s, &s);
96+
97+
secp256k1_scalar_get_b32(private_key, &s);
98+
secp256k1_scalar_clear(&s);
99+
100+
info.GetReturnValue().Set(COPY_BUFFER(&private_key[0], 32));
101+
}
102+
63103
NAN_METHOD(privateKeyTweakAdd) {
64104
Nan::HandleScope scope;
65105

src/privatekey.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
NAN_METHOD(privateKeyVerify);
88
NAN_METHOD(privateKeyExport);
99
NAN_METHOD(privateKeyImport);
10+
NAN_METHOD(privateKeyNegate);
11+
NAN_METHOD(privateKeyModInverse);
1012
NAN_METHOD(privateKeyTweakAdd);
1113
NAN_METHOD(privateKeyTweakMul);
1214

test/privatekey.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,110 @@ module.exports = function (t, secp256k1) {
113113
t.end()
114114
})
115115

116+
t.test('privateKeyNegate', function (t) {
117+
t.test('private key should be a Buffer', function (t) {
118+
t.throws(function () {
119+
secp256k1.privateKeyNegate(null)
120+
}, new RegExp('^TypeError: ' + messages.EC_PRIVATE_KEY_TYPE_INVALID + '$'))
121+
t.end()
122+
})
123+
124+
t.test('private key length is invalid', function (t) {
125+
t.throws(function () {
126+
var privateKey = util.getPrivateKey().slice(1)
127+
secp256k1.privateKeyNegate(privateKey)
128+
}, new RegExp('^RangeError: ' + messages.EC_PRIVATE_KEY_LENGTH_INVALID + '$'))
129+
t.end()
130+
})
131+
132+
t.test('private key is 0', function (t) {
133+
var privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32)
134+
135+
var expected = Buffer.alloc(32)
136+
var result = secp256k1.privateKeyNegate(privateKey)
137+
t.same(result, expected)
138+
139+
t.end()
140+
})
141+
142+
t.test('private key equal to N', function (t) {
143+
var privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32)
144+
145+
var expected = Buffer.alloc(32)
146+
var result = secp256k1.privateKeyNegate(privateKey)
147+
t.same(result, expected)
148+
149+
t.end()
150+
})
151+
152+
t.test('private key overflow', function (t) {
153+
var privateKey = util.ec.curve.n.addn(10).toArrayLike(Buffer, 'be', 32)
154+
155+
var expected = util.ec.curve.n.subn(10).toArrayLike(Buffer, 'be', 32)
156+
var result = secp256k1.privateKeyNegate(privateKey)
157+
t.same(result, expected)
158+
159+
t.end()
160+
})
161+
162+
util.repeat(t, 'random tests', util.env.repeat, function (t) {
163+
var privateKey = util.getPrivateKey()
164+
165+
var expected = util.ec.curve.n.sub(new BN(privateKey))
166+
var result = secp256k1.privateKeyNegate(privateKey)
167+
t.same(result.toString('hex'), expected.toString(16, 64))
168+
169+
t.end()
170+
})
171+
172+
t.end()
173+
})
174+
175+
t.test('privateKeyModInverse', function (t) {
176+
t.test('private key should be a Buffer', function (t) {
177+
t.throws(function () {
178+
secp256k1.privateKeyModInverse(null)
179+
}, new RegExp('^TypeError: ' + messages.EC_PRIVATE_KEY_TYPE_INVALID + '$'))
180+
t.end()
181+
})
182+
183+
t.test('private key length is invalid', function (t) {
184+
t.throws(function () {
185+
var privateKey = util.getPrivateKey().slice(1)
186+
secp256k1.privateKeyModInverse(privateKey)
187+
}, new RegExp('^RangeError: ' + messages.EC_PRIVATE_KEY_LENGTH_INVALID + '$'))
188+
t.end()
189+
})
190+
191+
t.test('private key is 0', function (t) {
192+
t.throws(function () {
193+
var privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32)
194+
secp256k1.privateKeyModInverse(privateKey)
195+
}, new RegExp('^Error: ' + messages.EC_PRIVATE_KEY_RANGE_INVALID + '$'))
196+
t.end()
197+
})
198+
199+
t.test('private key equal to N', function (t) {
200+
t.throws(function () {
201+
var privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32)
202+
secp256k1.privateKeyModInverse(privateKey)
203+
}, new RegExp('^Error: ' + messages.EC_PRIVATE_KEY_RANGE_INVALID + '$'))
204+
t.end()
205+
})
206+
207+
util.repeat(t, 'random tests', util.env.repeat, function (t) {
208+
var privateKey = util.getPrivateKey()
209+
210+
var expected = new BN(privateKey).invm(util.ec.curve.n)
211+
var result = secp256k1.privateKeyModInverse(privateKey)
212+
t.same(result.toString('hex'), expected.toString(16, 64))
213+
214+
t.end()
215+
})
216+
217+
t.end()
218+
})
219+
116220
t.test('privateKeyTweakAdd', function (t) {
117221
t.test('private key should be a Buffer', function (t) {
118222
t.throws(function () {

0 commit comments

Comments
 (0)