Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto.ecdsa: improvement to safety checking, unified signing (and verifying) api to accept options. #23463

Merged
merged 17 commits into from
Jan 18, 2025
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
crypto.ecdsa: unified signing and verify api, add example to the docs…
… and repo, some bits of rewrites.
blackshirt committed Jan 14, 2025
commit a0639e07408bd9ee43c2fb4c5faaa6dfdabc36ff
22 changes: 21 additions & 1 deletion vlib/crypto/ecdsa/README.md
Original file line number Diff line number Diff line change
@@ -2,7 +2,27 @@

`ecdsa` module for V language. Its a wrapper on top of openssl ecdsa functionality.
Its currently (expanded) to support the following curves:

- NIST P-256 curve, commonly referred as prime256v1 or secp256r1
- NIST P-384 curve, commonly referred as secp384r1
- NIST P-521 curve, commonly referred as secp521r1
- A famous Bitcoin curve, commonly referred as secp256k1
- A famous Bitcoin curve, commonly referred as secp256k1

# Example
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
```codeblock
import crypto.ecdsa
fn main() {
// create default NIST P-256 secp256r1 curve key pair
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// If you wish generate another curve, use following manner (or similar): `pbkey, pvkey := ecdsa.generate_key(nid: .secp521r1)!`
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// create signature with recommended hash
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
signature := pvkey.sign(message_tobe_signed, hash_config: .with_recommended_hash)!
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// verified the message with signature
verified := pbkey.verify(message_tobe_signed, signature, hash_config: .with_recommended_hash)!
dump(verified) // should be true
}
```
153 changes: 105 additions & 48 deletions vlib/crypto/ecdsa/ecdsa.v
Original file line number Diff line number Diff line change
@@ -103,10 +103,12 @@ pub struct PublicKey {
key &C.EC_KEY
}

// Generate a new key pair. If opt was not provided, its default to prime256v1 curve.
// generate_key generates a new key pair. If opt was not provided, its default to prime256v1 curve.
// If you want another curve, use in the following manner: `pubkey, pivkey := ecdsa.generate_key(nid: .secp384r1)!`
pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
res := C.EC_KEY_generate_key(ec_key)
@@ -124,13 +126,47 @@ pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
return pub_key, priv_key
}

// Create a new private key from a seed. If opt was not provided, its default to prime256v1 curve.
// new_key_from_seed creates a new private key from the seed bytes. If opt was not provided,
// its default to prime256v1 curve.
//
// Notes on the seed:
// You should make sure, the seed bytes was comes from cryptographically secure random generators,
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// likes the `crypto.rand` or other trusted sources.
// Internally, the seed size's would be checked to not exceed the key size of underlying curve,
// ie, 32 bytes length for p-256 and secp256k1, 48 bytes length for p-384 and 64 bytes length for p-521.
// Its recommended to use seed with bytes length matching with underlying curve key size.
pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
// Early exit check
if seed.len == 0 {
return error('Seed with null-length was not allowed')
}
// Create a new EC_KEY object with the specified curve
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
// Retrieve the EC_GROUP object associated with the EC_KEY
// Note:
// Its cast-ed with voidptr() to workaround the strictness of the type system,
// ie, cc backend with `-cstrict` option behaviour. Without this cast,
// C.EC_KEY_get0_group expected to return `const EC_GROUP *`,
// ie expected to return pointer into constant of EC_GROUP on C parts,
// so, its make cgen not happy with this and would fail with error.
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
group := voidptr(C.EC_KEY_get0_group(ec_key))
if group == 0 {
C.EC_KEY_free(ec_key)
return error('Unable to load group')
}
// Adds early check for upper size, so, we dont hit unnecessary
// call to math intensive calculation, conversion and checking routines.
num_bits := C.EC_GROUP_get_degree(group)
key_size := (num_bits + 7) / 8
if seed.len > key_size {
C.EC_KEY_free(ec_key)
return error('Seed length exceeds key size')
}

