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

This commit is contained in:
blackshirt 2025-01-19 01:07:19 +07:00 committed by GitHub
parent 3c4878063e
commit c2b7dbf9b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 752 additions and 109 deletions

View File

@ -252,6 +252,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
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
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_test.v' // requires OpenSSL
$if tinyc {
skip_files << 'examples/database/orm.v' // try fix it
}
@ -284,6 +285,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v'
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_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
}
@ -291,6 +293,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
skip_files << 'vlib/net/openssl/openssl_compiles_test.c.v'
skip_files << 'vlib/crypto/ecdsa/ecdsa_test.v' // requires OpenSSL
skip_files << 'vlib/crypto/ecdsa/util_test.v' // requires OpenSSL
skip_files << 'vlib/crypto/ecdsa/example/ecdsa_seed_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'
}

View File

@ -2,7 +2,38 @@
`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
> [!CAUTION]
> This module using low level OpenSSL opaque methods that mostly has been deprecated
> in OpenSSL 3.0.
> Please be aware, likely it would not compile with `-cstrict` options until
> its migrated into supported higher level API.
# Example
```v
import crypto.ecdsa
fn main() {
// create default NIST P-256 secp256r1 curve key pair. If you wish to generate another curve,
// use: `pbkey, pvkey := ecdsa.generate_key(nid: .secp521r1)!` instead.
pbkey, pvkey := ecdsa.generate_key()!
message_tobe_signed := 'Hello ecdsa'.bytes()
// create a signature with the recommended hash
signature := pvkey.sign(message_tobe_signed)!
// verify the message with the signature
verified := pbkey.verify(message_tobe_signed, signature)!
dump(verified) // should be true
// free allocated keys when you have done with your work.
pbkey.free()
pvkey.free()
}
```

View File

@ -8,6 +8,10 @@ import crypto
import crypto.sha256
import crypto.sha512
// See https://docs.openssl.org/master/man7/openssl_user_macros/#description
// should be 0x30000000L, but a lot of EC_KEY method was deprecated on version 3.0
// #define OPENSSL_API_COMPAT 0x10100000L
#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
#flag -I/usr/include/openssl
@ -21,31 +25,32 @@ import crypto.sha512
// C function declarations
fn C.EC_KEY_new_by_curve_name(nid int) &C.EC_KEY
fn C.EC_KEY_dup(src &C.EC_KEY) &C.EC_KEY
fn C.EC_KEY_generate_key(key &C.EC_KEY) int
fn C.EC_KEY_free(key &C.EC_KEY)
fn C.BN_bin2bn(s &u8, len int, ret &C.BIGNUM) &C.BIGNUM
fn C.EC_KEY_set_public_key(key &C.EC_KEY, &C.EC_POINT) int
fn C.EC_KEY_set_private_key(key &C.EC_KEY, prv &C.BIGNUM) int
fn C.EC_KEY_get0_group(key &C.EC_KEY) &C.EC_GROUP
fn C.EC_KEY_get0_private_key(key &C.EC_KEY) &C.BIGNUM
fn C.EC_KEY_get0_public_key(key &C.EC_KEY) &C.EC_POINT
fn C.EC_KEY_check_key(key &C.EC_KEY) int
fn C.EC_KEY_up_ref(key &C.EC_KEY) int
fn C.EC_POINT_new(group &C.EC_GROUP) &C.EC_POINT
fn C.EC_POINT_mul(group &C.EC_GROUP, r &C.EC_POINT, n &C.BIGNUM, q &C.EC_POINT, m &C.BIGNUM, ctx &C.BN_CTX) int
fn C.EC_KEY_set_public_key(key &C.EC_KEY, &C.EC_POINT) int
fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX) int
fn C.EC_POINT_free(point &C.EC_POINT)
fn C.EC_GROUP_cmp(a &C.EC_GROUP, b &C.EC_GROUP, ctx &C.BN_CTX) int
fn C.BN_num_bits(a &C.BIGNUM) int
fn C.BN_bn2bin(a &C.BIGNUM, to &u8) int
fn C.BN_bn2binpad(a &C.BIGNUM, to &u8, tolen int) int
fn C.BN_cmp(a &C.BIGNUM, b &C.BIGNUM) int
fn C.BN_CTX_new() &C.BN_CTX
fn C.BN_CTX_free(ctx &C.BN_CTX)
fn C.BN_bin2bn(s &u8, len int, ret &C.BIGNUM) &C.BIGNUM
fn C.BN_free(a &C.BIGNUM)
fn C.ECDSA_size(key &C.EC_KEY) u32
fn C.ECDSA_sign(type_ int, dgst &u8, dgstlen int, sig &u8, siglen &u32, eckey &C.EC_KEY) int
fn C.ECDSA_verify(type_ int, dgst &u8, dgstlen int, sig &u8, siglen int, eckey &C.EC_KEY) int
fn C.EC_KEY_get0_private_key(key &C.EC_KEY) &C.BIGNUM
fn C.BN_num_bits(a &C.BIGNUM) int
fn C.BN_bn2bin(a &C.BIGNUM, to &u8) int
fn C.EC_KEY_up_ref(key &C.EC_KEY) int
fn C.BN_cmp(a &C.BIGNUM, b &C.BIGNUM) int
fn C.EC_KEY_get0_public_key(key &C.EC_KEY) &C.EC_POINT
fn C.EC_POINT_cmp(group &C.EC_GROUP, a &C.EC_POINT, b &C.EC_POINT, ctx &C.BN_CTX) int
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
//
@ -74,7 +79,12 @@ pub enum Nid {
@[params]
pub struct CurveOptions {
pub mut:
nid Nid = .prime256v1 // default to NIST P-256 curve
// default to NIST P-256 curve
nid Nid = .prime256v1
// by default, allow arbitrary size of seed bytes as key.
// Set it to `true` when you need fixed size, using the curve key size.
// Its main purposes is to support the `.new_key_from_seed` call.
fixed_size bool
}
@[typedef]
@ -95,42 +105,155 @@ struct C.ECDSA_SIG {}
@[typedef]
struct C.BN_CTX {}
pub struct PrivateKey {
key &C.EC_KEY
// enum flag to allow flexible PrivateKey size
enum KeyFlag {
// flexible flag to allow flexible-size of seed bytes
flexible
// fixed flag for using underlying curve key size
fixed
}
// PrivateKey represents ECDSA private key. Actually its a key pair,
// contains private key and public key parts.
pub struct PrivateKey {
key &C.EC_KEY
mut:
// ks_flag with .flexible value allowing
// flexible-size seed bytes as key.
// When it is `.fixed`, it will use the underlying key size.
ks_flag KeyFlag = .flexible
// ks_size stores size of the seed bytes when ks_flag was .flexible.
// You should set it to a non zero value
ks_size int
}
// PublicKey represents ECDSA public key for verifying message.
pub struct PublicKey {
key &C.EC_KEY
}
// Generate a new key pair. If opt was not provided, its default to prime256v1 curve.
pub fn generate_key(opt CurveOptions) !(PublicKey, PrivateKey) {
// PrivateKey.new creates a new key pair. By default, it would create a prime256v1 based key.
pub fn PrivateKey.new(opt CurveOptions) !PrivateKey {
// creates new empty key
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
// Generates new public and private key for the supplied ec_key object.
res := C.EC_KEY_generate_key(ec_key)
if res != 1 {
C.EC_KEY_free(ec_key)
return error('Failed to generate EC_KEY')
}
// performs explicit check
chk := C.EC_KEY_check_key(ec_key)
if chk == 0 {
C.EC_KEY_free(ec_key)
return error('EC_KEY_check_key failed')
}
// when using default EC_KEY_generate_key, its using underlying curve key size
// and discarded opt.fixed_size flag when its not set.
priv_key := PrivateKey{
key: ec_key
key: ec_key
ks_flag: .fixed
}
return priv_key
}
// 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) {
// creates new empty key
ec_key := new_curve(opt)
if ec_key == 0 {
C.EC_KEY_free(ec_key)
return error('Failed to create new EC_KEY')
}
// we duplicate the empty ec_key and shares similiar curve infos
// and used this as public key
pbkey := C.EC_KEY_dup(ec_key)
if pbkey == 0 {
C.EC_KEY_free(ec_key)
C.EC_KEY_free(pbkey)
return error('Failed on EC_KEY_dup')
}
res := C.EC_KEY_generate_key(ec_key)
if res != 1 {
C.EC_KEY_free(ec_key)
C.EC_KEY_free(pbkey)
return error('Failed to generate EC_KEY')
}
// we take public key bits from above generated key
// and stored in duplicated public key object before.
pubkey_point := voidptr(C.EC_KEY_get0_public_key(ec_key))
if pubkey_point == 0 {
C.EC_POINT_free(pubkey_point)
C.EC_KEY_free(ec_key)
C.EC_KEY_free(pbkey)
return error('Failed to get public key BIGNUM')
}
np := C.EC_KEY_set_public_key(pbkey, pubkey_point)
if np != 1 {
C.EC_POINT_free(pubkey_point)
C.EC_KEY_free(ec_key)
C.EC_KEY_free(pbkey)
return error('Failed to set public key')
}
// when using default generate_key, its using underlying curve key size
// and discarded opt.fixed_size flag when its not set.
priv_key := PrivateKey{
key: ec_key
ks_flag: .fixed
}
pub_key := PublicKey{
key: ec_key
key: pbkey
}
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 come from a cryptographically secure random generator,
// 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: cast with voidptr() to allow -cstrict checks to pass
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')
}
// Check if its using fixed key size or flexible one
if opt.fixed_size {
if seed.len != key_size {
C.EC_KEY_free(ec_key)
return error('seed size doesnt match with curve key size')
}
}
// Convert the seed bytes into a BIGNUM
bn := C.BN_bin2bn(seed.data, seed.len, 0)
if bn == 0 {
@ -146,17 +269,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
@ -195,14 +307,38 @@ pub fn new_key_from_seed(seed []u8, opt CurveOptions) !PrivateKey {
}
C.EC_POINT_free(pub_key_point)
C.BN_free(bn)
return PrivateKey{
mut pvkey := PrivateKey{
key: ec_key
}
// we set the flag information on the key
if opt.fixed_size {
// using fixed one
pvkey.ks_flag = .fixed
pvkey.ks_size = key_size
} else {
pvkey.ks_size = seed.len
}
return pvkey
}
// 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 options,
// it will perform hashing before signing the message.
pub fn (pv PrivateKey) sign(message []u8, opt SignerOpts) ![]u8 {
digest := calc_digest(pv.key, message, opt)!
return pv.sign_message(digest)!
}
// sign_with_options signs message with the options. It will be deprecated,
// Use `PrivateKey.sign()` instead.
@[deprecated: 'use PrivateKey.sign() instead']
pub fn (pv PrivateKey) sign_with_options(message []u8, opt SignerOpts) ![]u8 {
return pv.sign(message, opt)
}
// 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,49 +355,95 @@ 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)
pub fn (priv_key PrivateKey) seed() ![]u8 {
// bytes represent private key as bytes.
pub fn (priv_key PrivateKey) bytes() ![]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)
// Get the buffer size to store the seed.
size := if priv_key.ks_flag == .flexible {
// should be non zero
priv_key.ks_size
} else {
num_bytes
}
mut buf := []u8{len: int(size)}
res := C.BN_bn2binpad(bn, buf.data, size)
if res == 0 {
return error('Failed to convert BIGNUM to bytes')
}
return buf
}
// Get the public key from private key
// seed gets the seed (private key bytes). It will be deprecated.
// Use `PrivateKey.bytes()` instead.
@[deprecated: 'use PrivateKey.bytes() instead']
pub fn (priv_key PrivateKey) seed() ![]u8 {
return priv_key.bytes()
}
// Get the public key from private key.
pub fn (priv_key PrivateKey) public_key() !PublicKey {
// Increase reference count
res := C.EC_KEY_up_ref(priv_key.key)
if res != 1 {
return error('Failed to increment EC_KEY reference count')
// There are some issues concerned when returning PublicKey directly using underlying
// `PrivateKey.key`. This private key containing sensitive information inside it, so return
// this without care maybe can lead to some serious security impact.
// See https://discord.com/channels/592103645835821068/592320321995014154/1329261267965448253
// So, we instead return a new EC_KEY opaque based information availables on private key object
// without private key bits has been set on this new opaque.
group := voidptr(C.EC_KEY_get0_group(priv_key.key))
if group == 0 {
return error('Failed to load group from priv_key')
}
nid := C.EC_GROUP_get_curve_name(group)
if nid != nid_prime256v1 && nid != nid_secp384r1 && nid != nid_secp521r1 && nid != nid_secp256k1 {
return error('Get unsupported curve nid')
}
// get public key point from private key opaque
pubkey_point := voidptr(C.EC_KEY_get0_public_key(priv_key.key))
if pubkey_point == 0 {
// C.EC_POINT_free(pubkey_point)
// todo: maybe its not set, just calculates new one
return error('Failed to get public key BIGNUM')
}
// creates a new EC_KEY opaque based on the same NID with private key and
// sets public key on it.
pub_key := C.EC_KEY_new_by_curve_name(nid)
np := C.EC_KEY_set_public_key(pub_key, pubkey_point)
if np != 1 {
// C.EC_POINT_free(pubkey_point)
C.EC_KEY_free(pub_key)
return error('Failed to set public key')
}
// performs explicit check
chk := C.EC_KEY_check_key(pub_key)
if chk == 0 {
C.EC_KEY_free(pub_key)
return error('EC_KEY_check_key failed')
}
// OK ?
return PublicKey{
key: priv_key.key
key: pub_key
}
}
// 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
//
// - 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 +517,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,7 +545,7 @@ fn (pv PrivateKey) recommended_hash() !crypto.Hash {
}
pub enum HashConfig {
with_recomended_hash
with_recommended_hash
with_no_hash
with_custom_hash
}
@ -371,44 +553,46 @@ pub enum HashConfig {
@[params]
pub struct SignerOpts {
pub mut:
hash_config HashConfig = .with_recomended_hash
// make sense when HashConfig != with_recomended_hash
// default to .with_recommended_hash
hash_config HashConfig = .with_recommended_hash
// 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 {
// 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 +601,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 +615,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)
}

View File

@ -13,25 +13,22 @@ fn test_ecdsa() {
println('Signature valid: ${is_valid}')
assert is_valid
key_free(priv_key.key)
key_free(pub_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
}
signature := priv_key.sign_with_options(message, opts) or { panic(err) }
signature := priv_key.sign(message) or { panic(err) }
// Verify the signature
is_valid := pub_key.verify(message, signature) or { panic(err) }
println('Signature valid: ${is_valid}')
key_free(pub_key.key)
key_free(priv_key.key)
assert is_valid
}
@ -41,6 +38,7 @@ fn test_generate_key() ! {
assert pub_key.key != unsafe { nil }
assert priv_key.key != unsafe { nil }
key_free(priv_key.key)
key_free(pub_key.key)
}
fn test_new_key_from_seed() ! {
@ -52,6 +50,15 @@ fn test_new_key_from_seed() ! {
key_free(priv_key.key)
}
fn test_new_key_from_seed_with_leading_zeros_bytes() ! {
// Test generating a key from a seed
seed := [u8(0), u8(1), 2, 3, 4, 5]
priv_key := new_key_from_seed(seed) or { panic(err) }
retrieved_seed := priv_key.seed() or { panic(err) }
assert seed == retrieved_seed
key_free(priv_key.key)
}
fn test_sign_and_verify() ! {
// Test signing and verifying a message
pub_key, priv_key := generate_key() or { panic(err) }
@ -60,22 +67,26 @@ fn test_sign_and_verify() ! {
is_valid := pub_key.verify(message, signature) or { panic(err) }
assert is_valid
key_free(priv_key.key)
key_free(pub_key.key)
}
fn test_seed() ! {
// Test retrieving the seed from a private key
_, priv_key := generate_key() or { panic(err) }
pub_key, priv_key := generate_key() or { panic(err) }
seed := priv_key.seed() or { panic(err) }
assert seed.len > 0
key_free(priv_key.key)
key_free(pub_key.key)
}
fn test_public_key() ! {
// Test getting the public key from a private key
_, priv_key := generate_key() or { panic(err) }
pubkk, priv_key := generate_key() or { panic(err) }
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2, _ := generate_key() or { panic(err) }
pub_key2, privkk := generate_key() or { panic(err) }
assert !pub_key1.equal(pub_key2)
key_free(pubkk.key)
key_free(privkk.key)
key_free(priv_key.key)
key_free(pub_key1.key)
key_free(pub_key2.key)
@ -83,31 +94,34 @@ fn test_public_key() ! {
fn test_private_key_equal() ! {
// Test private key equality
_, priv_key1 := generate_key() or { panic(err) }
pbk, priv_key1 := generate_key() or { panic(err) }
seed := priv_key1.seed() or { panic(err) }
priv_key2 := new_key_from_seed(seed) or { panic(err) }
assert priv_key1.equal(priv_key2)
key_free(pbk.key)
key_free(priv_key1.key)
key_free(priv_key2.key)
}
fn test_private_key_equality_on_different_curve() ! {
// default group
_, priv_key1 := generate_key() or { panic(err) }
pbk, 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)
key_free(pbk.key)
key_free(priv_key1.key)
key_free(priv_key2.key)
}
fn test_public_key_equal() ! {
// Test public key equality
_, priv_key := generate_key() or { panic(err) }
pbk, priv_key := generate_key() or { panic(err) }
pub_key1 := priv_key.public_key() or { panic(err) }
pub_key2 := priv_key.public_key() or { panic(err) }
assert pub_key1.equal(pub_key2)
key_free(pbk.key)
key_free(priv_key.key)
key_free(pub_key1.key)
key_free(pub_key2.key)
@ -128,24 +142,65 @@ fn test_sign_with_new_key_from_seed() ! {
fn test_invalid_signature() ! {
// Test verifying an invalid signature
pub_key, _ := generate_key() or { panic(err) }
pub_key, pvk := generate_key() or { panic(err) }
message := 'Test message'.bytes()
invalid_signature := [u8(1), 2, 3] // Deliberately invalid
result := pub_key.verify(message, invalid_signature) or {
// Expecting verification to fail
assert err.msg() == 'Failed to verify signature'
key_free(pub_key.key)
key_free(pvk.key)
return
}
assert !result
key_free(pub_key.key)
key_free(pvk.key)
}
fn test_different_keys_not_equal() ! {
// Test that different keys are not equal
_, priv_key1 := generate_key() or { panic(err) }
_, priv_key2 := generate_key() or { panic(err) }
pbk1, priv_key1 := generate_key() or { panic(err) }
pbk2, priv_key2 := generate_key() or { panic(err) }
assert !priv_key1.equal(priv_key2)
key_free(pbk1.key)
key_free(pbk2.key)
key_free(priv_key1.key)
key_free(priv_key2.key)
}
fn test_private_key_new() ! {
priv_key := PrivateKey.new()!
assert priv_key.ks_flag == .fixed
size := ec_key_size(priv_key.key)!
assert size == 32
pubkey := priv_key.public_key()!
message := 'Another test message'.bytes()
signature := priv_key.sign(message)!
is_valid := pubkey.verify(message, signature)!
assert is_valid
// new private key
seed := priv_key.seed()!
priv_key2 := new_key_from_seed(seed)!
pubkey2 := priv_key2.public_key()!
assert priv_key.equal(priv_key2)
assert pubkey.equal(pubkey2)
is_valid2 := pubkey2.verify(message, signature)!
assert is_valid2
// generates new key with different curve
priv_key3 := new_key_from_seed(seed, nid: .secp384r1)!
pubkey3 := priv_key3.public_key()!
assert !priv_key3.equal(priv_key2)
assert !pubkey3.equal(pubkey2)
is_valid3 := pubkey3.verify(message, signature)!
assert !is_valid3
priv_key.free()
priv_key2.free()
priv_key3.free()
pubkey.free()
pubkey2.free()
pubkey3.free()
}

View File

@ -0,0 +1,83 @@
import rand
import crypto.ecdsa
import encoding.hex
// 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()!
assert random_bytes == ret_seed
pvkey.free()
}
}
fn test_private_and_public_key_from_string() ! {
// See [this](https://github.com/vlang/v/blob/master/vlib/crypto/ecdsa/util_test.v) for detail
// of material used as a sample.
privkey_sample := '-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAwzj2iiJZaxgk/C6mp
oVskdr6j7akl4bPB8JRnT1J5XNbLPK/iNd/BW+xUJEj/pxWhZANiAAT4/euEWRPV
9cdhtjcKlwF2HrFMLvgxAXFx+01UPfMQ9XOj/85qUhVq1jXraSyDy5FYF28UW4dn
04xVeRuPBbCFxc/uqYj2s5ItHcAZSV3L5sGlXadPfTqoIjCBQAx44k8=
-----END PRIVATE KEY-----'
pubkey_sample := '-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx
cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS
LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
-----END PUBLIC KEY-----'
// Message tobe signed and verified
message_tobe_signed := 'Example of ECDSA with P-384'.bytes()
// Message signature generated with SHA384 digest with associated key previously.
signature := hex.decode('3066023100b08f6ec77bb319fdb7bce55a2714d7e79cc645d834ee539d8903cfcc88c6fa90df1558856cb840b2dd82e82cd89d7046023100d9d482ca8a6545a3b081fbdd4bb9643a2b4eda4e21fd624833216596032471faae646891f8d2f0bbb86b796c36d3c390')!
// loads a Privatekey and PublicKey from above sample
privkey := ecdsa.privkey_from_string(privkey_sample)!
pubkey := ecdsa.pubkey_from_string(pubkey_sample)!
// get a public key from private key
pbkey_from_privkey := privkey.public_key()!
// two public key should be equal, its comes from the same source.
assert pubkey.equal(pbkey_from_privkey)
// lets create the signature
created_signature := privkey.sign(message_tobe_signed)!
verified1 := pubkey.verify(message_tobe_signed, signature)!
verified2 := pubkey.verify(message_tobe_signed, created_signature)!
assert verified1 == true
assert verified2 == true
// Its also should be verified with pbkey_from_privkey opaque
verified3 := pbkey_from_privkey.verify(message_tobe_signed, signature)!
verified4 := pbkey_from_privkey.verify(message_tobe_signed, created_signature)!
assert verified3 == true
assert verified4 == true
// release the key
privkey.free()
pubkey.free()
pbkey_from_privkey.free()
}

View File

@ -0,0 +1,16 @@
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
pbkey.free()
pvkey.free()
}

View File

@ -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
@ -10,6 +12,12 @@ const nid_ec_publickey = C.NID_X9_62_id_ecPublicKey
@[typedef]
struct C.EVP_PKEY {}
@[typedef]
struct C.BIO_METHOD {}
@[typedef]
pub struct C.BIO {}
// EVP_PKEY *EVP_PKEY_new(void);
fn C.EVP_PKEY_new() &C.EVP_PKEY
@ -41,6 +49,24 @@ fn C.EVP_PKEY_base_id(key &C.EVP_PKEY) int
fn C.EC_GROUP_get_curve_name(g &C.EC_GROUP) int
fn C.EC_GROUP_free(group &C.EC_GROUP)
// 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
// EVP_PKEY *PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, void *u);
fn C.PEM_read_bio_PUBKEY(bp &C.BIO, x &&C.EVP_PKEY, cb int, u &voidptr) &C.EVP_PKEY
// 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.
@ -49,6 +75,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 +84,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 +106,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,9 +127,10 @@ 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.
pub fn (pbk PublicKey) bytes() ![]u8 {
point := voidptr(C.EC_KEY_get0_public_key(pbk.key))
// defer { C.EC_POINT_free(point)}
if point == 0 {
C.EC_POINT_free(point)
return error('Failed to get public key BIGNUM')
@ -126,6 +154,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 +163,162 @@ 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('EC_POINT_point2oct failed')
}
// returns the clone of the buffer[..n]
return buf[..n].clone()
}
// pubkey_from_string loads a PublicKey from valid PEM-formatted string in s.
pub fn pubkey_from_string(s string) !PublicKey {
if s.len == 0 {
return error('Null length string was not allowed')
}
mut evpkey := 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 {
C.BIO_free_all(bo)
return error('BIO_write failed')
}
evpkey = C.PEM_read_bio_PUBKEY(bo, &evpkey, 0, 0)
if evpkey == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
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(evpkey)
if nid != nid_ec_publickey {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Get an nid of non ecPublicKey')
}
// Gets the ec key
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
if eckey == 0 {
C.BIO_free_all(bo)
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('Failed to get ec key')
}
// check the group for the supported curve(s)
if !is_valid_supported_group(eckey) {
C.BIO_free_all(bo)
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('Unsupported group')
}
chk := C.EC_KEY_check_key(eckey)
if chk == 0 {
C.EC_KEY_free(eckey)
return error('EC_KEY_check_key failed')
}
C.EVP_PKEY_free(evpkey)
C.BIO_free_all(bo)
// Its OK to return
return PublicKey{
key: eckey
}
}
// privkey_from_string loads a PrivateKey from valid PEM-formatted string in s.
// Underlying wrapper support for old secg and pkcs8 private key format, but this was not heavily tested.
// This routine does not support for the pkcs8 EncryptedPrivateKeyInfo format.
// See [usage_test.v](https://github.com/vlang/v/blob/master/vlib/crypto/ecdsa/example/ecdsa_seed_test.v) file
// for example of usage.
pub fn privkey_from_string(s string) !PrivateKey {
if s.len == 0 {
return error('null string was not allowed')
}
mut evpkey := 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 {
C.BIO_free_all(bo)
return error('BIO_write failed')
}
evpkey = C.PEM_read_bio_PrivateKey(bo, &evpkey, 0, 0)
if evpkey == 0 {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
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(evpkey)
if nid != nid_ec_publickey {
C.BIO_free_all(bo)
C.EVP_PKEY_free(evpkey)
return error('Get an nid of non ecPublicKey')
}
eckey := C.EVP_PKEY_get1_EC_KEY(evpkey)
if eckey == 0 {
C.BIO_free_all(bo)
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('Failed to get ec key')
}
// check the group for the supported curve(s)
if !is_valid_supported_group(eckey) {
C.BIO_free_all(bo)
C.EC_KEY_free(eckey)
C.EVP_PKEY_free(evpkey)
return error('Unsupported group')
}
chk := C.EC_KEY_check_key(eckey)
if chk == 0 {
C.EC_KEY_free(eckey)
return error('EC_KEY_check_key failed')
}
ksize := ec_key_size(eckey)!
C.EVP_PKEY_free(evpkey)
C.BIO_free_all(bo)
// Its OK to return
return PrivateKey{
key: eckey
ks_flag: .fixed
ks_size: ksize
}
}
// Helpers
//
// is_valid_supported_group checks whether this eckey has valid group of supported curve.
@[inline]
fn is_valid_supported_group(eckey &C.EC_KEY) bool {
group := voidptr(C.EC_KEY_get0_group(eckey))
if group == 0 {
return false
}
nidgroup := C.EC_GROUP_get_curve_name(group)
if nidgroup == nid_prime256v1 || nidgroup == nid_secp384r1 || nidgroup == nid_secp521r1
|| nidgroup == nid_secp256k1 {
return true
}
return false
}
// key_size get the key size of this ec key
fn ec_key_size(ec_key &C.EC_KEY) !int {
group := voidptr(C.EC_KEY_get0_group(ec_key))
if group == 0 {
return error('Unable to load group')
}
num_bits := C.EC_GROUP_get_degree(group)
key_size := (num_bits + 7) / 8
return key_size
}

View File

@ -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
@ -20,7 +19,7 @@ LR3AGUldy+bBpV2nT306qCIwgUAMeOJP
-----END PUBLIC KEY-----'
// Message tobe signed and verified
const message_tobe_signed = 'Example of ECDSA with P-384'
const message_tobe_signed = 'Example of ECDSA with P-384'.bytes()
// Message signature created with SHA384 digest with associated above key
const expected_signature = hex.decode('3066023100b08f6ec77bb319fdb7bce55a2714d7e79cc645d834ee539d8903cfcc88c6fa90df1558856cb840b2dd82e82cd89d7046023100d9d482ca8a6545a3b081fbdd4bb9643a2b4eda4e21fd624833216596032471faae646891f8d2f0bbb86b796c36d3c390')!
@ -28,11 +27,13 @@ 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)!
status_without_hashed := pbkey.verify(message_tobe_signed, expected_signature,
hash_config: .with_no_hash
)!
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, expected_signature)!
assert status_with_hashed == true
key_free(pbkey.key)
}
@ -49,3 +50,78 @@ 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
// 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 := 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)!
verified := pbkey.verify(message_tobe_signed, signature)!
assert verified == true
pvkey.free()
pbkey.free()
}
fn test_load_pubkey_from_string_and_used_for_verifying() ! {
pbkey := pubkey_from_string(public_key_sample)!
pbkey_bytes := pbkey.bytes()!
expected_pubkey_bytes := '04f8fdeb845913d5f5c761b6370a9701761eb14c2ef831017171fb4d543df310f573a3ffce6a52156ad635eb692c83cb9158176f145b8767d38c55791b8f05b085c5cfeea988f6b3922d1dc019495dcbe6c1a55da74f7d3aa8223081400c78e24f'
assert pbkey_bytes.hex() == expected_pubkey_bytes
// expected signature was comes from hashed message with sha384
status_with_hashed := pbkey.verify(message_tobe_signed, expected_signature)!
assert status_with_hashed == true
pbkey.free()
}
// 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-----'
_ := privkey_from_string(key) or {
assert err == error('Unsupported group')
return
}
}