1 module tagion.crypto.secp256k1.NativeSecp256k1;
2 
3 @safe:
4 private import tagion.crypto.secp256k1.c.secp256k1;
5 private import tagion.crypto.secp256k1.c.secp256k1_ecdh;
6 private import tagion.crypto.secp256k1.c.secp256k1_schnorrsig;
7 private import tagion.crypto.secp256k1.c.secp256k1_extrakeys;
8 import tagion.crypto.random.random;
9 import std.algorithm;
10 import std.array;
11 import tagion.basic.ConsensusExceptions;
12 
13 enum SECP256K1 : uint {
14     FLAGS_TYPE_MASK = SECP256K1_FLAGS_TYPE_MASK,
15     FLAGS_TYPE_CONTEXT = SECP256K1_FLAGS_TYPE_CONTEXT,
16     FLAGS_TYPE_COMPRESSION = SECP256K1_FLAGS_TYPE_COMPRESSION,
17     /** The higher bits contain the actual data. Do not use directly. */
18     FLAGS_BIT_CONTEXT_VERIFY = SECP256K1_FLAGS_BIT_CONTEXT_VERIFY,
19     FLAGS_BIT_CONTEXT_SIGN = SECP256K1_FLAGS_BIT_CONTEXT_SIGN,
20     FLAGS_BIT_COMPRESSION = FLAGS_BIT_CONTEXT_SIGN,
21 
22     /** Flags to pass to secp256k1_context_create. */
23     CONTEXT_VERIFY = SECP256K1_CONTEXT_VERIFY,
24     CONTEXT_SIGN = SECP256K1_CONTEXT_SIGN,
25     CONTEXT_NONE = SECP256K1_CONTEXT_NONE,
26 
27     /** Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export. */
28     EC_COMPRESSED = SECP256K1_EC_COMPRESSED,
29     EC_UNCOMPRESSED = SECP256K1_EC_UNCOMPRESSED,
30 
31     /** Prefix byte used to tag various encoded curvepoints for specific purposes */
32     TAG_PUBKEY_EVEN = SECP256K1_TAG_PUBKEY_EVEN,
33     TAG_PUBKEY_ODD = SECP256K1_TAG_PUBKEY_ODD,
34     TAG_PUBKEY_UNCOMPRESSED = SECP256K1_TAG_PUBKEY_UNCOMPRESSED,
35     TAG_PUBKEY_HYBRID_EVEN = SECP256K1_TAG_PUBKEY_HYBRID_EVEN,
36     TAG_PUBKEY_HYBRID_ODD = SECP256K1_TAG_PUBKEY_HYBRID_ODD
37 }
38 
39 /++
40  + <p>This class holds native methods to handle ECDSA verification.</p>
41  +
42  + <p>You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1</p>
43  +
44  + <p>To build secp256k1 for use with bitcoinj, run
45  + `./configure --enable-jni --enable-experimental --enable-module-ecdh`
46  + and `make` then copy `.libs/libsecp256k1.so` to your system library path
47  + or point the JVM to the folder containing it with -Djava.library.path
48  + </p>
49  +/
50 class NativeSecp256k1 {
51     enum TWEAK_SIZE = 32;
52     enum SIGNATURE_SIZE = 64;
53     enum SECKEY_SIZE = 32;
54     enum XONLY_PUBKEY_SIZE = 32;
55     enum PUBKEY_SIZE = 33;
56     enum MESSAGE_SIZE = 32;
57     static void check(bool flag, ConsensusFailCode code, string file = __FILE__, size_t line = __LINE__) pure {
58         if (!flag) {
59             throw new SecurityConsensusException(code, file, line);
60         }
61     }
62 
63     enum KEYPAIR_SIZE = secp256k1_keypair.data.length;
64 
65     protected secp256k1_context* _ctx;
66 
67     @trusted
68     this(const SECP256K1 flag = SECP256K1.CONTEXT_SIGN | SECP256K1.CONTEXT_VERIFY) nothrow {
69         _ctx = secp256k1_context_create(flag);
70         scope (exit) {
71             randomizeContext;
72         }
73     }
74 
75     /++
76      + libsecp256k1 Cleanup - This destroys the secp256k1 context object
77      + This should be called at the end of the program for proper cleanup of the context.
78      +/
79     @trusted ~this() {
80         secp256k1_context_destroy(_ctx);
81     }
82 
83     @trusted
84     secp256k1_context* cloneContext() {
85         return secp256k1_context_clone(_ctx);
86     }
87 
88     /++
89      + libsecp256k1 PubKey Tweak-Mul - Tweak pubkey by multiplying to it
90      +
91      + @param tweak some bytes to tweak with
92      + @param pubkey 32-byte seckey
93      +/
94     @trusted
95     final void privTweak(
96             scope const(ubyte[]) keypair,
97             scope const(ubyte[]) tweak,
98             out ubyte[] tweakked_keypair) const
99     in (keypair.length == secp256k1_keypair.data.length)
100     in (tweak.length == TWEAK_SIZE)
101     do {
102         scope (exit) {
103             randomizeContext;
104         }
105         static assert(secp256k1_keypair.data.offsetof == 0);
106         tweakked_keypair = keypair.dup;
107         auto _keypair = cast(secp256k1_keypair*)(&tweakked_keypair[0]);
108         {
109             const ret = secp256k1_keypair_xonly_tweak_add(_ctx, _keypair, &tweak[0]);
110             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_TWEAK_MULT_FAULT);
111         }
112 
113     }
114 
115     @trusted
116     final immutable(ubyte[]) pubTweak(scope const(ubyte[]) pubkey, scope const(ubyte[]) tweak) const//in (pubkey.length == PUBKEY_SIZE)
117     in (tweak.length == TWEAK_SIZE)
118     do {
119         secp256k1_pubkey xy_pubkey;
120         {
121             const ret = secp256k1_ec_pubkey_parse(_ctx, &xy_pubkey, &pubkey[0], pubkey.length);
122             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_SERIALIZE);
123 
124         }
125         secp256k1_xonly_pubkey xonly_pubkey;
126         {
127             const ret = secp256k1_xonly_pubkey_from_pubkey(_ctx, &xonly_pubkey, null, &xy_pubkey);
128             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_SERIALIZE);
129         }
130         secp256k1_pubkey output_pubkey;
131         {
132             const ret = secp256k1_xonly_pubkey_tweak_add(_ctx, &output_pubkey, &xonly_pubkey, &tweak[0]);
133             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_TWEAK_MULT_FAULT);
134 
135         }
136         ubyte[PUBKEY_SIZE] pubkey_result;
137 
138         {
139             size_t len = PUBKEY_SIZE;
140             const ret = secp256k1_ec_pubkey_serialize(_ctx, &pubkey_result[0], &len, &output_pubkey, SECP256K1
141                     .EC_COMPRESSED);
142             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_PARSE_FAULT);
143 
144         }
145 
146         return pubkey_result.idup;
147     }
148     /++
149      + libsecp256k1 create ECDH secret - constant time ECDH calculation
150      +
151      + @param seckey byte array of secret key used in exponentiaion
152      + @param pubkey byte array of public key used in exponentiaion
153      +/
154     @trusted
155     final immutable(ubyte[]) createECDHSecret(
156             scope const(ubyte[]) seckey,
157             const(ubyte[]) pubkey) const
158     in (seckey.length == SECKEY_SIZE)
159     do {
160         scope (exit) {
161             randomizeContext;
162         }
163         secp256k1_pubkey pubkey_result;
164         ubyte[32] result;
165         {
166             const ret = secp256k1_ec_pubkey_parse(_ctx, &pubkey_result, &pubkey[0], pubkey.length);
167             check(ret == 1, ConsensusFailCode.SECURITY_PUBLIC_KEY_PARSE_FAULT);
168         }
169         {
170             const ret = secp256k1_ecdh(_ctx, &result[0], &pubkey_result, &seckey[0], null, null);
171             check(ret == 1, ConsensusFailCode.SECURITY_EDCH_FAULT);
172         }
173         return result.idup;
174     }
175 
176     /++
177      + libsecp256k1 randomize - updates the context randomization
178      +
179      + @param seed 32-byte random seed
180      +/
181     @trusted
182     bool randomizeContext() nothrow const {
183         import tagion.crypto.random.random;
184 
185         ubyte[] ctx_randomize;
186         ctx_randomize.length = 32;
187         getRandom(ctx_randomize);
188         auto __ctx = cast(secp256k1_context*) _ctx;
189         return secp256k1_context_randomize(__ctx, &ctx_randomize[0]) == 1;
190     }
191 
192     @trusted
193     final void createKeyPair(
194             scope const(ubyte[]) seckey,
195             ref secp256k1_keypair keypair) const
196     in (seckey.length == SECKEY_SIZE)
197 
198     do {
199         scope (exit) {
200             randomizeContext;
201         }
202         const rt = secp256k1_keypair_create(_ctx, &keypair, &seckey[0]);
203         check(rt == 1, ConsensusFailCode.SECURITY_FAILD_TO_CREATE_KEYPAIR);
204 
205     }
206 
207     @trusted
208     final void createKeyPair(
209             const(ubyte[]) seckey,
210             out ubyte[] keypair) const
211     in (seckey.length == SECKEY_SIZE || seckey.length == secp256k1_keypair.data.length)
212     do {
213         if (seckey.length == secp256k1_keypair.data.length) {
214             keypair = seckey.dup;
215             return;
216         }
217         keypair.length = secp256k1_keypair.data.length;
218         auto _keypair = cast(secp256k1_keypair*)(&keypair[0]);
219         createKeyPair(seckey, *_keypair);
220     }
221 
222     @trusted
223     final void getSecretKey(
224             ref scope const(ubyte[]) keypair,
225             out ubyte[] seckey) nothrow const
226     in (keypair.length == secp256k1_keypair.data.length)
227 
228     do {
229         seckey.length = SECKEY_SIZE;
230         const _keypair = cast(secp256k1_keypair*)&keypair[0];
231         const ret = secp256k1_keypair_sec(_ctx, &seckey[0], _keypair);
232         assert(ret is 1);
233     }
234 
235     @trusted
236     final void getPubkey(
237             ref scope const(secp256k1_keypair) keypair,
238             ref scope secp256k1_pubkey pubkey) const nothrow {
239         secp256k1_keypair_pub(_ctx, &pubkey, &keypair);
240     }
241 
242     /**
243        Takes both a seckey and keypair 
244 */
245     @trusted
246     final immutable(ubyte[]) getPubkey(scope const(ubyte[]) keypair_seckey) const
247     in (keypair_seckey.length == secp256k1_keypair.data.length ||
248             keypair_seckey.length == SECKEY_SIZE)
249 
250     do {
251         static assert(secp256k1_keypair.data.offsetof == 0);
252         if (keypair_seckey.length == SECKEY_SIZE) {
253             secp256k1_keypair tmp_keypair;
254             scope (exit) {
255                 tmp_keypair.data[] = 0;
256             }
257             createKeyPair(keypair_seckey, tmp_keypair);
258             return getPubkey(tmp_keypair);
259 
260         }
261         const _keypair = cast(secp256k1_keypair*)(&keypair_seckey[0]);
262         return getPubkey(*_keypair);
263     }
264 
265     @trusted
266     final immutable(ubyte[]) getPubkey(ref scope const(secp256k1_keypair) keypair) const {
267         secp256k1_pubkey xy_pubkey;
268         {
269             const rt = secp256k1_keypair_pub(_ctx, &xy_pubkey, &keypair);
270             check(rt == 1, ConsensusFailCode.SECURITY_FAILD_PUBKEY_FROM_KEYPAIR);
271         }
272         ubyte[PUBKEY_SIZE] pubkey;
273         {
274             size_t len = PUBKEY_SIZE;
275             const rt = secp256k1_ec_pubkey_serialize(_ctx, &pubkey[0], &len, &xy_pubkey, SECP256K1.EC_COMPRESSED);
276             check(rt == 1, ConsensusFailCode.SECURITY_FAILD_PUBKEY_FROM_KEYPAIR);
277         }
278         return pubkey.idup;
279 
280     }
281 
282     /++
283      + libsecp256k1 Create an Schnorr signature.
284      +
285      + @param msg Message hash, 32 bytes
286      + @param key Secret key, 32 bytes
287      +
288      + Return values
289      + @param sig byte array of signature
290      +/
291     @trusted
292     final immutable(ubyte[]) sign(
293             const(ubyte[]) msg,
294             ref scope const(secp256k1_keypair) keypair,
295             scope const(ubyte[]) aux_random) const
296     in (msg.length == MESSAGE_SIZE)
297     in (aux_random.length == MESSAGE_SIZE || aux_random.length == 0)
298 
299     do {
300         scope (exit) {
301             randomizeContext;
302         }
303         ubyte[SIGNATURE_SIZE] signature;
304         const rt = secp256k1_schnorrsig_sign32(_ctx, &signature[0], &msg[0], &keypair, &aux_random[0]);
305         check(rt == 1, ConsensusFailCode.SECURITY_FAILD_TO_SIGN_MESSAGE);
306         return signature.idup;
307     }
308 
309     @trusted
310     final immutable(ubyte[]) sign(
311             const(ubyte[]) msg,
312             scope const(ubyte[]) keypair,
313             scope const(ubyte[]) aux_random) const
314     in (keypair.length == secp256k1_keypair.data.length)
315     do {
316         const _keypair = cast(secp256k1_keypair*)(&keypair[0]);
317         return sign(msg, *_keypair, aux_random);
318     }
319 
320     final immutable(ubyte[]) sign(
321             const(ubyte[]) msg,
322             scope const(ubyte[]) keypair) const {
323         ubyte[MESSAGE_SIZE] _aux_random;
324         ubyte[] aux_random = _aux_random;
325         getRandom(aux_random);
326         return sign(msg, keypair, aux_random);
327     }
328 
329     /++
330      + Verifies the given secp256k1 signature in native code.
331      + Calling when enabled == false is undefined (probably library not loaded)
332 
333      + Params:
334      +       msg            = The message which was signed, must be exactly 32 bytes
335      +       signature      = The signature
336      +       pub            = The public key which did the signing
337      +/
338     @trusted
339     final bool verify(
340             const(ubyte[]) msg,
341             const(ubyte[]) signature,
342             const(ubyte[]) pubkey) const nothrow
343     in (pubkey.length == PUBKEY_SIZE)
344 
345     do {
346         secp256k1_pubkey xy_pubkey;
347         secp256k1_ec_pubkey_parse(_ctx, &xy_pubkey, &pubkey[0], pubkey.length);
348         return verify(signature, msg, xy_pubkey);
349     }
350 
351     @trusted
352     final bool verify(
353             const(ubyte[]) signature,
354             const(ubyte[]) msg,
355             ref scope const(secp256k1_pubkey) pubkey) const nothrow
356     in (signature.length == SIGNATURE_SIZE)
357     in (msg.length == MESSAGE_SIZE)
358 
359     do {
360         secp256k1_xonly_pubkey xonly_pubkey;
361         int ret = secp256k1_xonly_pubkey_from_pubkey(_ctx, &xonly_pubkey, null, &pubkey);
362 
363         if (ret != 0) {
364             ret = secp256k1_schnorrsig_verify(_ctx, &signature[0], &msg[0], MESSAGE_SIZE, &xonly_pubkey);
365         }
366         return ret != 0;
367 
368     }
369 }
370 
371 version (unittest) {
372     import std.string : representation;
373     import tagion.utils.Miscellaneous : decode;
374 
375     const(ubyte[]) sha256(scope const(ubyte[]) data) {
376         import std.digest;
377         import std.digest.sha : SHA256;
378 
379         return digest!SHA256(data).dup;
380     }
381 }
382 
383 unittest { /// Schnorr test generated from the secp256k1/examples/schnorr.c 
384     const aux_random = "b0d8d9a460ddcea7ae5dc37a1b5511eb2ab829abe9f2999e490beba20ff3509a".decode;
385     const msg_hash = "1bd69c075dd7b78c4f20a698b22a3fb9d7461525c39827d6aaf7a1628be0a283".decode;
386     const secret_key = "e46b4b2b99674889342c851f890862264a872d4ac53a039fbdab91fd68ed4e71".decode;
387     const expected_pubkey = "02ecd21d66cf97843d467c9d02c5781ec1ec2b369620605fd847bd23472afc7e74".decode;
388     const expected_signature = "021e9a32a12ead3144bb230a81794913a856296ed369159d01b8f57d6d7e7d3630e34f84d49ec054d5251ff6539f24b21097a9c39329eaab2e9429147d6d82f8"
389         .decode;
390     const expected_keypair = decode("e46b4b2b99674889342c851f890862264a872d4ac53a039fbdab91fd68ed4e71747efc2a4723bd47d85f602096362becc11e78c5029d7c463d8497cf661dd2eca89c1820ccc2dd9b0e0e5ab13b1454eb3c37c31308ae20dd8d2aca2199ff4e6b");
391     auto crypt = new NativeSecp256k1;
392     //secp256k1_keypair keypair;
393     ubyte[] keypair;
394     crypt.createKeyPair(secret_key, keypair);
395     assert(keypair == expected_keypair);
396     const signature = crypt.sign(msg_hash, keypair, aux_random);
397     assert(signature == expected_signature);
398     const pubkey = crypt.getPubkey(keypair);
399     assert(pubkey == expected_pubkey);
400     const signature_ok = crypt.verify(msg_hash, signature, pubkey);
401     assert(signature_ok, "Schnorr signing failded");
402 
403 }
404 
405 unittest { /// Schnorr tweak
406     const aux_random = "b0d8d9a460ddcea7ae5dc37a1b5511eb2ab829abe9f2999e490beba20ff3509a".decode;
407     const msg_hash = "1bd69c075dd7b78c4f20a698b22a3fb9d7461525c39827d6aaf7a1628be0a283".decode;
408     const secret_key = "e46b4b2b99674889342c851f890862264a872d4ac53a039fbdab91fd68ed4e71".decode;
409     auto crypt = new NativeSecp256k1;
410     //secp256k1_keypair keypair;
411     ubyte[] keypair;
412     crypt.createKeyPair(secret_key, keypair);
413 
414     const pubkey = crypt.getPubkey(keypair);
415     const tweak = sha256("Some tweak".representation);
416     ubyte[] tweakked_keypair;
417     crypt.privTweak(keypair, tweak, tweakked_keypair);
418     assert(tweakked_keypair != keypair);
419     const tweakked_pubkey = crypt.pubTweak(pubkey, tweak);
420 
421     assert(tweakked_pubkey != pubkey);
422 
423     const tweakked_pubkey_from_keypair = crypt.getPubkey(tweakked_keypair);
424 
425     assert(tweakked_pubkey == tweakked_pubkey_from_keypair, "The tweakked pubkey should be the same as the keypair tweakked pubkey");
426 
427     const signature = crypt.sign(msg_hash, keypair, aux_random);
428     const tweakked_signature = crypt.sign(msg_hash, tweakked_keypair, aux_random);
429 
430     assert(signature != tweakked_signature, "The signature and the tweakked signature should not be the same");
431     {
432         const tweakked_signature_ok = crypt.verify(msg_hash, tweakked_signature, tweakked_pubkey);
433         assert(tweakked_signature, "Tweakked signature should be correct");
434     }
435     {
436         const signature_not_ok = crypt.verify(msg_hash, tweakked_signature, pubkey);
437         assert(!signature_not_ok, "None tweakked signature should not be correct");
438     }
439     {
440         const signature_not_ok = crypt.verify(msg_hash, signature, tweakked_pubkey);
441         assert(!signature_not_ok, "None tweakked signature should not be correct");
442     }
443 }