// Convert the seed bytes into a BIGNUM
bn := C.BN_bin2bn(seed.data, seed.len, 0)
if bn == 0 {
@@ -146,17 +182,6 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
}
// Now compute the public key
//
// Retrieve the EC_GROUP object associated with the EC_KEY
// Note:
// Its cast-ed with voidptr() to workaround the strictness of the type system,
// ie, cc backend with `-cstrict` option behaviour. Without this cast,
// C.EC_KEY_get0_group expected to return `const EC_GROUP *`,
// ie expected to return pointer into constant of EC_GROUP on C parts,
// so, its make cgen not happy with this and would fail with error.
group := voidptr(C.EC_KEY_get0_group(ec_key))
if group == 0 {
return error('failed to load group')
}
// Create a new EC_POINT object for the public key
pub_key_point := C.EC_POINT_new(group)
// Create a new BN_CTX object for efficient BIGNUM operations
@@ -200,9 +225,18 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
}
}

// Sign a message with private key
// FIXME: should the message should be hashed?
pub fn (priv_key PrivateKey) sign(message []u8) ![]u8 {
// sign performs signing the message with the options. By default, the options was using `.with_no_hash` options,
// so, its would not precompute the the digest (hash) of message before signing.
// If you wish to use with recommended hash function, you should pass `hash_config: .with_recommended_hash`
// to precompute the digest and and then sign the hash value.
// TODO: maybe changed to use .with_recommended_hash in the future to align with recommended way for signing.
pub fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8 {
digest := calc_digest(pv.key, message, opt)!
return pv.sign_message(digest)!
}

// sign_message sign a message with private key
fn (priv_key PrivateKey) sign_message(message []u8) ![]u8 {
if message.len == 0 {
return error('Message cannot be null or empty')
}
@@ -219,22 +253,31 @@ pub fn (priv_key PrivateKey) sign(message []u8) ![]u8 {
return signed_data.clone()
}

// Verify a signature with public key
pub fn (pub_key PublicKey) verify(message []u8, sig []u8) !bool {
res := C.ECDSA_verify(0, message.data, message.len, sig.data, sig.len, pub_key.key)
// verify verifies a message with the signature are valid with public key provided .
// You should provide it with the same SignerOpts used with the `.sign()` call.
// or verify would fail (false).
pub fn (pub_key PublicKey) verify(message []u8, sig []u8, opt SignerOpts) !bool {
digest := calc_digest(pub_key.key, message, opt)!
res := C.ECDSA_verify(0, digest.data, digest.len, sig.data, sig.len, pub_key.key)
if res == -1 {
return error('Failed to verify signature')
}
return res == 1
}

// Get the seed (private key bytes)
// seed gets the seed (private key bytes).
// Notes:
// Generally, if the private key generated from the seed with `new_key_from_seed(seed)`
// the call to `.seed()` would produce original seed bytes except your original seed bytes
// contains leading zeros bytes, internally its would be chopped off by underlying wrapper,
// so, its would produces unmatching length with the original seed.
pub fn (priv_key PrivateKey) seed() ![]u8 {
bn := voidptr(C.EC_KEY_get0_private_key(priv_key.key))
if bn == 0 {
return error('Failed to get private key BIGNUM')
}
num_bytes := (C.BN_num_bits(bn) + 7) / 8

mut buf := []u8{len: int(num_bytes)}
res := C.BN_bn2bin(bn, buf.data)
if res == 0 {
@@ -260,8 +303,9 @@ pub fn (priv_key PrivateKey) public_key() !PublicKey {
fn C.EC_GROUP_cmp(a &C.EC_GROUP, b &C.EC_GROUP, ctx &C.BN_CTX) int

// equal compares two private keys was equal. Its checks for two things, ie:
// - whether both of private keys lives under the same group (curve)
// - compares if two private key bytes was equal
//
// - whether both of private keys lives under the same group (curve),
// - compares if two private key bytes was equal.
pub fn (priv_key PrivateKey) equal(other PrivateKey) bool {
group1 := voidptr(C.EC_KEY_get0_group(priv_key.key))
group2 := voidptr(C.EC_KEY_get0_group(other.key))
@@ -335,10 +379,10 @@ fn new_curve(opt CurveOptions) &C.EC_KEY {
return C.EC_KEY_new_by_curve_name(nid)
}

// Gets recommended hash function of the current PrivateKey.
// Its purposes for hashing message to be signed
fn (pv PrivateKey) recommended_hash() !crypto.Hash {
group := voidptr(C.EC_KEY_get0_group(pv.key))
// Gets recommended hash function of the key.
// Its purposes for hashing message to be signed.
fn recommended_hash(key &C.EC_KEY) !crypto.Hash {
group := voidptr(C.EC_KEY_get0_group(key))
if group == 0 {
return error('Unable to load group')
}
@@ -363,52 +407,54 @@ fn (pv PrivateKey) recommended_hash() !crypto.Hash {
}

pub enum HashConfig {
with_recomended_hash
with_recommended_hash
with_no_hash
with_custom_hash
}

@[params]
pub struct SignerOpts {
pub mut:
hash_config HashConfig = .with_recomended_hash
// make sense when HashConfig != with_recomended_hash
// default to .with_no_hash to align with the current behaviour of signing.
hash_config HashConfig = .with_no_hash
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// make sense when HashConfig != with_recommended_hash
allow_smaller_size bool
allow_custom_hash bool
// set to non-nil if allow_custom_hash was true
custom_hash &hash.Hash = unsafe { nil }
}

// sign_with_options sign the message with the options. By default, it would precompute
// hash value from message, with recommended_hash function, and then sign the hash value.
pub fn (pv PrivateKey) sign_with_options(message []u8, opts SignerOpts) ![]u8 {
spytheman marked this conversation as resolved.
Show resolved Hide resolved
// calc_digest tries to calculates digest (hash) of the message based on options provided.
// If the options was with_no_hash, its return default message without hashing.
fn calc_digest(key &C.EC_KEY, message []u8, opt SignerOpts) ![]u8 {
if message.len == 0 {
return error('null-length messages')
}
// we're working on mutable copy of SignerOpts, with some issues when make it as a mutable.
// ie, declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed.
mut cfg := opts
mut cfg := opt
match cfg.hash_config {
.with_recomended_hash {
h := pv.recommended_hash()!
.with_no_hash {
// return original message
return message
}
.with_recommended_hash {
h := recommended_hash(key)!
match h {
.sha256 {
digest := sha256.sum256(message)
return pv.sign(digest)!
return sha256.sum256(message)
}
.sha384 {
digest := sha512.sum384(message)
return pv.sign(digest)!
return sha512.sum384(message)
}
.sha512 {
digest := sha512.sum512(message)
return pv.sign(digest)!
return sha512.sum512(message)
}
else {
return error('Unsupported hash')
}
}
}
.with_no_hash {
return pv.sign(message)!
}
.with_custom_hash {
if !cfg.allow_custom_hash {
return error('custom hash was not allowed, set it into true')
@@ -417,7 +463,7 @@ pub fn (pv PrivateKey) sign_with_options(message []u8, opts SignerOpts) ![]u8 {
return error('Custom hasher was not defined')
}
// check key size bits
group := voidptr(C.EC_KEY_get0_group(pv.key))
group := voidptr(C.EC_KEY_get0_group(key))
if group == 0 {
return error('fail to load group')
}
@@ -431,16 +477,27 @@ pub fn (pv PrivateKey) sign_with_options(message []u8, opts SignerOpts) ![]u8 {
return error('Hash into smaller size than current key size was not allowed')
}
}
// otherwise, just hash the message and sign
digest := cfg.custom_hash.sum(message)
defer { unsafe { cfg.custom_hash.free() } }
return pv.sign(digest)!
return digest
}
}
return error('Not should be here')
}

// Clear allocated memory for key
pub fn key_free(ec_key &C.EC_KEY) {
fn key_free(ec_key &C.EC_KEY) {
C.EC_KEY_free(ec_key)
}

// free clears out allocated memory for PublicKey.
// Dont use PublicKey after calling `.free()`
pub fn (pb &PublicKey) free() {
C.EC_KEY_free(pb.key)
}

// free clears out allocated memory for PrivateKey
// Dont use PrivateKey after calling `.free()`
pub fn (pv &PrivateKey) free() {
C.EC_KEY_free(pv.key)
}
12 changes: 5 additions & 7 deletions vlib/crypto/ecdsa/ecdsa_test.v
Original file line number Diff line number Diff line change
@@ -15,21 +15,19 @@ fn test_ecdsa() {
key_free(priv_key.key)
}

// This test should exactly has the same behaviour with default sign(message),
// because we passed .with_no_hash flag as an option.
fn test_ecdsa_signing_with_options() {
fn test_ecdsa_signing_with_recommended_hash_options() {
// Generate key pair
pub_key, priv_key := generate_key() or { panic(err) }

// Sign a message
message := 'Hello, ECDSA!'.bytes()
opts := SignerOpts{
hash_config: .with_no_hash
opt := SignerOpts{
hash_config: .with_recommended_hash
}
signature := priv_key.sign_with_options(message, opts) or { panic(err) }
signature := priv_key.sign(message, opt) or { panic(err) }

// Verify the signature
is_valid := pub_key.verify(message, signature) or { panic(err) }
is_valid := pub_key.verify(message, signature, opt) or { panic(err) }
println('Signature valid: ${is_valid}')
key_free(pub_key.key)
assert is_valid
32 changes: 32 additions & 0 deletions vlib/crypto/ecdsa/example/ecdsa_seed_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import rand
import crypto.ecdsa

// The test file placed on its own directory. Its for workaround for
// module lookup problem, because there are two rand module availables,
// between `crypto.rand` and `rand` module.
// See [the talk](https://discord.com/channels/592103645835821068/592294828432424960/1328198034806407311) on discord.
fn test_new_key_from_seed_with_random_size_and_data() ! {
num_iters := 100
// default prime256v1 curve key size was 32 bytes.
max_key_size := i32(48)
for i := 0; i <= num_iters; i++ {
m := rand.i32n(max_key_size)!
random_bytes := rand.bytes(m)!
pvkey := ecdsa.new_key_from_seed(random_bytes) or {
// With default size, would error on m > 32 or m == 0
// dump(m)
if m == 0 {
assert err == error('Seed with null-length was not allowed')
} else if m > 32 {
assert err == error('Seed length exceeds key size')
} else {
assert err == error('EC_KEY_check_key failed')
}
continue
}
ret_seed := pvkey.seed()!
// this test would fail if random_bytes was leading by zeros bytes.
assert random_bytes == ret_seed
pvkey.free()
}
}
14 changes: 14 additions & 0 deletions vlib/crypto/ecdsa/example/example1.v
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import crypto.ecdsa

fn main() {
// create secp256r1, NIST P-256 curve key pair
pbkey, pvkey := ecdsa.generate_key()!

message_tobe_signed := 'Hello ecdsa'.bytes()
// create signature with recommended hash
signature := pvkey.sign(message_tobe_signed, hash_config: .with_recommended_hash)!

// verified the message with signature
verified := pbkey.verify(message_tobe_signed, signature, hash_config: .with_recommended_hash)!
dump(verified) // should true
}
92 changes: 88 additions & 4 deletions vlib/crypto/ecdsa/util.v
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ module ecdsa
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/pem.h>

// #define NID_X9_62_id_ecPublicKey 408
const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey
@@ -49,6 +51,7 @@ fn C.EC_GROUP_free(group &C.EC_GROUP)
// Examples:
// ```codeblock
// import crypto.pem
// import crypto.ecdsa
//
// const pubkey_sample = '-----BEGIN PUBLIC KEY-----
// MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
@@ -57,7 +60,7 @@ fn C.EC_GROUP_free(group &C.EC_GROUP)
// -----END PUBLIC KEY-----'
//
// block, _ := pem.decode(pubkey_sample) or { panic(err) }
// pubkey := pubkey_from_bytes(block.data)!
// pubkey := ecdsa.pubkey_from_bytes(block.data)!
// ```
pub fn pubkey_from_bytes(bytes []u8) !PublicKey {
if bytes.len == 0 {
@@ -79,7 +82,7 @@ pub fn pubkey_from_bytes(bytes []u8) !PublicKey {

eckey := C.EVP_PKEY_get1_EC_KEY(pub_key)
if eckey == 0 {
key_free(eckey)
C.EC_KEY_free(eckey)
return error('Failed to get ec key')
}
// check the group for the supported curve(s)
@@ -100,7 +103,7 @@ pub fn pubkey_from_bytes(bytes []u8) !PublicKey {
}
}

// bytes gets the bytes of public key parts of this keypair.
// bytes gets the bytes of public key parts of this key.
pub fn (pbk PublicKey) bytes() ![]u8 {
point := voidptr(C.EC_KEY_get0_public_key(pbk.key))
if point == 0 {
@@ -126,6 +129,7 @@ pub fn (pbk PublicKey) bytes() ![]u8 {
mut buf := []u8{len: num_bytes}

// Get conversion format.
//
// The uncompressed form is indicated by 0x04 and the compressed form is indicated
// by either 0x02 or 0x03, hybrid 0x06
// The public key MUST be rejected if any other value is included in the first octet.
@@ -134,7 +138,87 @@ pub fn (pbk PublicKey) bytes() ![]u8 {
return error('bad conversion format')
}
n := C.EC_POINT_point2oct(group, point, conv_form, buf.data, buf.len, ctx)

if n == 0 {
return error('Fails on EC_POINT_point2oct')
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
}
// returns the clone of the buffer[..n]
return buf[..n].clone()
}

@[typedef]
struct C.BIO_METHOD {}

@[typedef]
struct C.BIO {}

// BIO * BIO_new(BIO_METHOD *type);
fn C.BIO_new(t &C.BIO_METHOD) &C.BIO

// void BIO_free_all(BIO *a);
fn C.BIO_free_all(a &C.BIO)

// BIO_METHOD * BIO_s_mem(void);
fn C.BIO_s_mem() &C.BIO_METHOD

// int BIO_write(BIO *b, const void *buf, int len);
fn C.BIO_write(b &C.BIO, buf &u8, length int) int

// EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, void *u);
fn C.PEM_read_bio_PrivateKey(bp &C.BIO, x &&C.EVP_PKEY, cb int, u &voidptr) &C.EVP_PKEY

// load_privkey_from_string loads PrivateKey from valid PEM-formatted string in s.
// Underlying wrapper support for old secg and pkcs8 private key format, but this not heavily tested.
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// This routine also does not handling for pkcs8 EncryptedPrivateKeyInfo format, the callback was not handled.
pub fn load_privkey_from_string(s string) !PrivateKey {
if s.len == 0 {
return error('null string was not allowed')
}
//
mut key := C.EVP_PKEY_new()
bo := C.BIO_new(C.BIO_s_mem())
if bo == 0 {
return error('Failed to create BIO_new')
}
n := C.BIO_write(bo, s.str, s.len)
if n == 0 {
// todo: retry the write
}
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
defer { C.BIO_free_all(bo) }
key = C.PEM_read_bio_PrivateKey(bo, &key, 0, 0)

if key == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(key)
return error('Error loading key')
}
// Get the NID of this key, and check if the key object was
// have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey
nid := C.EVP_PKEY_base_id(key)
if nid != nid_ec_publickey {
C.EVP_PKEY_free(key)
return error('Get an nid of non ecPublicKey')
}

eckey := C.EVP_PKEY_get1_EC_KEY(key)
if eckey == 0 {
C.EC_KEY_free(eckey)
return error('Failed to get ec key')
}
// check the group for the supported curve(s)
group := voidptr(C.EC_KEY_get0_group(eckey))
if group == 0 {
C.EC_GROUP_free(group)
return error('Failed to load group from key')
}
nidgroup := C.EC_GROUP_get_curve_name(group)
if nidgroup != nid_prime256v1 && nidgroup != nid_secp384r1 && nidgroup != nid_secp521r1
&& nidgroup != nid_secp256k1 {
C.EC_GROUP_free(group)
C.EC_KEY_free(eckey)
return error('Unsupported group')
}
// Its OK to return
return PrivateKey{
key: eckey
}
}
68 changes: 65 additions & 3 deletions vlib/crypto/ecdsa/util_test.v
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ module ecdsa

import encoding.hex
import crypto.pem
import crypto.sha512

// This material wss generated with https://emn178.github.io/online-tools/ecdsa/key-generator
// with curve SECG secp384r1 aka NIST P-384
@@ -31,8 +30,10 @@ fn test_load_pubkey_from_der_serialized_bytes() ! {
status_without_hashed := pbkey.verify(message_tobe_signed.bytes(), expected_signature)!
assert status_without_hashed == false

hashed_msg := sha512.sum384(message_tobe_signed.bytes())
status_with_hashed := pbkey.verify(hashed_msg, expected_signature)!
// expected signature was comes from hashed message with sha384
status_with_hashed := pbkey.verify(message_tobe_signed.bytes(), expected_signature,
hash_config: .with_recommended_hash
)!
assert status_with_hashed == true
key_free(pbkey.key)
}
@@ -49,3 +50,64 @@ fn test_for_pubkey_bytes() ! {
key_free(pbkey.key)
key_free(pvkey.key)
}

// above pem-formatted private key read with
// `$openssl ec -in vlib/crypto/ecdsa/example.pem -text -param_out -check`
// produces following result:
// ```codeblock
blackshirt marked this conversation as resolved.
Show resolved Hide resolved
// read EC key
// Private-Key: (384 bit)
// priv:
// 30:ce:3d:a2:88:96:5a:c6:09:3f:0b:a9:a9:a1:5b:
// 24:76:be:a3:ed:a9:25:e1:b3:c1:f0:94:67:4f:52:
// 79:5c:d6:cb:3c:af:e2:35:df:c1:5b:ec:54:24:48:
// ff:a7:15
// pub:
// 04:f8:fd:eb:84:59:13:d5:f5:c7:61:b6:37:0a:97:
// 01:76:1e:b1:4c:2e:f8:31:01:71:71:fb:4d:54:3d:
// f3:10:f5:73:a3:ff:ce:6a:52:15:6a:d6:35:eb:69:
// 2c:83:cb:91:58:17:6f:14:5b:87:67:d3:8c:55:79:
// 1b:8f:05:b0:85:c5:cf:ee:a9:88:f6:b3:92:2d:1d:
// c0:19:49:5d:cb:e6:c1:a5:5d:a7:4f:7d:3a:a8:22:
// 30:81:40:0c:78:e2:4f
// ASN1 OID: secp384r1
// NIST CURVE: P-384
// EC Key valid.
// writing EC key
// -----BEGIN EC PRIVATE KEY-----
// MIGkAgEBBDAwzj2iiJZaxgk/C6mpoVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/B
// W+xUJEj/pxWgBwYFK4EEACKhZANiAAT4/euEWRPV9cdhtjcKlwF2HrFMLvgxAXFx
// +01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn04xVeRuPBbCFxc/uqYj2s5It
// HcAZSV3L5sGlXadPfTqoIjCBQAx44k8=
// -----END EC PRIVATE KEY-----
// ```
fn test_load_privkey_from_string_sign_and_verify() ! {
pvkey := load_privkey_from_string(privatekey_sample)!
expected_pvkey_bytes := '30ce3da288965ac6093f0ba9a9a15b2476bea3eda925e1b3c1f094674f52795cd6cb3cafe235dfc15bec542448ffa715'
assert pvkey.seed()!.hex() == expected_pvkey_bytes

// public key part
pbkey := pvkey.public_key()!
pbkey_bytes := pbkey.bytes()!
expected_pubkey_bytes := '04f8fdeb845913d5f5c761b6370a9701761eb14c2ef831017171fb4d543df310f573a3ffce6a52156ad635eb692c83cb9158176f145b8767d38c55791b8f05b085c5cfeea988f6b3922d1dc019495dcbe6c1a55da74f7d3aa8223081400c78e24f'
assert pbkey_bytes.hex() == expected_pubkey_bytes

// lets sign the message with default hash, ie, sha384
signature := pvkey.sign(message_tobe_signed.bytes())!

verified := pbkey.verify(message_tobe_signed.bytes(), signature)!
assert verified == true
}

// test for loading privat key from unsupported curve should fail.
fn test_load_privkey_from_string_with_unsupported_curve() ! {
// generated with openssl ecparam -name secp192k1 -genkey -noout -out key.pem
key := '-----BEGIN EC PRIVATE KEY-----
MFwCAQEEGDHV+WhJL2UjUhgMLh52k0RJjRebtu4HvqAHBgUrgQQAH6E0AzIABFyF
UHhnmmVRraSwrVkPdYIeXhH/Ob4+8OLcwrQBMv4RXsD1GVFsgkvEYDTEb/vnMA==
-----END EC PRIVATE KEY-----'
_ := load_privkey_from_string(key) or {
assert err == error('Unsupported group')
return
}
}