1 module secp256k1;
2 
3 import std.stdio;
4 import std.exception : enforce;
5 import std.conv : to, text;
6 import std : toHexString;
7 import std.algorithm : reverse;
8 
9 import include.secp256k1_recovery;
10 import include.secp256k1;
11 
12 import keccak : keccak256;
13 
14 alias bytes = ubyte[];
15 unittest
16 {
17     writeln("Elliptic Curve example");
18     ubyte[32] pk;
19     pk[0 .. $] = "beb75b08049e9316d1375999c7d968f3c23fdf606b296fcdfc9a41cdd7e7347c".hexToBytes;
20     auto ec = new secp256k1(pk);
21     auto msg = keccak256(cast(ubyte[]) "abbbabbaabababababa");
22     ec.writeln;
23     ec.sign(msg).writeln;
24     ec.address.toHexString.writeln(" --  address");
25     ec.sign(msg).ecRecover(msg).toHexString.writeln(" -- recovered address");
26     foreach (i; 0 .. 500)
27     {
28         auto sign = ec.sign(msg);
29         auto recAddr = sign.ecRecover(msg);
30         assert(recAddr == ec.address, text(sign, recAddr));
31     }
32     assert(ec.verify(msg, ec.sign(msg)));
33 }
34 
35 private bytes hexToBytes(string s) pure
36 {
37     import std : chunks, map, array, startsWith, replace;
38     import std.range : padLeft;
39 
40     s = s.replace("_", "").replace(" ", "");
41     if (s.startsWith(`0x`))
42         s = s[2 .. $];
43 
44     return s.padLeft('0', s.length + s.length % 2).chunks(2).map!q{a.parse!ubyte(16)}.array;
45 }
46 
47 struct Signature
48 {
49     int recid;
50     ubyte[32] r;
51     ubyte[32] s;
52 }
53 
54 class secp256k1
55 {
56     private secp256k1_context* ctx;
57     bool canSign;
58     bool canVerify;
59     ubyte[32] secKey;
60     ubyte[64] pubKey;
61 
62     this() @trusted
63     {
64         ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
65         do
66         {
67             import random = std.random;
68 
69             random.uniform!"[]"(ulong.min, ulong.max);
70             static immutable secKeySize = 32 / ulong.sizeof;
71             foreach (ref e; cast(ulong[secKeySize]) secKey)
72             {
73                 e = random.uniform(ulong.min, ulong.max);
74             }
75         }
76         while (!secp256k1_ec_seckey_verify(ctx, secKey.ptr));
77         canSign = canVerify = true;
78         assert(secp256k1_ec_pubkey_create(ctx, cast(secp256k1_pubkey*)&pubKey, secKey.ptr));
79     }
80 
81     this(const ubyte[32] key) pure nothrow @safe @nogc
82     {
83         canSign = canVerify = true;
84         secKey = key;
85         ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
86         assert(secp256k1_ec_pubkey_create(ctx, cast(secp256k1_pubkey*)&pubKey, secKey.ptr));
87     }
88 
89     this(const ubyte[64] pubKey) pure nothrow @safe @nogc
90     {
91         canVerify = true;
92         ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
93         this.pubKey = pubKey;
94     }
95 
96     Signature sign(const ubyte[] data) pure @trusted
97     {
98         ubyte[32] msgHash = keccak256(data);
99         return signHash(msgHash);
100     }
101 
102     Signature signHash(const ubyte[32] msgHash) pure @trusted
103     {
104         canSign.enforce("this key cannot sign: secKey not inited");
105         secp256k1_ecdsa_recoverable_signature signature;
106         ctx.secp256k1_ecdsa_sign_recoverable(&signature, msgHash.ptr, secKey.ptr, null, null);
107 
108         signature.data[].reverse;
109         return Signature(signature.data[0], signature.data[33 .. 65], signature.data[1 .. 33]);
110     }
111 
112     bool verify(const ubyte[] data, const Signature sig) pure nothrow @nogc @trusted
113 
114     {
115         secp256k1_ecdsa_signature cSig;
116 
117         auto msgHash = keccak256(data);
118         cSig.data[32 .. 64] = sig.r;
119         cSig.data[0 .. 32] = sig.s;
120         cSig.data[].reverse;
121         return cast(bool) ctx.secp256k1_ecdsa_verify(&cSig, msgHash.ptr,
122                 cast(secp256k1_pubkey*)&pubKey);
123 
124     }
125 
126     ubyte[20] address() @property const pure @nogc @safe nothrow
127     {
128         return pubKey.secp256k1_pubkey.toAddress;
129     }
130 
131     ~this()
132     {
133         secp256k1_context_destroy(ctx);
134     }
135 }
136 
137 ubyte[20] toAddress(secp256k1_pubkey pubKey) pure nothrow @nogc @safe
138 {
139     import std.algorithm : reverse;
140 
141     pubKey.data[0 .. 32].reverse;
142     pubKey.data[32 .. $].reverse;
143     return keccak256(pubKey.data[])[12 .. $];
144 
145 }
146 
147 ubyte[20] ecRecover(Signature sig, ubyte[] msg) pure nothrow @nogc @trusted
148 {
149     auto ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
150     secp256k1_pubkey pubKey;
151     secp256k1_ecdsa_recoverable_signature cSig;
152     cSig.data[0] = cast(ubyte) sig.recid;
153     cSig.data[33 .. 65] = sig.r;
154     cSig.data[1 .. 33] = sig.s;
155     cSig.data[].reverse;
156     auto msgHash = keccak256(msg);
157     ctx.secp256k1_ecdsa_recover(cast(secp256k1_pubkey*)&pubKey, &cSig, msgHash.ptr);
158     return toAddress(pubKey);
159 
160 }
161 
162 unittest
163 {
164     import std.stdio;
165 
166     writeln("Leading zero seach test");
167     foreach (i; 0 .. 10_000)
168     {
169         auto a = new secp256k1;
170         auto msg = keccak256(cast(ubyte[]) "abbbabbaabababababa");
171         auto sig = a.sign(msg);
172         assert(a.verify(msg, sig));
173         auto addr = a.address.toHexString;
174         if (addr.leadingZeros > 2)
175         {
176             addr.writeln;
177         }
178     }
179 
180 }
181 
182 auto leadingZeros(T)(T s) pure nothrow @nogc @safe
183 {
184 
185     foreach (i, c; s)
186     {
187         if (c != '0')
188             return i;
189     }
190     return s.length;
191 }