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 }