diff --git a/bench/crypto/ecdsa/ecdsa.c b/bench/crypto/ecdsa/ecdsa.c new file mode 100644 index 0000000000..77959a9cc2 --- /dev/null +++ b/bench/crypto/ecdsa/ecdsa.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ITERATIONS 1000 +#define MESSAGE "Benchmark message" + +// Function to calculate the difference in microseconds between two timevals +long time_diff_microseconds(struct timeval start, struct timeval end) { + long seconds = end.tv_sec - start.tv_sec; + long useconds = end.tv_usec - start.tv_usec; + return seconds * 1000000 + useconds; +} + +int main() { + int iterations = ITERATIONS; + struct timeval start, end; + long total_gen_time = 0; + long total_sign_time = 0; + long total_verify_time = 0; + long avg_gen_time, avg_sign_time, avg_verify_time; + + // Benchmarking key generation + printf("Benchmarking key generation...\n"); + for(int i = 0; i < iterations; i++) { + gettimeofday(&start, NULL); + + // Create a new EC_KEY object with the P-256 curve + EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (key == NULL) { + fprintf(stderr, "Error creating EC_KEY object.\n"); + exit(EXIT_FAILURE); + } + + // Generate the key + if (EC_KEY_generate_key(key) != 1) { + fprintf(stderr, "Error generating EC key.\n"); + EC_KEY_free(key); + exit(EXIT_FAILURE); + } + + gettimeofday(&end, NULL); + total_gen_time += time_diff_microseconds(start, end); + + // Free the key + EC_KEY_free(key); + } + avg_gen_time = total_gen_time / iterations; + printf("Average key generation time: %ld µs\n", avg_gen_time); + + // Generate a single key for signing and verification + EC_KEY *priv_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (priv_key == NULL) { + fprintf(stderr, "Error creating EC_KEY object.\n"); + exit(EXIT_FAILURE); + } + if (EC_KEY_generate_key(priv_key) != 1) { + fprintf(stderr, "Error generating EC key.\n"); + EC_KEY_free(priv_key); + exit(EXIT_FAILURE); + } + + // Prepare the message hash + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256((unsigned char*)MESSAGE, strlen(MESSAGE), hash); + + // Benchmarking signing + printf("Benchmarking signing...\n"); + for(int i = 0; i < iterations; i++) { + gettimeofday(&start, NULL); + + ECDSA_SIG *signature = ECDSA_do_sign(hash, SHA256_DIGEST_LENGTH, priv_key); + if (signature == NULL) { + fprintf(stderr, "Error signing message.\n"); + EC_KEY_free(priv_key); + exit(EXIT_FAILURE); + } + + gettimeofday(&end, NULL); + total_sign_time += time_diff_microseconds(start, end); + + // Free the signature + ECDSA_SIG_free(signature); + } + avg_sign_time = total_sign_time / iterations; + printf("Average sign time: %ld µs\n", avg_sign_time); + + // Create a signature for verification benchmarking + ECDSA_SIG *signature = ECDSA_do_sign(hash, SHA256_DIGEST_LENGTH, priv_key); + if (signature == NULL) { + fprintf(stderr, "Error signing message for verification.\n"); + EC_KEY_free(priv_key); + exit(EXIT_FAILURE); + } + + // Benchmarking verification + printf("Benchmarking verification...\n"); + for(int i = 0; i < iterations; i++) { + gettimeofday(&start, NULL); + + int verify_status = ECDSA_do_verify(hash, SHA256_DIGEST_LENGTH, signature, priv_key); + + gettimeofday(&end, NULL); + total_verify_time += time_diff_microseconds(start, end); + + if (verify_status != 1) { + fprintf(stderr, "Signature verification failed.\n"); + ECDSA_SIG_free(signature); + EC_KEY_free(priv_key); + exit(EXIT_FAILURE); + } + } + avg_verify_time = total_verify_time / iterations; + printf("Average verify time: %ld µs\n", avg_verify_time); + + // Cleanup + ECDSA_SIG_free(signature); + EC_KEY_free(priv_key); + + return 0; +} + diff --git a/bench/crypto/ecdsa/ecdsa.go b/bench/crypto/ecdsa/ecdsa.go new file mode 100644 index 0000000000..a7f24fef3c --- /dev/null +++ b/bench/crypto/ecdsa/ecdsa.go @@ -0,0 +1,63 @@ +package main + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "time" +) + +func main() { + iterations := 1000 + + fmt.Println("Benchmarking key generation...") + var totalGenTime int64 + for i := 0; i < iterations; i++ { + start := time.Now() + _, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } + totalGenTime += time.Since(start).Microseconds() + } + avgGenTime := totalGenTime / int64(iterations) + fmt.Printf("Average key generation time: %d µs\n", avgGenTime) + + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } + message := []byte("Benchmark message") + + fmt.Println("Benchmarking signing...") + var totalSignTime int64 + for i := 0; i < iterations; i++ { + start := time.Now() + _, _, err := ecdsa.Sign(rand.Reader, privKey, message) + if err != nil { + panic(err) + } + totalSignTime += time.Since(start).Microseconds() + } + avgSignTime := totalSignTime / int64(iterations) + fmt.Printf("Average sign time: %d µs\n", avgSignTime) + + r, s, err := ecdsa.Sign(rand.Reader, privKey, message) + if err != nil { + panic(err) + } + + pubKey := &privKey.PublicKey + + fmt.Println("Benchmarking verification...") + var totalVerifyTime int64 + for i := 0; i < iterations; i++ { + start := time.Now() + ecdsa.Verify(pubKey, message, r, s) + totalVerifyTime += time.Since(start).Microseconds() + } + avgVerifyTime := totalVerifyTime / int64(iterations) + fmt.Printf("Average verify time: %d µs\n", avgVerifyTime) +} + diff --git a/vlib/crypto/ecdsa/ecdsa.v b/vlib/crypto/ecdsa/ecdsa.v new file mode 100644 index 0000000000..424fad19fb --- /dev/null +++ b/vlib/crypto/ecdsa/ecdsa.v @@ -0,0 +1,229 @@ +// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module ecdsa + +#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include + +#flag -I/usr/include/openssl +#flag -lcrypto +#flag darwin -I/usr/local/opt/openssl/include +#flag darwin -L/usr/local/opt/openssl/lib +#include +#include +#include +#include + +// C function declarations +fn C.EC_KEY_new_by_curve_name(nid int) &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_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_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_free(point &C.EC_POINT) +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) + +// NID constants +const nid_prime256v1 = C.NID_X9_62_prime256v1 + +@[typedef] +struct C.EC_KEY {} + +@[typedef] +struct C.EC_GROUP {} + +@[typedef] +struct C.BIGNUM {} + +@[typedef] +struct C.EC_POINT {} + +@[typedef] +struct C.ECDSA_SIG {} + +@[typedef] +struct C.BN_CTX {} + +pub struct PrivateKey { + key &C.EC_KEY +} + +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) + if ec_key == 0 { + return error('Failed to create new EC_KEY') + } + res := C.EC_KEY_generate_key(ec_key) + if res != 1 { + C.EC_KEY_free(ec_key) + return error('Failed to generate EC_KEY') + } + priv_key := PrivateKey{ + key: ec_key + } + pub_key := PublicKey{ + key: ec_key + } + 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 EC_KEY object with the specified curve + ec_key := C.EC_KEY_new_by_curve_name(nid) + if ec_key == 0 { + return error('Failed to create new EC_KEY') + } + // Convert the seed bytes into a BIGNUM + bn := C.BN_bin2bn(seed.data, seed.len, 0) + if bn == 0 { + C.EC_KEY_free(ec_key) + return error('Failed to create BIGNUM from seed') + } + // Set the BIGNUM as the private key in the EC_KEY object + mut res := C.EC_KEY_set_private_key(ec_key, bn) + if res != 1 { + C.BN_free(bn) + C.EC_KEY_free(ec_key) + return error('Failed to set private key') + } + // Now compute the public key + // + // Retrieve the EC_GROUP object associated with the EC_KEY + group := C.EC_KEY_get0_group(ec_key) + // 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 + ctx := C.BN_CTX_new() + if ctx == 0 { + C.EC_POINT_free(pub_key_point) + C.BN_free(bn) + C.EC_KEY_free(ec_key) + return error('Failed to create BN_CTX') + } + defer { + C.BN_CTX_free(ctx) + } + // Perform the point multiplication to compute the public key: pub_key_point = bn * G + res = C.EC_POINT_mul(group, pub_key_point, bn, 0, 0, ctx) + if res != 1 { + C.EC_POINT_free(pub_key_point) + C.BN_free(bn) + C.EC_KEY_free(ec_key) + return error('Failed to compute public key') + } + // Set the computed public key in the EC_KEY object + res = C.EC_KEY_set_public_key(ec_key, pub_key_point) + if res != 1 { + C.EC_POINT_free(pub_key_point) + C.BN_free(bn) + C.EC_KEY_free(ec_key) + return error('Failed to set public key') + } + C.EC_POINT_free(pub_key_point) + C.BN_free(bn) + return PrivateKey{ + key: ec_key + } +} + +// Sign a message with private key +pub fn (priv_key PrivateKey) sign(message []u8) ![]u8 { + if message.len == 0 { + return error('Message cannot be null or empty') + } + mut sig_len := u32(0) + sig_size := C.ECDSA_size(priv_key.key) + sig := unsafe { malloc(int(sig_size)) } + res := C.ECDSA_sign(0, message.data, message.len, sig, &sig_len, priv_key.key) + if res != 1 { + unsafe { free(sig) } + return error('Failed to sign message') + } + signed_data := unsafe { sig.vbytes(int(sig_len)) } + unsafe { free(sig) } + 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) + 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 { + bn := 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 { + return error('Failed to convert BIGNUM to bytes') + } + return buf +} + +// 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') + } + return PublicKey{ + key: priv_key.key + } +} + +// Compare two private keys +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 +} + +// 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) + ctx := C.BN_CTX_new() + if ctx == 0 { + return false + } + defer { + C.BN_CTX_free(ctx) + } + res := C.EC_POINT_cmp(group, point1, point2, ctx) + return res == 0 +} diff --git a/vlib/crypto/ecdsa/ecdsa_test.v b/vlib/crypto/ecdsa/ecdsa_test.v new file mode 100644 index 0000000000..94fb996c89 --- /dev/null +++ b/vlib/crypto/ecdsa/ecdsa_test.v @@ -0,0 +1,101 @@ +module ecdsa + +fn test_ecdsa() { + // Generate key pair + pub_key, priv_key := generate_key() or { panic(err) } + + // Sign a message + message := 'Hello, ECDSA!'.bytes() + 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}') + assert is_valid +} + +fn test_generate_key() ! { + // Test key generation + pub_key, priv_key := generate_key() or { panic(err) } + assert pub_key.key != unsafe { nil } + assert priv_key.key != unsafe { nil } +} + +fn test_new_key_from_seed() ! { + // Test generating a key from a seed + seed := [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 +} + +fn test_sign_and_verify() ! { + // Test signing and verifying a message + pub_key, priv_key := generate_key() or { panic(err) } + message := 'Test message'.bytes() + signature := priv_key.sign(message) or { panic(err) } + is_valid := pub_key.verify(message, signature) or { panic(err) } + assert is_valid +} + +fn test_seed() ! { + // Test retrieving the seed from a private key + _, priv_key := generate_key() or { panic(err) } + seed := priv_key.seed() or { panic(err) } + assert seed.len > 0 +} + +fn test_public_key() ! { + // Test getting the public key from a private key + _, priv_key := generate_key() or { panic(err) } + pub_key1 := priv_key.public_key() or { panic(err) } + pub_key2, _ := generate_key() or { panic(err) } + assert !pub_key1.equal(pub_key2) +} + +fn test_private_key_equal() ! { + // Test private key equality + _, 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) +} + +fn test_public_key_equal() ! { + // Test public key equality + _, 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) +} + +fn test_sign_with_new_key_from_seed() ! { + // Test signing with a key generated from a seed + seed := [u8(10), 20, 30, 40, 50] + priv_key := new_key_from_seed(seed) or { panic(err) } + message := 'Another test message'.bytes() + signature := priv_key.sign(message) or { panic(err) } + pub_key := priv_key.public_key() or { panic(err) } + is_valid := pub_key.verify(message, signature) or { panic(err) } + assert is_valid +} + +fn test_invalid_signature() ! { + // Test verifying an invalid signature + pub_key, _ := 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' + return + } + assert !result +} + +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) } + assert !priv_key1.equal(priv_key2) +}