From 9e135dd60c04d5884a67d4fca2f1afac1562e64a Mon Sep 17 00:00:00 2001 From: Joseph Spadavecchia Date: Thu, 15 Jul 2021 13:58:17 +0100 Subject: [PATCH 1/3] Added arbitrary message signing interface * Manually merged in https://github.com/jspada/c-reference-signer/tree/message-signing * Added back in message_verify() --- crypto.c | 99 ++++++++++++++++++++++++++++++++++++++++++++ crypto.h | 3 ++ unit_tests.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 213 insertions(+), 3 deletions(-) diff --git a/crypto.c b/crypto.c index 20770a2..dfe2af9 100644 --- a/crypto.c +++ b/crypto.c @@ -1079,3 +1079,102 @@ void sign(Signature *sig, const Keypair *kp, const Transaction *transaction, uin scalar_mul(e_priv, e, kp->priv); scalar_add(sig->s, k, e_priv); } + +bool sign_message(Signature *sig, const Keypair *kp, const uint8_t *msg, const size_t len, const uint8_t network_id) +{ + // Convert msg bytes to ROInput + uint64_t input_fields[4 * 0]; // Messages are stored in bits (no fields) + uint8_t input_bits[len]; + ROInput input; + input.fields_capacity = 0; + input.bits_capacity = 8 * len; + input.fields = input_fields; + input.bits = input_bits; + input.fields_len = 0; + input.bits_len = 0; + + roinput_add_bytes(&input, msg, len); + + Scalar k; + message_derive(k, kp, &input, network_id); + + uint64_t k_nonzero; + fiat_pasta_fq_nonzero(&k_nonzero, k); + if (! k_nonzero) { + return false; + } + + // r = k*g + Affine r; + affine_scalar_mul(&r, k, &AFFINE_ONE); + + field_copy(sig->rx, r.x); + + if (field_is_odd(r.y)) { + // negate (k = -k) + Scalar tmp; + fiat_pasta_fq_copy(tmp, k); + scalar_negate(k, tmp); + } + + Scalar e; + message_hash(e, &kp->pub, r.x, &input, POSEIDON_3W, network_id); + + // s = k + e*sk + Scalar e_priv; + scalar_mul(e_priv, e, kp->priv); + scalar_add(sig->s, k, e_priv); + + return true; +} + +bool verify_message(Signature *sig, const Compressed *pub_compressed, const uint8_t *msg, const size_t len, uint8_t network_id) +{ + // Convert msg bytes to ROInput + uint64_t input_fields[4 * 0]; // Messages are stored in bits (no fields) + uint8_t input_bits[len]; + ROInput input; + input.fields_capacity = 0; + input.bits_capacity = 8 * len; + input.fields = input_fields; + input.bits = input_bits; + input.fields_len = 0; + input.bits_len = 0; + + roinput_add_bytes(&input, msg, len); + + Affine pub; + decompress(&pub, pub_compressed); + + Scalar e; + message_hash(e, &pub, sig->rx, &input, POSEIDON_3W, network_id); + + Group g; + affine_to_group(&g, &AFFINE_ONE); + + Group sg; + group_scalar_mul(&sg, sig->s, &g); + + Group pub_proj; + affine_to_group(&pub_proj, &pub); + Group epub; + group_scalar_mul(&epub, e, &pub_proj); + + Group neg_epub; + fiat_pasta_fp_copy(neg_epub.X, epub.X); + fiat_pasta_fp_opp(neg_epub.Y, epub.Y); + fiat_pasta_fp_copy(neg_epub.Z, epub.Z); + + Group r; + group_add(&r, &sg, &neg_epub); + + Affine raff; + affine_from_group(&raff, &r); + + Field ry_bigint; + fiat_pasta_fp_from_montgomery(ry_bigint, raff.y); + + const bool ry_even = (ry_bigint[0] & 1) == 0; + + return (ry_even && fiat_pasta_fp_equals(raff.x, sig->rx)); +} diff --git a/crypto.h b/crypto.h index 5e301a2..117dbbf 100644 --- a/crypto.h +++ b/crypto.h @@ -136,6 +136,9 @@ bool generate_address(char *address, size_t len, const Affine *pub_key); void sign(Signature *sig, const Keypair *kp, const Transaction *transaction, const uint8_t network_id); bool verify(Signature *sig, const Compressed *pub, const Transaction *transaction, const uint8_t network_id); +bool sign_message(Signature *sig, const Keypair *kp, const uint8_t *msg, const size_t len, const uint8_t network_id); +bool verify_message(Signature *sig, const Compressed *pub, const uint8_t *msg, const size_t len, const uint8_t network_id); + void compress(Compressed *compressed, const Affine *pt); bool decompress(Affine *pt, const Compressed *compressed); diff --git a/unit_tests.c b/unit_tests.c index 6029665..6059f1a 100644 --- a/unit_tests.c +++ b/unit_tests.c @@ -168,12 +168,11 @@ bool sign_transaction(char *signature, const size_t len, txn.amount = amount; txn.token_locked = false; - Compressed pub_compressed; - compress(&pub_compressed, &kp.pub); - Signature sig; sign(&sig, &kp, &txn, network_id); + Compressed pub_compressed; + compress(&pub_compressed, &kp.pub); if (!verify(&sig, &pub_compressed, &txn, network_id)) { return false; } @@ -252,6 +251,41 @@ bool check_sign_tx(const char *account_number, return strcmp(signature, target) == 0; } +bool check_sign_message(const char *signer_priv_hex, const uint8_t *msg, const size_t len, + const uint8_t network_id, const char *target) +{ + Scalar priv_key; + if (!privkey_from_hex(priv_key, signer_priv_hex)) { + return false; + } + + Keypair kp; + scalar_copy(kp.priv, priv_key); + generate_pubkey(&kp.pub, priv_key); + + Signature sig; + if (!sign_message(&sig, &kp, msg, len, network_id)) { + return false; + } + + Compressed pub_compressed; + compress(&pub_compressed, &kp.pub); + if (!verify_message(&sig, &pub_compressed, msg, len, network_id)) { + return false; + } + + char signature[129]; + sig_to_hex(signature, sizeof(signature), sig); + + if (strcmp(signature, target) != 0) { + fprintf(stderr, "signature mismatch: expected=%s, got=%s\n", + target, signature); + return false; + } + + return true; +} + char *field_to_hex(char *hex, size_t len, const Field x) { assert(len == 65); hex[64] = '\0'; @@ -955,6 +989,78 @@ void test_sign_tx() { } } +void test_sign_message() { + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"Hello message signing!", 22, MAINNET_ID, + "323b8ceb03a61d9e1b6e36b5aae40c660989fb7a082d7821175c7acaf0bb860a0ecaa64d883c4fc849d003c7dba7f5b32f3296f7a20dc31c0da9eb7c4add9d70")); + + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"Hello message signing!", 22, TESTNET_ID, + "21d34c4dea1737e99b60b23f2f75e1a4f307e599f8e0e2c7812f0401a12f809404d5d98701fb82215ed60fc06e31c57f2dda19a852c991b1329502f95d865a37")); + + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"Hello message signing\"", 22, MAINNET_ID, + "22343219a00a6a96ea921ed5b123f04d3fed5c127679015ab517a47191fc3d6d0f6f91857051c42b246e8ac213364f7a75de17704409050f0aba7daeffdcf87e")); + + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"Hello message signing#", 22, MAINNET_ID, + "2f5c98cb977722a2d3ef62d2f8f56fdbc36958dd85a935f3fac2986e6ab7f2a923f0398a8a8ef6ade3aa484299939aa8c63c5dacd4c2e5bb08129a0a25dc9c99")); + + // TODO: Check with Izaak if signing empty messages is OK security wise with schnorr scheme + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"", 0, MAINNET_ID, + "2f55b066d17000d46f1e99557b478cf69bdef36c70cec0187dcb0eedf0957d65089944c833195fd7c12a640430936cce07449689c2d58effdf8d7215980d75ce")); + + // Check we are respecting the length passed + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + (uint8_t *)"1123", 0, MAINNET_ID, + "2f55b066d17000d46f1e99557b478cf69bdef36c70cec0187dcb0eedf0957d65089944c833195fd7c12a640430936cce07449689c2d58effdf8d7215980d75ce")); + + { + uint8_t bytes[] = { }; + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + bytes, sizeof(bytes), MAINNET_ID, + "2f55b066d17000d46f1e99557b478cf69bdef36c70cec0187dcb0eedf0957d65089944c833195fd7c12a640430936cce07449689c2d58effdf8d7215980d75ce")); + } + + { + uint8_t byte = 0; + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + &byte, sizeof(byte), MAINNET_ID, + "313950fd22463fd2b79d3b2a2ce9ddffe57b4c115442c20a0c247f07a56999b73b09bcedb217ea151c5557fb9a549cafaca72c2bbd21ac31c77c26f59a70fd0d")); + } + + { + uint8_t byte = 0xfe; + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + &byte, sizeof(byte), MAINNET_ID, + "091aaf54133ac3ec97c8db1bf9b47d11bed6741337d09c44af739eab4d6f961616234e7d5c4f74260ddb361558c7f06843cbc20fa8e25a87b39e0ff5c7377787")); + } + + { + uint8_t bytes[] = { 0x01, 0xfe, 0x74 }; + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + bytes, sizeof(bytes), MAINNET_ID, + "197c2fa73e4b4acd47ada0c97ff6c2cbda162dc812e0156c441f4b32993e48223b0b7dbe85a8270823726f65cc6f47dccfef0dd0c391fbb335a288d4694b538a")); + } + + { + uint8_t bytes[] = { + 0xaf, 0xac, 0xfa, 0xca, 0x12, 0xbc, 0x09, 0x00, + 0x5d, 0x99, 0x01, 0xd4, 0xe9, 0x9b, 0xce, 0xee, + 0x1d, 0x22, 0x46, 0x3f, 0xb7, 0x23, 0x9d, 0xb3, + 0x2a, 0x2c, 0xe9, 0xd7, 0xdf, 0xfe, 0x57, 0xb4, + 0xc1, 0x15, 0x44, 0x2c, 0x20, 0xa1, 0x00, 0x01, + 0xb1, 0x7f, 0x7b, 0x73, 0x1c, 0x57, 0x16, 0x1a, + 0x2e, 0x01, 0x3c, 0x50, 0x15, 0x1c, 0x54, 0xf2, + 0x45, 0x3b, 0x78, 0x45, 0x4e, 0xbc, 0x71, 0x83 + }; + assert(check_sign_message("164244176fddb5d769b7de2027469d027ad428fadcc0c02396e6280142efb718", + bytes, sizeof(bytes), MAINNET_ID, + "1b58d0c5a7ff38259ecf631a49cd7e2895631e1967564bccc73736c7b0ff3a443eaa2bcbf824c6dc1a4dd04c9fa7d128ee79688a44515320a58963d02ab3cb38")); + } +} + int main(int argc, char* argv[]) { printf("Running unit tests\n"); @@ -1006,6 +1112,8 @@ int main(int argc, char* argv[]) { test_sign_tx(); + test_sign_message(); + printf("Unit tests completed successfully\n"); return 0; From b5caef0b7df105999aad0f2507cbb999b8583d34 Mon Sep 17 00:00:00 2001 From: georgeee Date: Thu, 15 Jul 2021 19:26:27 +0300 Subject: [PATCH 2/3] Add interface convenient for call via FFI --- Makefile | 10 ++++++++-- crypto.c | 16 +++++++++++++++- crypto.h | 1 + default.nix | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 default.nix diff --git a/Makefile b/Makefile index 29e98de..acd8ee0 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,13 @@ unit_tests: $(OBJS) *.c *.h @./$@ %.o: %.c %.h - $(CC) -Wall -Werror $< -c + $(CC) -fPIC -Wall -Werror $< -c clean: - rm -rf *.o *.log reference_signer unit_tests + rm -rf *.o *.log *.so *.a reference_signer unit_tests + +libmina_signer.a: $(OBJS) + ar rcs $@ $(OBJS) + +libmina_signer.so: $(OBJS) + $(CC) -shared -o $@ $(OBJS) diff --git a/crypto.c b/crypto.c index dfe2af9..c8cbf1d 100644 --- a/crypto.c +++ b/crypto.c @@ -710,7 +710,10 @@ void generate_keypair(Keypair *keypair, uint32_t account) uint64_t priv_non_montgomery[4] = { 0, 0, 0, 0 }; FILE* fr = fopen("/dev/urandom", "r"); if (!fr) perror("urandom"), exit(EXIT_FAILURE); - fread((void*)priv_non_montgomery, sizeof(uint8_t), 32, fr); + int fres = fread((void*)priv_non_montgomery, sizeof(uint8_t), 32, fr); + if (fres != 0) { + // TODO handle the error of fread + } fclose(fr), fr = NULL; // Make sure the private key is in [0, p) @@ -1128,6 +1131,17 @@ bool sign_message(Signature *sig, const Keypair *kp, const uint8_t *msg, const s return true; } +bool verify_message_string(const char * sig_raw, const char * pub_compressed_raw, const char * msg, const size_t len, uint8_t network_id) +{ + Signature sig; + Compressed pub_compressed; + pub_compressed.is_odd = pub_compressed_raw[sizeof(Field)] != 0; + fiat_pasta_fp_to_montgomery(pub_compressed.x, (uint64_t*) pub_compressed_raw); + fiat_pasta_fp_to_montgomery(sig.rx, (uint64_t*) sig_raw); + fiat_pasta_fq_to_montgomery(sig.s, (uint64_t*) (sig_raw + sizeof(Field))); + return verify_message(&sig, &pub_compressed, (uint8_t*) msg, len, network_id); +} + bool verify_message(Signature *sig, const Compressed *pub_compressed, const uint8_t *msg, const size_t len, uint8_t network_id) { // Convert msg bytes to ROInput diff --git a/crypto.h b/crypto.h index 117dbbf..4cce68f 100644 --- a/crypto.h +++ b/crypto.h @@ -138,6 +138,7 @@ bool verify(Signature *sig, const Compressed *pub, const Transaction *transactio bool sign_message(Signature *sig, const Keypair *kp, const uint8_t *msg, const size_t len, const uint8_t network_id); bool verify_message(Signature *sig, const Compressed *pub, const uint8_t *msg, const size_t len, const uint8_t network_id); +bool verify_message_string(const char * sig_raw, const char * pub_compressed_raw, const char * msg, const size_t len, uint8_t network_id); void compress(Compressed *compressed, const Affine *pt); bool decompress(Affine *pt, const Compressed *compressed); diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..224e707 --- /dev/null +++ b/default.nix @@ -0,0 +1,14 @@ +with import {}; +stdenv.mkDerivation { + name = "c-reference-signer-1.0"; + buildInputs = [ stdenv glibc ]; + src = ./.; + + buildPhase = '' + make clean libmina_signer.so + ''; + installPhase = '' + mkdir -p $out/lib + mv libmina_signer.so $out/lib + ''; +} From 6f492281cdf0206aa1771019e322a1fce03f2b35 Mon Sep 17 00:00:00 2001 From: georgeee Date: Wed, 10 Apr 2024 12:21:03 +0200 Subject: [PATCH 3/3] Export headers in nix derivation --- default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 224e707..6bdaf7c 100644 --- a/default.nix +++ b/default.nix @@ -8,7 +8,8 @@ stdenv.mkDerivation { make clean libmina_signer.so ''; installPhase = '' - mkdir -p $out/lib + mkdir -p $out/lib $out/headers mv libmina_signer.so $out/lib + cp *.h $out/headers ''; }