/* Copyright (c) 2013-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file crypto_ed25519.c * * \brief Wrapper code for an ed25519 implementation. * * Ed25519 is a Schnorr signature on a Twisted Edwards curve, defined * by Dan Bernstein. For more information, see https://ed25519.cr.yp.to/ * * This module wraps our choice of Ed25519 backend, and provides a few * convenience functions for checking and generating signatures. It also * provides Tor-specific tools for key blinding and for converting Ed25519 * keys to and from the corresponding Curve25519 keys. */ #define CRYPTO_ED25519_PRIVATE #include "orconfig.h" #ifdef HAVE_SYS_STAT_H #include #endif #include "crypto_curve25519.h" #include "crypto_digest.h" #include "crypto_ed25519.h" #include "crypto_format.h" #include "crypto_rand.h" #include "crypto_util.h" #include "torlog.h" #include "util.h" #include "util_format.h" #include "ed25519/ref10/ed25519_ref10.h" #include "ed25519/donna/ed25519_donna_tor.h" static void pick_ed25519_impl(void); /** An Ed25519 implementation, as a set of function pointers. */ typedef struct { int (*selftest)(void); int (*seckey)(unsigned char *); int (*seckey_expand)(unsigned char *, const unsigned char *); int (*pubkey)(unsigned char *, const unsigned char *); int (*keygen)(unsigned char *, unsigned char *); int (*open)(const unsigned char *, const unsigned char *, size_t, const unsigned char *); int (*sign)(unsigned char *, const unsigned char *, size_t, const unsigned char *, const unsigned char *); int (*open_batch)(const unsigned char **, size_t *, const unsigned char **, const unsigned char **, size_t, int *); int (*blind_secret_key)(unsigned char *, const unsigned char *, const unsigned char *); int (*blind_public_key)(unsigned char *, const unsigned char *, const unsigned char *); int (*pubkey_from_curve25519_pubkey)(unsigned char *, const unsigned char *, int); int (*ed25519_scalarmult_with_group_order)(unsigned char *, const unsigned char *); } ed25519_impl_t; /** The Ref10 Ed25519 implementation. This one is pure C and lightly * optimized. */ static const ed25519_impl_t impl_ref10 = { NULL, ed25519_ref10_seckey, ed25519_ref10_seckey_expand, ed25519_ref10_pubkey, ed25519_ref10_keygen, ed25519_ref10_open, ed25519_ref10_sign, NULL, ed25519_ref10_blind_secret_key, ed25519_ref10_blind_public_key, ed25519_ref10_pubkey_from_curve25519_pubkey, ed25519_ref10_scalarmult_with_group_order, }; /** The Ref10 Ed25519 implementation. This one is heavily optimized, but still * mostly C. The C still tends to be heavily platform-specific. */ static const ed25519_impl_t impl_donna = { ed25519_donna_selftest, ed25519_donna_seckey, ed25519_donna_seckey_expand, ed25519_donna_pubkey, ed25519_donna_keygen, ed25519_donna_open, ed25519_donna_sign, ed25519_sign_open_batch_donna, ed25519_donna_blind_secret_key, ed25519_donna_blind_public_key, ed25519_donna_pubkey_from_curve25519_pubkey, ed25519_donna_scalarmult_with_group_order, }; /** Which Ed25519 implementation are we using? NULL if we haven't decided * yet. */ static const ed25519_impl_t *ed25519_impl = NULL; /** Helper: Return our chosen Ed25519 implementation. * * This should only be called after we've picked an implementation, but * it _does_ recover if you forget this. **/ static inline const ed25519_impl_t * get_ed_impl(void) { if (BUG(ed25519_impl == NULL)) { pick_ed25519_impl(); // LCOV_EXCL_LINE - We always call ed25519_init(). } return ed25519_impl; } #ifdef TOR_UNIT_TESTS /** For testing: used to remember our actual choice of Ed25519 * implementation */ static const ed25519_impl_t *saved_ed25519_impl = NULL; /** For testing: Use the Ed25519 implementation called name until * crypto_ed25519_testing_restore_impl is called. Recognized names are * "donna" and "ref10". */ void crypto_ed25519_testing_force_impl(const char *name) { tor_assert(saved_ed25519_impl == NULL); saved_ed25519_impl = ed25519_impl; if (! strcmp(name, "donna")) { ed25519_impl = &impl_donna; } else { tor_assert(!strcmp(name, "ref10")); ed25519_impl = &impl_ref10; } } /** For testing: go back to whatever Ed25519 implementation we had picked * before crypto_ed25519_testing_force_impl was called. */ void crypto_ed25519_testing_restore_impl(void) { ed25519_impl = saved_ed25519_impl; saved_ed25519_impl = NULL; } #endif /* defined(TOR_UNIT_TESTS) */ /** * Initialize a new ed25519 secret key in seckey_out. If * extra_strong, take the RNG inputs directly from the operating * system. Return 0 on success, -1 on failure. */ int ed25519_secret_key_generate(ed25519_secret_key_t *seckey_out, int extra_strong) { int r; uint8_t seed[32]; if (extra_strong) crypto_strongest_rand(seed, sizeof(seed)); else crypto_rand((char*)seed, sizeof(seed)); r = get_ed_impl()->seckey_expand(seckey_out->seckey, seed); memwipe(seed, 0, sizeof(seed)); return r < 0 ? -1 : 0; } /** * Given a 32-byte random seed in seed, expand it into an ed25519 * secret key in seckey_out. Return 0 on success, -1 on failure. */ int ed25519_secret_key_from_seed(ed25519_secret_key_t *seckey_out, const uint8_t *seed) { if (get_ed_impl()->seckey_expand(seckey_out->seckey, seed) < 0) return -1; return 0; } /** * Given a secret key in seckey, expand it into an * ed25519 public key. Return 0 on success, -1 on failure. */ int ed25519_public_key_generate(ed25519_public_key_t *pubkey_out, const ed25519_secret_key_t *seckey) { if (get_ed_impl()->pubkey(pubkey_out->pubkey, seckey->seckey) < 0) return -1; return 0; } /** Generate a new ed25519 keypair in keypair_out. If * extra_strong is set, try to mix some system entropy into the key * generation process. Return 0 on success, -1 on failure. */ int ed25519_keypair_generate(ed25519_keypair_t *keypair_out, int extra_strong) { if (ed25519_secret_key_generate(&keypair_out->seckey, extra_strong) < 0) return -1; if (ed25519_public_key_generate(&keypair_out->pubkey, &keypair_out->seckey)<0) return -1; return 0; } /** Return true iff 'pubkey' is set to zero (eg to indicate that it is not * set). */ int ed25519_public_key_is_zero(const ed25519_public_key_t *pubkey) { return tor_mem_is_zero((char*)pubkey->pubkey, ED25519_PUBKEY_LEN); } /* Return a heap-allocated array that contains msg prefixed by the * string prefix_str. Set final_msg_len_out to the size of the * final array. If an error occurred, return NULL. It's the responsibility of * the caller to free the returned array. */ static uint8_t * get_prefixed_msg(const uint8_t *msg, size_t msg_len, const char *prefix_str, size_t *final_msg_len_out) { size_t prefixed_msg_len, prefix_len; uint8_t *prefixed_msg; tor_assert(prefix_str); tor_assert(final_msg_len_out); prefix_len = strlen(prefix_str); /* msg_len + strlen(prefix_str) must not overflow. */ if (msg_len > SIZE_T_CEILING - prefix_len) { return NULL; } prefixed_msg_len = msg_len + prefix_len; prefixed_msg = tor_malloc_zero(prefixed_msg_len); memcpy(prefixed_msg, prefix_str, prefix_len); memcpy(prefixed_msg + prefix_len, msg, msg_len); *final_msg_len_out = prefixed_msg_len; return prefixed_msg; } /** * Set signature_out to a signature of the len-byte message * msg, using the secret and public key in keypair. * * Return 0 if we successfully signed the message, otherwise return -1. */ int ed25519_sign(ed25519_signature_t *signature_out, const uint8_t *msg, size_t len, const ed25519_keypair_t *keypair) { if (get_ed_impl()->sign(signature_out->sig, msg, len, keypair->seckey.seckey, keypair->pubkey.pubkey) < 0) { return -1; } return 0; } /** * Like ed25519_sign(), but also prefix msg with prefix_str * before signing. prefix_str must be a NUL-terminated string. */ MOCK_IMPL(int, ed25519_sign_prefixed,(ed25519_signature_t *signature_out, const uint8_t *msg, size_t msg_len, const char *prefix_str, const ed25519_keypair_t *keypair)) { int retval; size_t prefixed_msg_len; uint8_t *prefixed_msg; tor_assert(prefix_str); prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, &prefixed_msg_len); if (BUG(!prefixed_msg)) { /* LCOV_EXCL_START -- only possible when the message and prefix are * ridiculously huge */ log_warn(LD_GENERAL, "Failed to get prefixed msg."); return -1; /* LCOV_EXCL_STOP */ } retval = ed25519_sign(signature_out, prefixed_msg, prefixed_msg_len, keypair); tor_free(prefixed_msg); return retval; } /** * Check whether if signature is a valid signature for the * len-byte message in msg made with the key pubkey. * * Return 0 if the signature is valid; -1 if it isn't. */ MOCK_IMPL(int, ed25519_checksig,(const ed25519_signature_t *signature, const uint8_t *msg, size_t len, const ed25519_public_key_t *pubkey)) { return get_ed_impl()->open(signature->sig, msg, len, pubkey->pubkey) < 0 ? -1 : 0; } /** * Like ed2519_checksig(), but also prefix msg with prefix_str * before verifying signature. prefix_str must be a NUL-terminated * string. */ int ed25519_checksig_prefixed(const ed25519_signature_t *signature, const uint8_t *msg, size_t msg_len, const char *prefix_str, const ed25519_public_key_t *pubkey) { int retval; size_t prefixed_msg_len; uint8_t *prefixed_msg; prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, &prefixed_msg_len); if (BUG(!prefixed_msg)) { /* LCOV_EXCL_START -- only possible when the message and prefix are * ridiculously huge */ log_warn(LD_GENERAL, "Failed to get prefixed msg."); return -1; /* LCOV_EXCL_STOP */ } retval = ed25519_checksig(signature, prefixed_msg, prefixed_msg_len, pubkey); tor_free(prefixed_msg); return retval; } /** Validate every signature among those in checkable, which contains * exactly n_checkable elements. If okay_out is non-NULL, set * the i'th element of okay_out to 1 if the i'th element of * checkable is valid, and to 0 otherwise. Return 0 if every signature * was valid. Otherwise return -N, where N is the number of invalid * signatures. */ MOCK_IMPL(int, ed25519_checksig_batch,(int *okay_out, const ed25519_checkable_t *checkable, int n_checkable)) { int i, res; const ed25519_impl_t *impl = get_ed_impl(); if (impl->open_batch == NULL) { /* No batch verification implementation available, fake it by checking the * each signature individually. */ res = 0; for (i = 0; i < n_checkable; ++i) { const ed25519_checkable_t *ch = &checkable[i]; int r = ed25519_checksig(&ch->signature, ch->msg, ch->len, ch->pubkey); if (r < 0) --res; if (okay_out) okay_out[i] = (r == 0); } } else { /* ed25519-donna style batch verification available. * * Theoretically, this should only be called if n_checkable >= 3, since * that's the threshold where the batch verification actually kicks in, * but the only difference is a few mallocs/frees. */ const uint8_t **ms; size_t *lens; const uint8_t **pks; const uint8_t **sigs; int *oks; int all_ok; ms = tor_calloc(n_checkable, sizeof(uint8_t*)); lens = tor_calloc(n_checkable, sizeof(size_t)); pks = tor_calloc(n_checkable, sizeof(uint8_t*)); sigs = tor_calloc(n_checkable, sizeof(uint8_t*)); oks = okay_out ? okay_out : tor_calloc(n_checkable, sizeof(int)); for (i = 0; i < n_checkable; ++i) { ms[i] = checkable[i].msg; lens[i] = checkable[i].len; pks[i] = checkable[i].pubkey->pubkey; sigs[i] = checkable[i].signature.sig; oks[i] = 0; } res = 0; all_ok = impl->open_batch(ms, lens, pks, sigs, n_checkable, oks); for (i = 0; i < n_checkable; ++i) { if (!oks[i]) --res; } /* XXX: For now sanity check oks with the return value. Once we have * more confidence in the code, if `all_ok == 0` we can skip iterating * over oks since all the signatures were found to be valid. */ tor_assert(((res == 0) && !all_ok) || ((res < 0) && all_ok)); tor_free(ms); tor_free(lens); tor_free(pks); tor_free(sigs); if (! okay_out) tor_free(oks); } return res; } /** * Given a curve25519 keypair in inp, generate a corresponding * ed25519 keypair in out, and set signbit_out to the * sign bit of the X coordinate of the ed25519 key. * * NOTE THAT IT IS PROBABLY NOT SAFE TO USE THE GENERATED KEY FOR ANYTHING * OUTSIDE OF WHAT'S PRESENTED IN PROPOSAL 228. In particular, it's probably * not a great idea to use it to sign attacker-supplied anything. */ int ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out, int *signbit_out, const curve25519_keypair_t *inp) { const char string[] = "Derive high part of ed25519 key from curve25519 key"; ed25519_public_key_t pubkey_check; crypto_digest_t *ctx; uint8_t sha512_output[DIGEST512_LEN]; memcpy(out->seckey.seckey, inp->seckey.secret_key, 32); ctx = crypto_digest512_new(DIGEST_SHA512); crypto_digest_add_bytes(ctx, (const char*)out->seckey.seckey, 32); crypto_digest_add_bytes(ctx, (const char*)string, sizeof(string)); crypto_digest_get_digest(ctx, (char *)sha512_output, sizeof(sha512_output)); crypto_digest_free(ctx); memcpy(out->seckey.seckey + 32, sha512_output, 32); ed25519_public_key_generate(&out->pubkey, &out->seckey); *signbit_out = out->pubkey.pubkey[31] >> 7; ed25519_public_key_from_curve25519_public_key(&pubkey_check, &inp->pubkey, *signbit_out); tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); memwipe(&pubkey_check, 0, sizeof(pubkey_check)); memwipe(sha512_output, 0, sizeof(sha512_output)); return 0; } /** * Given a curve25519 public key and sign bit of X coordinate of the ed25519 * public key, generate the corresponding ed25519 public key. */ int ed25519_public_key_from_curve25519_public_key(ed25519_public_key_t *pubkey, const curve25519_public_key_t *pubkey_in, int signbit) { return get_ed_impl()->pubkey_from_curve25519_pubkey(pubkey->pubkey, pubkey_in->public_key, signbit); } /** * Given an ed25519 keypair in inp, generate a corresponding * ed25519 keypair in out, blinded by the corresponding 32-byte input * in 'param'. * * Tor uses key blinding for the "next-generation" hidden services design: * service descriptors are encrypted with a key derived from the service's * long-term public key, and then signed with (and stored at a position * indexed by) a short-term key derived by blinding the long-term keys. * * Return 0 if blinding was successful, else return -1. */ int ed25519_keypair_blind(ed25519_keypair_t *out, const ed25519_keypair_t *inp, const uint8_t *param) { ed25519_public_key_t pubkey_check; get_ed_impl()->blind_secret_key(out->seckey.seckey, inp->seckey.seckey, param); if (ed25519_public_blind(&pubkey_check, &inp->pubkey, param) < 0) { return -1; } ed25519_public_key_generate(&out->pubkey, &out->seckey); tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); memwipe(&pubkey_check, 0, sizeof(pubkey_check)); return 0; } /** * Given an ed25519 public key in inp, generate a corresponding blinded * public key in out, blinded with the 32-byte parameter in * param. Return 0 on success, -1 on railure. */ int ed25519_public_blind(ed25519_public_key_t *out, const ed25519_public_key_t *inp, const uint8_t *param) { return get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param); } /** * Store seckey unencrypted to filename, marking it with tag. * Return 0 on success, -1 on failure. */ int ed25519_seckey_write_to_file(const ed25519_secret_key_t *seckey, const char *filename, const char *tag) { return crypto_write_tagged_contents_to_file(filename, "ed25519v1-secret", tag, seckey->seckey, sizeof(seckey->seckey)); } /** * Read seckey unencrypted from filename, storing it into * seckey_out. Set *tag_out to the tag it was marked with. * Return 0 on success, -1 on failure. */ int ed25519_seckey_read_from_file(ed25519_secret_key_t *seckey_out, char **tag_out, const char *filename) { ssize_t len; len = crypto_read_tagged_contents_from_file(filename, "ed25519v1-secret", tag_out, seckey_out->seckey, sizeof(seckey_out->seckey)); if (len == sizeof(seckey_out->seckey)) { return 0; } else if (len >= 0) { errno = EINVAL; } tor_free(*tag_out); return -1; } /** * Store pubkey unencrypted to filename, marking it with tag. * Return 0 on success, -1 on failure. */ int ed25519_pubkey_write_to_file(const ed25519_public_key_t *pubkey, const char *filename, const char *tag) { return crypto_write_tagged_contents_to_file(filename, "ed25519v1-public", tag, pubkey->pubkey, sizeof(pubkey->pubkey)); } /** * Store pubkey unencrypted to filename, marking it with tag. * Return 0 on success, -1 on failure. */ int ed25519_pubkey_read_from_file(ed25519_public_key_t *pubkey_out, char **tag_out, const char *filename) { ssize_t len; len = crypto_read_tagged_contents_from_file(filename, "ed25519v1-public", tag_out, pubkey_out->pubkey, sizeof(pubkey_out->pubkey)); if (len == sizeof(pubkey_out->pubkey)) { return 0; } else if (len >= 0) { errno = EINVAL; } tor_free(*tag_out); return -1; } /** Release all storage held for kp. */ void ed25519_keypair_free_(ed25519_keypair_t *kp) { if (! kp) return; memwipe(kp, 0, sizeof(*kp)); tor_free(kp); } /** Return true iff key1 and key2 are the same public key. */ int ed25519_pubkey_eq(const ed25519_public_key_t *key1, const ed25519_public_key_t *key2) { tor_assert(key1); tor_assert(key2); return tor_memeq(key1->pubkey, key2->pubkey, ED25519_PUBKEY_LEN); } /** * Set dest to contain the same key as src. */ void ed25519_pubkey_copy(ed25519_public_key_t *dest, const ed25519_public_key_t *src) { tor_assert(dest); tor_assert(src); memcpy(dest, src, sizeof(ed25519_public_key_t)); } /** Check whether the given Ed25519 implementation seems to be working. * If so, return 0; otherwise return -1. */ MOCK_IMPL(STATIC int, ed25519_impl_spot_check,(void)) { static const uint8_t alicesk[32] = { 0xc5,0xaa,0x8d,0xf4,0x3f,0x9f,0x83,0x7b, 0xed,0xb7,0x44,0x2f,0x31,0xdc,0xb7,0xb1, 0x66,0xd3,0x85,0x35,0x07,0x6f,0x09,0x4b, 0x85,0xce,0x3a,0x2e,0x0b,0x44,0x58,0xf7 }; static const uint8_t alicepk[32] = { 0xfc,0x51,0xcd,0x8e,0x62,0x18,0xa1,0xa3, 0x8d,0xa4,0x7e,0xd0,0x02,0x30,0xf0,0x58, 0x08,0x16,0xed,0x13,0xba,0x33,0x03,0xac, 0x5d,0xeb,0x91,0x15,0x48,0x90,0x80,0x25 }; static const uint8_t alicemsg[2] = { 0xaf, 0x82 }; static const uint8_t alicesig[64] = { 0x62,0x91,0xd6,0x57,0xde,0xec,0x24,0x02, 0x48,0x27,0xe6,0x9c,0x3a,0xbe,0x01,0xa3, 0x0c,0xe5,0x48,0xa2,0x84,0x74,0x3a,0x44, 0x5e,0x36,0x80,0xd7,0xdb,0x5a,0xc3,0xac, 0x18,0xff,0x9b,0x53,0x8d,0x16,0xf2,0x90, 0xae,0x67,0xf7,0x60,0x98,0x4d,0xc6,0x59, 0x4a,0x7c,0x15,0xe9,0x71,0x6e,0xd2,0x8d, 0xc0,0x27,0xbe,0xce,0xea,0x1e,0xc4,0x0a }; const ed25519_impl_t *impl = get_ed_impl(); uint8_t sk[ED25519_SECKEY_LEN]; uint8_t pk[ED25519_PUBKEY_LEN]; uint8_t sig[ED25519_SIG_LEN]; int r = 0; /* Some implementations (eg: The modified Ed25519-donna) have handy self-test * code that sanity-checks the internals. If present, use that to screen out * catastrophic errors like massive compiler failure. */ if (impl->selftest && impl->selftest() != 0) goto fail; /* Validate results versus known answer tests. People really should be * running "make test" instead of relying on this, but it's better than * nothing. * * Test vectors taken from "EdDSA & Ed25519 - 6. Test Vectors for Ed25519 * (TEST3)" (draft-josefsson-eddsa-ed25519-03). */ /* Key expansion, public key derivation. */ if (impl->seckey_expand(sk, alicesk) < 0) goto fail; if (impl->pubkey(pk, sk) < 0) goto fail; if (fast_memneq(pk, alicepk, ED25519_PUBKEY_LEN)) goto fail; /* Signing, verification. */ if (impl->sign(sig, alicemsg, sizeof(alicemsg), sk, pk) < 0) return -1; if (fast_memneq(sig, alicesig, ED25519_SIG_LEN)) return -1; if (impl->open(sig, alicemsg, sizeof(alicemsg), pk) < 0) return -1; /* XXX/yawning: Someone that's more paranoid than I am, can write "Assume * ref0 is canonical, and fuzz impl against it" if they want, but I doubt * that will catch anything that the known answer tests won't. */ goto end; // LCOV_EXCL_START -- We can only reach this if our ed25519 implementation is // broken. fail: r = -1; // LCOV_EXCL_STOP end: return r; } /** Force the Ed25519 implementation to a given one, without sanity checking * the output. Used for testing. */ void ed25519_set_impl_params(int use_donna) { if (use_donna) ed25519_impl = &impl_donna; else ed25519_impl = &impl_ref10; } /** Choose whether to use the Ed25519-donna implementation. */ static void pick_ed25519_impl(void) { ed25519_impl = &impl_donna; if (ed25519_impl_spot_check() == 0) return; /* LCOV_EXCL_START * unreachable unless ed25519_donna is broken */ log_warn(LD_CRYPTO, "The Ed25519-donna implementation seems broken; using " "the ref10 implementation."); ed25519_impl = &impl_ref10; /* LCOV_EXCL_STOP */ } /* Initialize the Ed25519 implementation. This is necessary if you're * going to use them in a multithreaded setting, and not otherwise. */ void ed25519_init(void) { pick_ed25519_impl(); } /* Return true if point is the identity element of the ed25519 group. */ static int ed25519_point_is_identity_element(const uint8_t *point) { /* The identity element in ed25159 is the point with coordinates (0,1). */ static const uint8_t ed25519_identity[32] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; tor_assert(sizeof(ed25519_identity) == ED25519_PUBKEY_LEN); return tor_memeq(point, ed25519_identity, sizeof(ed25519_identity)); } /** Validate pubkey to ensure that it has no torsion component. * Return 0 if pubkey is valid, else return -1. */ int ed25519_validate_pubkey(const ed25519_public_key_t *pubkey) { uint8_t result[32] = {9}; /* First check that we were not given the identity element */ if (ed25519_point_is_identity_element(pubkey->pubkey)) { log_warn(LD_CRYPTO, "ed25519 pubkey is the identity"); return -1; } /* For any point on the curve, doing l*point should give the identity element * (where l is the group order). Do the computation and check that the * identity element is returned. */ if (get_ed_impl()->ed25519_scalarmult_with_group_order(result, pubkey->pubkey) < 0) { log_warn(LD_CRYPTO, "ed25519 group order scalarmult failed"); return -1; } if (!ed25519_point_is_identity_element(result)) { log_warn(LD_CRYPTO, "ed25519 validation failed"); return -1; } return 0; }