33// that can be found in the LICENSE file.
44module ecdsa
55
6+ import hash
7+ import crypto
8+ import crypto.sha256
9+ import crypto.sha512
10+
611#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
712
813#flag -I/usr/include/openssl
@@ -39,9 +44,39 @@ fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX
3944fn C.BN_CTX_new () & C.BN_CTX
4045fn C.BN_CTX_free (ctx & C.BN_CTX)
4146
47+ // for checking the key
48+ fn C.EC_KEY_check_key (key & C.EC_KEY) int
49+
4250// NID constants
51+ //
52+ // NIST P-256 is refered to as secp256r1 and prime256v1, defined as #define NID_X9_62_prime256v1 415
53+ // Different names, but they are all the same.
54+ // https://www.rfc-editor.org/rfc/rfc4492.html#appendix-A
4355const nid_prime256v1 = C.NID_X9_62_ prime256 v1
4456
57+ // NIST P-384, ie, secp384r1 curve, defined as #define NID_secp384r1 715
58+ const nid_secp384r1 = C.NID_secp384 r1
59+
60+ // NIST P-521, ie, secp521r1 curve, defined as #define NID_secp521r1 716
61+ const nid_secp521r1 = C.NID_secp521 r1
62+
63+ // Bitcoin curve, defined as #define NID_secp256k1 714
64+ const nid_secp256k1 = C.NID_secp256 k1
65+
66+ // The list of supported curve(s)
67+ pub enum Nid {
68+ prime256 v1
69+ secp384 r1
70+ secp521 r1
71+ secp256 k1
72+ }
73+
74+ @[params]
75+ pub struct CurveOptions {
76+ pub mut :
77+ nid Nid = .prime256 v1 // default to NIST P-256 curve
78+ }
79+
4580@[typedef]
4681struct C.EC_KEY {}
4782
@@ -68,10 +103,9 @@ pub struct PublicKey {
68103 key & C.EC_KEY
69104}
70105
71- // Generate a new key pair
72- pub fn generate_key () ! (PublicKey, PrivateKey) {
73- nid := nid_prime256 v1 // Using NIST P-256 curve
74- ec_key := C.EC_KEY_new_by_curve_name (nid)
106+ // Generate a new key pair. If opt was not provided, its default to prime256v1 curve.
107+ pub fn generate_key (opt CurveOptions) ! (PublicKey, PrivateKey) {
108+ ec_key := new_curve (opt)
75109 if ec_key == 0 {
76110 return error ('Failed to create new EC_KEY' )
77111 }
@@ -80,6 +114,7 @@ pub fn generate_key() !(PublicKey, PrivateKey) {
80114 C.EC_KEY_free (ec_key)
81115 return error ('Failed to generate EC_KEY' )
82116 }
117+
83118 priv_key := PrivateKey{
84119 key: ec_key
85120 }
@@ -89,11 +124,10 @@ pub fn generate_key() !(PublicKey, PrivateKey) {
89124 return pub_key, priv_key
90125}
91126
92- // Create a new private key from a seed
93- pub fn new_key_from_seed (seed []u8 ) ! PrivateKey {
94- nid := nid_prime256 v1
127+ // Create a new private key from a seed. If opt was not provided, its default to prime256v1 curve.
128+ pub fn new_key_from_seed (seed []u8 , opt CurveOptions) ! PrivateKey {
95129 // Create a new EC_KEY object with the specified curve
96- ec_key := C. EC_KEY_new_by_curve_name (nid )
130+ ec_key := new_curve (opt )
97131 if ec_key == 0 {
98132 return error ('Failed to create new EC_KEY' )
99133 }
@@ -113,7 +147,16 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
113147 // Now compute the public key
114148 //
115149 // Retrieve the EC_GROUP object associated with the EC_KEY
116- group := C.EC_KEY_get0_group (ec_key)
150+ // Note:
151+ // Its cast-ed with voidptr() to workaround the strictness of the type system,
152+ // ie, cc backend with `-cstrict` option behaviour. Without this cast,
153+ // C.EC_KEY_get0_group expected to return `const EC_GROUP *`,
154+ // ie expected to return pointer into constant of EC_GROUP on C parts,
155+ // so, its make cgen not happy with this and would fail with error.
156+ group := voidptr (C.EC_KEY_get0_group (ec_key))
157+ if group == 0 {
158+ return error ('failed to load group' )
159+ }
117160 // Create a new EC_POINT object for the public key
118161 pub_key_point := C.EC_POINT_new (group)
119162 // Create a new BN_CTX object for efficient BIGNUM operations
@@ -143,6 +186,13 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
143186 C.EC_KEY_free (ec_key)
144187 return error ('Failed to set public key' )
145188 }
189+ // Add key check
190+ // EC_KEY_check_key return 1 on success or 0 on error.
191+ chk := C.EC_KEY_check_key (ec_key)
192+ if chk == 0 {
193+ key_free (ec_key)
194+ return error ('EC_KEY_check_key failed' )
195+ }
146196 C.EC_POINT_free (pub_key_point)
147197 C.BN_free (bn)
148198 return PrivateKey{
@@ -151,6 +201,7 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey {
151201}
152202
153203// Sign a message with private key
204+ // FIXME: should the message should be hashed?
154205pub fn (priv_key PrivateKey) sign (message []u8 ) ! []u8 {
155206 if message.len == 0 {
156207 return error ('Message cannot be null or empty' )
@@ -179,7 +230,7 @@ pub fn (pub_key PublicKey) verify(message []u8, sig []u8) !bool {
179230
180231// Get the seed (private key bytes)
181232pub fn (priv_key PrivateKey) seed () ! []u8 {
182- bn := C.EC_KEY_get0_private_key (priv_key.key)
233+ bn := voidptr ( C.EC_KEY_get0_private_key (priv_key.key) )
183234 if bn == 0 {
184235 return error ('Failed to get private key BIGNUM' )
185236 }
@@ -204,28 +255,189 @@ pub fn (priv_key PrivateKey) public_key() !PublicKey {
204255 }
205256}
206257
207- // Compare two private keys
258+ // EC_GROUP_cmp() for comparing two group (curve).
259+ // EC_GROUP_cmp returns 0 if the curves are equal, 1 if they are not equal, or -1 on error.
260+ fn C.EC_GROUP_cmp (a & C.EC_GROUP, b & C.EC_GROUP, ctx & C.BN_CTX) int
261+
262+ // equal compares two private keys was equal. Its checks for two things, ie:
263+ // - whether both of private keys lives under the same group (curve)
264+ // - compares if two private key bytes was equal
208265pub fn (priv_key PrivateKey) equal (other PrivateKey) bool {
209- bn1 := C.EC_KEY_get0_private_key (priv_key.key)
210- bn2 := C.EC_KEY_get0_private_key (other.key)
211- res := C.BN_cmp (bn1 , bn2 )
212- return res == 0
266+ group1 := voidptr (C.EC_KEY_get0_group (priv_key.key))
267+ group2 := voidptr (C.EC_KEY_get0_group (other.key))
268+ ctx := C.BN_CTX_new ()
269+ if ctx == 0 {
270+ return false
271+ }
272+ defer {
273+ C.BN_CTX_free (ctx)
274+ }
275+ gres := C.EC_GROUP_cmp (group1 , group2 , ctx)
276+ // Its lives on the same group
277+ if gres == 0 {
278+ bn1 := voidptr (C.EC_KEY_get0_private_key (priv_key.key))
279+ bn2 := voidptr (C.EC_KEY_get0_private_key (other.key))
280+ res := C.BN_cmp (bn1 , bn2 )
281+ return res == 0
282+ }
283+ return false
213284}
214285
215286// Compare two public keys
216287pub fn (pub_key PublicKey) equal (other PublicKey) bool {
217- group := C.EC_KEY_get0_group (pub_key.key)
218- point1 := C.EC_KEY_get0_public_key (pub_key.key)
219- point2 := C.EC_KEY_get0_public_key (other.key)
288+ // TODO: check validity of the group
289+ group1 := voidptr (C.EC_KEY_get0_group (pub_key.key))
290+ group2 := voidptr (C.EC_KEY_get0_group (other.key))
291+ if group1 == 0 || group2 == 0 {
292+ return false
293+ }
220294 ctx := C.BN_CTX_new ()
221295 if ctx == 0 {
222296 return false
223297 }
224298 defer {
225299 C.BN_CTX_free (ctx)
226300 }
227- res := C.EC_POINT_cmp (group, point1 , point2 , ctx)
228- return res == 0
301+ gres := C.EC_GROUP_cmp (group1 , group2 , ctx)
302+ // Its lives on the same group
303+ if gres == 0 {
304+ point1 := voidptr (C.EC_KEY_get0_public_key (pub_key.key))
305+ point2 := voidptr (C.EC_KEY_get0_public_key (other.key))
306+ if point1 == 0 || point2 == 0 {
307+ return false
308+ }
309+ res := C.EC_POINT_cmp (group1 , point1 , point2 , ctx)
310+ return res == 0
311+ }
312+
313+ return false
314+ }
315+
316+ // Helpers
317+ //
318+ // new_curve creates a new empty curve based on curve NID, default to prime256v1 (or secp256r1).
319+ fn new_curve (opt CurveOptions) & C.EC_KEY {
320+ mut nid := nid_prime256 v1
321+ match opt.nid {
322+ .prime256 v1 {
323+ // do nothing
324+ }
325+ .secp384 r1 {
326+ nid = nid_secp384 r1
327+ }
328+ .secp521 r1 {
329+ nid = nid_secp521 r1
330+ }
331+ .secp256 k1 {
332+ nid = nid_secp256 k1
333+ }
334+ }
335+ return C.EC_KEY_new_by_curve_name (nid)
336+ }
337+
338+ // Gets recommended hash function of the current PrivateKey.
339+ // Its purposes for hashing message to be signed
340+ fn (pv PrivateKey) recommended_hash () ! crypto.Hash {
341+ group := voidptr (C.EC_KEY_get0_group (pv.key))
342+ if group == 0 {
343+ return error ('Unable to load group' )
344+ }
345+ // gets the bits size of private key group
346+ num_bits := C.EC_GROUP_get_degree (group)
347+ match true {
348+ // use sha256
349+ num_bits < = 256 {
350+ return .sha256
351+ }
352+ num_bits > 256 && num_bits < = 384 {
353+ return .sha384
354+ }
355+ // TODO: what hash should be used if the size is over > 512 bits
356+ num_bits > 384 {
357+ return .sha512
358+ }
359+ else {
360+ return error ('Unsupported bits size' )
361+ }
362+ }
363+ }
364+
365+ pub enum HashConfig {
366+ with_recomended_hash
367+ with_no_hash
368+ with_custom_hash
369+ }
370+
371+ @[params]
372+ pub struct SignerOpts {
373+ pub mut :
374+ hash_config HashConfig = .with_recomended_hash
375+ // make sense when HashConfig != with_recomended_hash
376+ allow_smaller_size bool
377+ allow_custom_hash bool
378+ // set to non-nil if allow_custom_hash was true
379+ custom_hash & hash.Hash = unsafe { nil }
380+ }
381+
382+ // sign_with_options sign the message with the options. By default, it would precompute
383+ // hash value from message, with recommended_hash function, and then sign the hash value.
384+ pub fn (pv PrivateKey) sign_with_options (message []u8 , opts SignerOpts) ! []u8 {
385+ // we're working on mutable copy of SignerOpts, with some issues when make it as a mutable.
386+ // ie, declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed.
387+ mut cfg := opts
388+ match cfg.hash_config {
389+ .with_recomended_hash {
390+ h := pv.recommended_hash ()!
391+ match h {
392+ .sha256 {
393+ digest := sha256 .sum256 (message)
394+ return pv.sign (digest)!
395+ }
396+ .sha384 {
397+ digest := sha512 .sum384 (message)
398+ return pv.sign (digest)!
399+ }
400+ .sha512 {
401+ digest := sha512 .sum512 (message)
402+ return pv.sign (digest)!
403+ }
404+ else {
405+ return error ('Unsupported hash' )
406+ }
407+ }
408+ }
409+ .with_no_hash {
410+ return pv.sign (message)!
411+ }
412+ .with_custom_hash {
413+ if ! cfg.allow_custom_hash {
414+ return error ('custom hash was not allowed, set it into true' )
415+ }
416+ if cfg.custom_hash == unsafe { nil } {
417+ return error ('Custom hasher was not defined' )
418+ }
419+ // check key size bits
420+ group := voidptr (C.EC_KEY_get0_group (pv.key))
421+ if group == 0 {
422+ return error ('fail to load group' )
423+ }
424+ num_bits := C.EC_GROUP_get_degree (group)
425+ // check for key size matching
426+ key_size := (num_bits + 7 ) / 8
427+ // If current Private Key size is bigger then current hash output size,
428+ // by default its not allowed, until set the allow_smaller_size into true
429+ if key_size > cfg.custom_hash.size () {
430+ if ! cfg.allow_smaller_size {
431+ return error ('Hash into smaller size than current key size was not allowed' )
432+ }
433+ }
434+ // otherwise, just hash the message and sign
435+ digest := cfg.custom_hash.sum (message)
436+ defer { unsafe { cfg.custom_hash.free () } }
437+ return pv.sign (digest)!
438+ }
439+ }
440+ return error ('Not should be here' )
229441}
230442
231443// Clear allocated memory for key
0 commit comments