From 68c2da2560f0219d6eb7391637b165e77b9e4c21 Mon Sep 17 00:00:00 2001 From: blackshirt Date: Sun, 12 Jan 2025 17:19:05 +0700 Subject: [PATCH] crypto.ecdsa: expand ecdsa module, to support other curves like secp384r1, secp521r1, secp256k1 (#23407) --- cmd/tools/modules/testing/common.v | 7 +- vlib/crypto/ecdsa/README.md | 8 + vlib/crypto/ecdsa/ecdsa.v | 252 ++++++++++++++++++++++++++--- vlib/crypto/ecdsa/ecdsa_test.v | 28 ++++ vlib/crypto/ecdsa/util.v | 139 ++++++++++++++++ vlib/crypto/ecdsa/util_test.v | 48 ++++++ 6 files changed, 460 insertions(+), 22 deletions(-) create mode 100644 vlib/crypto/ecdsa/README.md create mode 100644 vlib/crypto/ecdsa/util.v create mode 100644 vlib/crypto/ecdsa/util_test.v diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index c4420fc4cd9060..25b6f6152fa382 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -250,6 +250,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { skip_files << 'examples/websocket/client-server/server.v' // requires OpenSSL skip_files << 'vlib/v/tests/websocket_logger_interface_should_compile_test.v' // requires OpenSSL skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL + skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL $if tinyc { skip_files << 'examples/database/orm.v' // try fix it } @@ -280,13 +281,15 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { } if github_job == 'docker-ubuntu-musl' { skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v' - skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' + skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL + skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL skip_files << 'vlib/x/ttf/ttf_test.v' skip_files << 'vlib/encoding/iconv/iconv_test.v' // needs libiconv to be installed } if github_job == 'tests-sanitize-memory-clang' { skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v' - skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' + skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL + skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL // Fails compilation with: `/usr/bin/ld: /lib/x86_64-linux-gnu/libpthread.so.0: error adding symbols: DSO missing from command line` skip_files << 'examples/sokol/sounds/simple_sin_tones.v' } diff --git a/vlib/crypto/ecdsa/README.md b/vlib/crypto/ecdsa/README.md new file mode 100644 index 00000000000000..870c6d79992dc9 --- /dev/null +++ b/vlib/crypto/ecdsa/README.md @@ -0,0 +1,8 @@ +## ecdsa + +`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 \ No newline at end of file diff --git a/vlib/crypto/ecdsa/ecdsa.v b/vlib/crypto/ecdsa/ecdsa.v index ae90980c090c8b..32b7c9e0256b9c 100644 --- a/vlib/crypto/ecdsa/ecdsa.v +++ b/vlib/crypto/ecdsa/ecdsa.v @@ -3,6 +3,11 @@ // that can be found in the LICENSE file. module ecdsa +import hash +import crypto +import crypto.sha256 +import crypto.sha512 + #flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include #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 fn C.BN_CTX_new() &C.BN_CTX fn C.BN_CTX_free(ctx &C.BN_CTX) +// for checking the key +fn C.EC_KEY_check_key(key &C.EC_KEY) int + // NID constants +// +// NIST P-256 is refered to as secp256r1 and prime256v1, defined as #define NID_X9_62_prime256v1 415 +// Different names, but they are all the same. +// https://www.rfc-editor.org/rfc/rfc4492.html#appendix-A const nid_prime256v1 = C.NID_X9_62_prime256v1 +// NIST P-384, ie, secp384r1 curve, defined as #define NID_secp384r1 715 +const nid_secp384r1 = C.NID_secp384r1 + +// NIST P-521, ie, secp521r1 curve, defined as #define NID_secp521r1 716 +const nid_secp521r1 = C.NID_secp521r1 + +// Bitcoin curve, defined as #define NID_secp256k1 714 +const nid_secp256k1 = C.NID_secp256k1 + +// The list of supported curve(s) +pub enum Nid { + prime256v1 + secp384r1 + secp521r1 + secp256k1 +} + +@[params] +pub struct CurveOptions { +pub mut: + nid Nid = .prime256v1 // default to NIST P-256 curve +} + @[typedef] struct C.EC_KEY {} @@ -68,10 +103,9 @@ pub struct PublicKey { key &C.EC_KEY } -// Generate a new key pair -pub fn generate_key() !(PublicKey, PrivateKey) { - nid := nid_prime256v1 // Using NIST P-256 curve - ec_key := C.EC_KEY_new_by_curve_name(nid) +// Generate a new key pair. If opt was not provided, its default to prime256v1 curve. +pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) { + ec_key := new_curve(opt) if ec_key == 0 { return error('Failed to create new EC_KEY') } @@ -80,6 +114,7 @@ pub fn generate_key() !(PublicKey, PrivateKey) { C.EC_KEY_free(ec_key) return error('Failed to generate EC_KEY') } + priv_key := PrivateKey{ key: ec_key } @@ -89,11 +124,10 @@ pub fn generate_key() !(PublicKey, PrivateKey) { return pub_key, priv_key } -// Create a new private key from a seed -pub fn new_key_from_seed(seed []u8) !PrivateKey { - nid := nid_prime256v1 +// Create a new private key from a seed. If opt was not provided, its default to prime256v1 curve. +pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey { // Create a new EC_KEY object with the specified curve - ec_key := C.EC_KEY_new_by_curve_name(nid) + ec_key := new_curve(opt) if ec_key == 0 { return error('Failed to create new EC_KEY') } @@ -113,7 +147,16 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey { // Now compute the public key // // Retrieve the EC_GROUP object associated with the EC_KEY - group := C.EC_KEY_get0_group(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 @@ -143,6 +186,13 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey { C.EC_KEY_free(ec_key) return error('Failed to set public key') } + // Add key check + // EC_KEY_check_key return 1 on success or 0 on error. + chk := C.EC_KEY_check_key(ec_key) + if chk == 0 { + key_free(ec_key) + return error('EC_KEY_check_key failed') + } C.EC_POINT_free(pub_key_point) C.BN_free(bn) return PrivateKey{ @@ -151,6 +201,7 @@ pub fn new_key_from_seed(seed []u8) !PrivateKey { } // Sign a message with private key +// FIXME: should the message should be hashed? pub fn (priv_key PrivateKey) sign(message []u8) ![]u8 { if message.len == 0 { return error('Message cannot be null or empty') @@ -179,7 +230,7 @@ pub fn (pub_key PublicKey) verify(message []u8, sig []u8) !bool { // Get the seed (private key bytes) pub fn (priv_key PrivateKey) seed() ![]u8 { - bn := C.EC_KEY_get0_private_key(priv_key.key) + bn := voidptr(C.EC_KEY_get0_private_key(priv_key.key)) if bn == 0 { return error('Failed to get private key BIGNUM') } @@ -204,19 +255,42 @@ pub fn (priv_key PrivateKey) public_key() !PublicKey { } } -// Compare two private keys +// EC_GROUP_cmp() for comparing two group (curve). +// EC_GROUP_cmp returns 0 if the curves are equal, 1 if they are not equal, or -1 on error. +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 pub fn (priv_key PrivateKey) equal(other PrivateKey) bool { - bn1 := C.EC_KEY_get0_private_key(priv_key.key) - bn2 := C.EC_KEY_get0_private_key(other.key) - res := C.BN_cmp(bn1, bn2) - return res == 0 + group1 := voidptr(C.EC_KEY_get0_group(priv_key.key)) + group2 := voidptr(C.EC_KEY_get0_group(other.key)) + ctx := C.BN_CTX_new() + if ctx == 0 { + return false + } + defer { + C.BN_CTX_free(ctx) + } + gres := C.EC_GROUP_cmp(group1, group2, ctx) + // Its lives on the same group + if gres == 0 { + bn1 := voidptr(C.EC_KEY_get0_private_key(priv_key.key)) + bn2 := voidptr(C.EC_KEY_get0_private_key(other.key)) + res := C.BN_cmp(bn1, bn2) + return res == 0 + } + return false } // Compare two public keys pub fn (pub_key PublicKey) equal(other PublicKey) bool { - group := C.EC_KEY_get0_group(pub_key.key) - point1 := C.EC_KEY_get0_public_key(pub_key.key) - point2 := C.EC_KEY_get0_public_key(other.key) + // TODO: check validity of the group + group1 := voidptr(C.EC_KEY_get0_group(pub_key.key)) + group2 := voidptr(C.EC_KEY_get0_group(other.key)) + if group1 == 0 || group2 == 0 { + return false + } ctx := C.BN_CTX_new() if ctx == 0 { return false @@ -224,8 +298,146 @@ pub fn (pub_key PublicKey) equal(other PublicKey) bool { defer { C.BN_CTX_free(ctx) } - res := C.EC_POINT_cmp(group, point1, point2, ctx) - return res == 0 + gres := C.EC_GROUP_cmp(group1, group2, ctx) + // Its lives on the same group + if gres == 0 { + point1 := voidptr(C.EC_KEY_get0_public_key(pub_key.key)) + point2 := voidptr(C.EC_KEY_get0_public_key(other.key)) + if point1 == 0 || point2 == 0 { + return false + } + res := C.EC_POINT_cmp(group1, point1, point2, ctx) + return res == 0 + } + + return false +} + +// Helpers +// +// new_curve creates a new empty curve based on curve NID, default to prime256v1 (or secp256r1). +fn new_curve(opt CurveOptions) &C.EC_KEY { + mut nid := nid_prime256v1 + match opt.nid { + .prime256v1 { + // do nothing + } + .secp384r1 { + nid = nid_secp384r1 + } + .secp521r1 { + nid = nid_secp521r1 + } + .secp256k1 { + nid = nid_secp256k1 + } + } + 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)) + if group == 0 { + return error('Unable to load group') + } + // gets the bits size of private key group + num_bits := C.EC_GROUP_get_degree(group) + match true { + // use sha256 + num_bits <= 256 { + return .sha256 + } + num_bits > 256 && num_bits <= 384 { + return .sha384 + } + // TODO: what hash should be used if the size is over > 512 bits + num_bits > 384 { + return .sha512 + } + else { + return error('Unsupported bits size') + } + } +} + +pub enum HashConfig { + with_recomended_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 + 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 { + // 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 + match cfg.hash_config { + .with_recomended_hash { + h := pv.recommended_hash()! + match h { + .sha256 { + digest := sha256.sum256(message) + return pv.sign(digest)! + } + .sha384 { + digest := sha512.sum384(message) + return pv.sign(digest)! + } + .sha512 { + digest := sha512.sum512(message) + return pv.sign(digest)! + } + 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') + } + if cfg.custom_hash == unsafe { nil } { + return error('Custom hasher was not defined') + } + // check key size bits + group := voidptr(C.EC_KEY_get0_group(pv.key)) + if group == 0 { + return error('fail to load group') + } + num_bits := C.EC_GROUP_get_degree(group) + // check for key size matching + key_size := (num_bits + 7) / 8 + // If current Private Key size is bigger then current hash output size, + // by default its not allowed, until set the allow_smaller_size into true + if key_size > cfg.custom_hash.size() { + if !cfg.allow_smaller_size { + 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 error('Not should be here') } // Clear allocated memory for key diff --git a/vlib/crypto/ecdsa/ecdsa_test.v b/vlib/crypto/ecdsa/ecdsa_test.v index 18e3d7ab3cf214..07b24ab8f841c4 100644 --- a/vlib/crypto/ecdsa/ecdsa_test.v +++ b/vlib/crypto/ecdsa/ecdsa_test.v @@ -15,6 +15,25 @@ 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() { + // 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 + } + signature := priv_key.sign_with_options(message, opts) or { panic(err) } + + // Verify the signature + is_valid := pub_key.verify(message, signature) or { panic(err) } + println('Signature valid: ${is_valid}') + assert is_valid +} + fn test_generate_key() ! { // Test key generation pub_key, priv_key := generate_key() or { panic(err) } @@ -71,6 +90,15 @@ fn test_private_key_equal() ! { key_free(priv_key2.key) } +fn test_private_key_equality_on_different_curve() ! { + // default group + _, priv_key1 := generate_key() or { panic(err) } + seed := priv_key1.seed() or { panic(err) } + // using different group + priv_key2 := new_key_from_seed(seed, nid: .secp384r1) or { panic(err) } + assert !priv_key1.equal(priv_key2) +} + fn test_public_key_equal() ! { // Test public key equality _, priv_key := generate_key() or { panic(err) } diff --git a/vlib/crypto/ecdsa/util.v b/vlib/crypto/ecdsa/util.v new file mode 100644 index 00000000000000..cd5bff006e1bdc --- /dev/null +++ b/vlib/crypto/ecdsa/util.v @@ -0,0 +1,139 @@ +module ecdsa + +#include +#include +#include + +// #define NID_X9_62_id_ecPublicKey 408 +const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey + +@[typedef] +struct C.EVP_PKEY {} + +// EVP_PKEY *EVP_PKEY_new(void); +fn C.EVP_PKEY_new() &C.EVP_PKEY + +// EVP_PKEY_free(EVP_PKEY *key); +fn C.EVP_PKEY_free(key &C.EVP_PKEY) + +// EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *pkey); +fn C.EVP_PKEY_get1_EC_KEY(pkey &C.EVP_PKEY) &C.EC_KEY + +// EVP_PKEY *d2i_PUBKEY(EVP_PKEY **a, const unsigned char **pp, long length); +fn C.d2i_PUBKEY(k &&C.EVP_PKEY, pp &&u8, length u32) &C.EVP_PKEY + +// point_conversion_form_t EC_KEY_get_conv_form(const EC_KEY *key); +fn C.EC_KEY_get_conv_form(k &C.EC_KEY) int + +// EC_GROUP_get_degree +fn C.EC_GROUP_get_degree(g &C.EC_GROUP) int + +// const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *key); +fn C.EC_KEY_get0_public_key(key &C.EC_KEY) &C.EC_POINT + +// size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *point, point_conversion_form_t form, uint8_t *buf, size_t max_out, BN_CTX *ctx); +fn C.EC_POINT_point2oct(g &C.EC_GROUP, p &C.EC_POINT, form int, buf &u8, max_out int, ctx &C.BN_CTX) int + +// int EVP_PKEY_get_base_id(const EVP_PKEY *pkey); +fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) int + +// int EC_GROUP_get_curve_name(const EC_GROUP *group); +fn C.EC_GROUP_get_curve_name(g &C.EC_GROUP) int +fn C.EC_GROUP_free(group &C.EC_GROUP) + +// pubkey_from_bytes loads ECDSA Public Key from bytes array. +// The bytes of data should be a valid of ASN.1 DER serialized SubjectPublicKeyInfo structrue of RFC 5480. +// Otherwise, its should an error. +// Typically, you can load the bytes from pem formatted of ecdsa public key. +// +// Examples: +// ```codeblock +// import crypto.pem +// +// const pubkey_sample = '-----BEGIN PUBLIC KEY----- +// MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx +// cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS +// LR3AGUldy+bBpV2nT306qCIwgUAMeOJP +// -----END PUBLIC KEY-----' +// +// block, _ := pem.decode(pubkey_sample) or { panic(err) } +// pubkey := pubkey_from_bytes(block.data)! +// ``` +pub fn pubkey_from_bytes(bytes []u8) !PublicKey { + if bytes.len == 0 { + return error('Invalid bytes') + } + mut pub_key := C.EVP_PKEY_new() + pub_key = C.d2i_PUBKEY(&pub_key, voidptr(&bytes.data), bytes.len) + if pub_key == 0 { + C.EVP_PKEY_free(pub_key) + return error('Error loading public key') + } + // Get the NID of this pubkey, and check if the pubkey object was + // have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey + nid := C.EVP_PKEY_base_id(pub_key) + if nid != nid_ec_publickey { + C.EVP_PKEY_free(pub_key) + return error('Get an nid of non ecPublicKey') + } + + eckey := C.EVP_PKEY_get1_EC_KEY(pub_key) + if eckey == 0 { + 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 { + return error('Unsupported group') + } + // Its OK to return + return PublicKey{ + key: eckey + } +} + +// bytes gets the bytes of public key parts of this keypair. +pub fn (pbk PublicKey) bytes() ![]u8 { + point := voidptr(C.EC_KEY_get0_public_key(pbk.key)) + if point == 0 { + C.EC_POINT_free(point) + return error('Failed to get public key BIGNUM') + } + + group := voidptr(C.EC_KEY_get0_group(pbk.key)) + num_bits := C.EC_GROUP_get_degree(group) + // 1 byte of conversion format || x || y of EC_POINT + num_bytes := 1 + 2 * ((num_bits + 7) / 8) + + ctx := C.BN_CTX_new() + defer { + C.BN_CTX_free(ctx) + } + + if ctx == 0 { + C.EC_POINT_free(point) + C.BN_CTX_free(ctx) + return error('Failed to create BN_CTX') + } + 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. + conv_form := C.EC_KEY_get_conv_form(pbk.key) + if conv_form !in [2, 3, 4, 6] { + return error('bad conversion format') + } + n := C.EC_POINT_point2oct(group, point, conv_form, buf.data, buf.len, ctx) + + // returns the clone of the buffer[..n] + return buf[..n].clone() +} diff --git a/vlib/crypto/ecdsa/util_test.v b/vlib/crypto/ecdsa/util_test.v new file mode 100644 index 00000000000000..410539ecb9eaf9 --- /dev/null +++ b/vlib/crypto/ecdsa/util_test.v @@ -0,0 +1,48 @@ +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 +const privatekey_sample = '-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAwzj2iiJZaxgk/C6mp +oVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/BW+xUJEj/pxWhZANiAAT4/euEWRPV +9cdhtjcKlwF2HrFMLvgxAXFx+01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn +04xVeRuPBbCFxc/uqYj2s5ItHcAZSV3L5sGlXadPfTqoIjCBQAx44k8= +-----END PRIVATE KEY-----' + +const public_key_sample = '-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx +cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS +LR3AGUldy+bBpV2nT306qCIwgUAMeOJP +-----END PUBLIC KEY-----' + +// Message tobe signed and verified +const message_tobe_signed = 'Example of ECDSA with P-384' +// Message signature created with SHA384 digest with associated above key +const expected_signature = hex.decode('3066023100b08f6ec77bb319fdb7bce55a2714d7e79cc645d834ee539d8903cfcc88c6fa90df1558856cb840b2dd82e82cd89d7046023100d9d482ca8a6545a3b081fbdd4bb9643a2b4eda4e21fd624833216596032471faae646891f8d2f0bbb86b796c36d3c390')! + +fn test_load_pubkey_from_der_serialized_bytes() ! { + block, _ := pem.decode(public_key_sample) or { panic(err) } + pbkey := pubkey_from_bytes(block.data)! + + 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)! + assert status_with_hashed == true +} + +fn test_for_pubkey_bytes() ! { + // material generated with online ecdsa generator https://emn178.github.io/online-tools/ecdsa/key-generator/ + pv := '62e998bea8a15f52ff0b76cf3fe281cfcd8042ce4479b6e652ca7b5a36f6fb40' + pb := '0421af184ac64c8a13e66c65d4f1ad31677edeaa97af791aef73b66ea26d1623a411f67b6c4d842ba22fa39d1216bd64acef00a1b924ac11a10af679ac3a7eb2fd' + pvkey := new_key_from_seed(hex.decode(pv)!)! + + assert pvkey.seed()!.hex() == pv + pbkey := pvkey.public_key()! + assert pbkey.bytes()!.hex() == pb +}