diff --git a/libraries/Crypto/Ed25519.cpp b/libraries/Crypto/Ed25519.cpp index 5732e73b..cad8f836 100644 --- a/libraries/Crypto/Ed25519.cpp +++ b/libraries/Crypto/Ed25519.cpp @@ -116,16 +116,21 @@ static limb_t const numQ[NUM_LIMBS_256BIT] PROGMEM = { /** * \brief Signs a message using a specific Ed25519 private key. * + * \param mode Ed25519 variant: Ed25519 / Ed25519ph / Ed25519ctx. * \param signature The signature value. * \param privateKey The private key to use to sign the message. * \param publicKey The public key corresponding to \a privateKey. * \param message Points to the message to be signed. - * \param len The length of the \a message to be signed. + * \param msglen The length of the \a message to be signed. + * \param context Points to the context (optional for Ed25519ph, mandatory for Ed25519ctx). + * \param ctxlen The length of the \a context. Up to 255 bytes. * * \sa verify(), derivePublicKey() */ -void Ed25519::sign(uint8_t signature[64], const uint8_t privateKey[32], - const uint8_t publicKey[32], const void *message, size_t len) +void Ed25519::sign(Ed25519::Mode mode, uint8_t signature[64], + const uint8_t privateKey[32], const uint8_t publicKey[32], + const void *message, size_t msglen, + const void *context, size_t ctxlen) { SHA512 hash; uint8_t *buf = (uint8_t *)(hash.state.w); // Reuse hash buffer to save memory. @@ -134,26 +139,37 @@ void Ed25519::sign(uint8_t signature[64], const uint8_t privateKey[32], limb_t k[NUM_LIMBS_256BIT]; limb_t t[NUM_LIMBS_512BIT + 1]; Point rB; + uint8_t prefix[32]; // Derive the secret scalar a and the message prefix from the private key. deriveKeys(&hash, a, privateKey); + memcpy(prefix, buf + 32, 32); - // Hash the prefix and the message to derive r. + // Hash dom2(), the prefix and the message to derive r. hash.reset(); - hash.update(buf + 32, 32); - hash.update(message, len); + hashDom2(&hash, mode, context, ctxlen); + hash.update(prefix, 32); + if (mode != Mode::ph) + hash.update(message, msglen); + else + hashPH(&hash, message, msglen); hash.finalize(buf, 0); reduceQFromBuffer(r, buf, t); + clean(prefix); // Encode rB into the first half of the signature buffer as R. mul(rB, r); encodePoint(signature, rB); - // Hash R, A, and the message to get k. + // Hash dom2(), R, A, and the message to get k. hash.reset(); + hashDom2(&hash, mode, context, ctxlen); hash.update(signature, 32); // R hash.update(publicKey, 32); // A - hash.update(message, len); + if (mode != Mode::ph) + hash.update(message, msglen); + else + hashPH(&hash, message, msglen); hash.finalize(buf, 0); reduceQFromBuffer(k, buf, t); @@ -176,18 +192,23 @@ void Ed25519::sign(uint8_t signature[64], const uint8_t privateKey[32], /** * \brief Verifies a signature using a specific Ed25519 public key. * + * \param mode Ed25519 variant: Ed25519 / Ed25519ph / Ed25519ctx. * \param signature The signature value to be verified. * \param publicKey The public key to use to verify the signature. * \param message The message whose signature is to be verified. - * \param len The length of the \a message to be verified. + * \param msglen The length of the \a message to be verified. + * \param context Points to the context (optional for Ed25519ph, mandatory for Ed25519ctx). + * \param ctxlen The length of the \a context. Up to 255 bytes. * * \return Returns true if the \a signature is valid for \a message; * or false if the \a signature is not valid. * * \sa sign() */ -bool Ed25519::verify(const uint8_t signature[64], const uint8_t publicKey[32], - const void *message, size_t len) +bool Ed25519::verify(Ed25519::Mode mode, const uint8_t signature[64], + const uint8_t publicKey[32], + const void *message, size_t msglen, + const void *context, size_t ctxlen) { SHA512 hash; Point A; @@ -201,9 +222,13 @@ bool Ed25519::verify(const uint8_t signature[64], const uint8_t publicKey[32], if (decodePoint(A, publicKey) && decodePoint(R, signature)) { // Reconstruct the k value from the signing step. hash.reset(); + hashDom2(&hash, mode, context, ctxlen); hash.update(signature, 32); hash.update(publicKey, 32); - hash.update(message, len); + if (mode != Mode::ph) + hash.update(message, msglen); + else + hashPH(&hash, message, msglen); hash.finalize(k, 0); // Calculate s * B. The s value is stored temporarily in kA.t. @@ -640,3 +665,33 @@ void Ed25519::deriveKeys(SHA512 *hash, limb_t *a, const uint8_t privateKey[32]) // Unpack the first half of the hash value into "a". BigNumberUtil::unpackLE(a, NUM_LIMBS_256BIT, buf, 32); } + +/** +* \brief The internal PH function used in Ed25519ph +* calculates SHA hash on the input message and updates the main hash +*/ +void Ed25519::hashPH(SHA512 *hash, const void *message, size_t len) +{ + SHA512 sha; + uint64_t *buf = sha.state.w; // Reuse hash buffer to save memory. + sha.update(message, len); + sha.finalize(buf, 0); + + hash->update(buf, 64); +} +/** +* \brief The internal dom2 function used in Ed25519ph and Ed25519ctx +* updates the main hash with a fixed string, flag and context bytes +*/ +void Ed25519::hashDom2(SHA512 *hash, Mode mode, const void *ctx, size_t len) +{ + if (mode == Mode::standard) + return; + static const char prefix[] PROGMEM = "SigEd25519 no Ed25519 collisions"; + hash->update(prefix, 32); + uint8_t phflag = (mode == Mode::ph ? 1 : 0); + uint8_t fc[] = { phflag, (uint8_t)len }; + hash->update(fc, 2); + if (ctx && len) + hash->update(ctx, len); +} diff --git a/libraries/Crypto/Ed25519.h b/libraries/Crypto/Ed25519.h index bbdd9f20..69abbdf9 100644 --- a/libraries/Crypto/Ed25519.h +++ b/libraries/Crypto/Ed25519.h @@ -29,11 +29,33 @@ class Ed25519 { public: + enum class Mode { + standard, + ph, + ctx, + }; + static void sign(uint8_t signature[64], const uint8_t privateKey[32], - const uint8_t publicKey[32], const void *message, - size_t len); + const uint8_t publicKey[32], const void *message, size_t len) + { + sign(Mode::standard, signature, privateKey, publicKey, message, len); + } + static bool verify(const uint8_t signature[64], const uint8_t publicKey[32], - const void *message, size_t len); + const void *message, size_t len) + { + return verify(Mode::standard, signature, publicKey, message, len); + } + + static void sign(Mode mode, uint8_t signature[64], + const uint8_t privateKey[32], const uint8_t publicKey[32], + const void *message, size_t msglen, + const void *context = nullptr, size_t ctxlen = 0); + + static bool verify(Mode mode, const uint8_t signature[64], + const uint8_t publicKey[32], + const void *message, size_t msglen, + const void *context = nullptr, size_t ctxlen = 0); static void generatePrivateKey(uint8_t privateKey[32]); static void derivePublicKey(uint8_t publicKey[32], const uint8_t privateKey[32]); @@ -66,6 +88,9 @@ class Ed25519 static bool decodePoint(Point &point, const uint8_t *buf); static void deriveKeys(SHA512 *hash, limb_t *a, const uint8_t privateKey[32]); + + static void hashPH(SHA512 *hash, const void *message, size_t len); + static void hashDom2(SHA512 *hash, Mode mode, const void *ctx, size_t len); }; #endif diff --git a/libraries/Crypto/examples/TestEd25519/TestEd25519.ino b/libraries/Crypto/examples/TestEd25519/TestEd25519.ino index 4f616cad..b8a2c784 100644 --- a/libraries/Crypto/examples/TestEd25519/TestEd25519.ino +++ b/libraries/Crypto/examples/TestEd25519/TestEd25519.ino @@ -33,10 +33,13 @@ This example runs tests on the Ed25519 algorithm. struct TestVector { const char *name; + Ed25519::Mode mode; uint8_t privateKey[32]; uint8_t publicKey[32]; - uint8_t message[2]; - size_t len; + uint8_t message[16]; + size_t msglen; + uint8_t context[16] = {}; + size_t ctxlen = 0; uint8_t signature[64]; }; @@ -44,6 +47,7 @@ struct TestVector // https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05 static TestVector const testVectorEd25519_1 PROGMEM = { .name = "Ed25519 #1", + .mode = Ed25519::Mode::standard, .privateKey = {0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, @@ -53,7 +57,7 @@ static TestVector const testVectorEd25519_1 PROGMEM = { 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a}, .message = {0x00, 0x00}, - .len = 0, + .msglen = 0, .signature = {0xe5, 0x56, 0x43, 0x00, 0xc3, 0x60, 0xac, 0x72, 0x90, 0x86, 0xe2, 0xcc, 0x80, 0x6e, 0x82, 0x8a, 0x84, 0x87, 0x7f, 0x1e, 0xb8, 0xe5, 0xd9, 0x74, @@ -65,6 +69,7 @@ static TestVector const testVectorEd25519_1 PROGMEM = { }; static TestVector const testVectorEd25519_2 PROGMEM = { .name = "Ed25519 #2", + .mode = Ed25519::Mode::standard, .privateKey = {0x4c, 0xcd, 0x08, 0x9b, 0x28, 0xff, 0x96, 0xda, 0x9d, 0xb6, 0xc3, 0x46, 0xec, 0x11, 0x4e, 0x0f, 0x5b, 0x8a, 0x31, 0x9f, 0x35, 0xab, 0xa6, 0x24, @@ -74,7 +79,7 @@ static TestVector const testVectorEd25519_2 PROGMEM = { 0x9c, 0x98, 0x2c, 0xcf, 0x2e, 0xc4, 0x96, 0x8c, 0xc0, 0xcd, 0x55, 0xf1, 0x2a, 0xf4, 0x66, 0x0c}, .message = {0x72, 0x00}, - .len = 1, + .msglen = 1, .signature = {0x92, 0xa0, 0x09, 0xa9, 0xf0, 0xd4, 0xca, 0xb8, 0x72, 0x0e, 0x82, 0x0b, 0x5f, 0x64, 0x25, 0x40, 0xa2, 0xb2, 0x7b, 0x54, 0x16, 0x50, 0x3f, 0x8f, @@ -84,6 +89,54 @@ static TestVector const testVectorEd25519_2 PROGMEM = { 0x38, 0x7b, 0x2e, 0xae, 0xb4, 0x30, 0x2a, 0xee, 0xb0, 0x0d, 0x29, 0x16, 0x12, 0xbb, 0x0c, 0x00} }; +// https://datatracker.ietf.org/doc/html/rfc8032#section-7.2 test foo +static TestVector const testVectorEd25519ctx PROGMEM = { + .name = "Ed25519ctx", + .mode = Ed25519::Mode::ctx, + .privateKey = {0x03, 0x05, 0x33, 0x4e, 0x38, 0x1a, 0xf7, 0x8f, + 0x14, 0x1c, 0xb6, 0x66, 0xf6, 0x19, 0x9f, 0x57, + 0xbc, 0x34, 0x95, 0x33, 0x5a, 0x25, 0x6a, 0x95, + 0xbd, 0x2a, 0x55, 0xbf, 0x54, 0x66, 0x63, 0xf6}, + .publicKey = {0xdf, 0xc9, 0x42, 0x5e, 0x4f, 0x96, 0x8f, 0x7f, + 0x0c, 0x29, 0xf0, 0x25, 0x9c, 0xf5, 0xf9, 0xae, +0xd6, 0x85, 0x1c, 0x2b, 0xb4, 0xad, 0x8b, 0xfb, 0x86, 0x0c, 0xfe, 0xe0, 0xab, 0x24, 0x82, 0x92}, + .message = {0xf7, 0x26, 0x93, 0x6d, 0x19, 0xc8, 0x00, 0x49, + 0x4e, 0x3f, 0xda, 0xff, 0x20, 0xb2, 0x76, 0xa8}, + .msglen = 16, + .context = {0x66, 0x6f, 0x6f}, + .ctxlen = 3, + .signature = {0x55, 0xa4, 0xcc, 0x2f, 0x70, 0xa5, 0x4e, 0x04, + 0x28, 0x8c, 0x5f, 0x4c, 0xd1, 0xe4, 0x5a, 0x7b, + 0xb5, 0x20, 0xb3, 0x62, 0x92, 0x91, 0x18, 0x76, + 0xca, 0xda, 0x73, 0x23, 0x19, 0x8d, 0xd8, 0x7a, + 0x8b, 0x36, 0x95, 0x0b, 0x95, 0x13, 0x00, 0x22, + 0x90, 0x7a, 0x7f, 0xb7, 0xc4, 0xe9, 0xb2, 0xd5, + 0xf6, 0xcc, 0xa6, 0x85, 0xa5, 0x87, 0xb4, 0xb2, + 0x1f, 0x4b, 0x88, 0x8e, 0x4e, 0x7e, 0xdb, 0x0d} +}; +// https://datatracker.ietf.org/doc/html/rfc8032#section-7.3 test abc +static TestVector const testVectorEd25519ph PROGMEM = { + .name = "Ed25519ph", + .mode = Ed25519::Mode::ph, + .privateKey = {0x83, 0x3f, 0xe6, 0x24, 0x09, 0x23, 0x7b, 0x9d, + 0x62, 0xec, 0x77, 0x58, 0x75, 0x20, 0x91, 0x1e, + 0x9a, 0x75, 0x9c, 0xec, 0x1d, 0x19, 0x75, 0x5b, + 0x7d, 0xa9, 0x01, 0xb9, 0x6d, 0xca, 0x3d, 0x42}, + .publicKey = {0xec, 0x17, 0x2b, 0x93, 0xad, 0x5e, 0x56, 0x3b, + 0xf4, 0x93, 0x2c, 0x70, 0xe1, 0x24, 0x50, 0x34, + 0xc3, 0x54, 0x67, 0xef, 0x2e, 0xfd, 0x4d, 0x64, + 0xeb, 0xf8, 0x19, 0x68, 0x34, 0x67, 0xe2, 0xbf}, + .message = {0x61, 0x62, 0x63}, + .msglen = 3, + .signature = {0x98, 0xa7, 0x02, 0x22, 0xf0, 0xb8, 0x12, 0x1a, + 0xa9, 0xd3, 0x0f, 0x81, 0x3d, 0x68, 0x3f, 0x80, + 0x9e, 0x46, 0x2b, 0x46, 0x9c, 0x7f, 0xf8, 0x76, + 0x39, 0x49, 0x9b, 0xb9, 0x4e, 0x6d, 0xae, 0x41, + 0x31, 0xf8, 0x50, 0x42, 0x46, 0x3c, 0x2a, 0x35, + 0x5a, 0x20, 0x03, 0xd0, 0x62, 0xad, 0xf5, 0xaa, + 0xa1, 0x0b, 0x8c, 0x61, 0xe6, 0x36, 0x06, 0x2a, + 0xaa, 0xd1, 0x1c, 0x2a, 0x26, 0x08, 0x34, 0x06} +}; static TestVector testVector; @@ -111,8 +164,8 @@ void testFixedVectors(const struct TestVector *test) Serial.print(" sign ... "); Serial.flush(); unsigned long start = micros(); - Ed25519::sign(signature, test->privateKey, test->publicKey, - test->message, test->len); + Ed25519::sign(test->mode, signature, test->privateKey, test->publicKey, + test->message, test->msglen, test->context, test->ctxlen); unsigned long elapsed = micros() - start; if (memcmp(signature, test->signature, 64) == 0) { Serial.print("ok"); @@ -130,7 +183,8 @@ void testFixedVectors(const struct TestVector *test) Serial.print(" verify ... "); Serial.flush(); start = micros(); - bool verified = Ed25519::verify(signature, test->publicKey, test->message, test->len); + bool verified = Ed25519::verify(test->mode, signature, test->publicKey, + test->message, test->msglen, test->context, test->ctxlen); elapsed = micros() - start; if (verified) { Serial.print("ok"); @@ -164,6 +218,8 @@ void testFixedVectors() { testFixedVectors(&testVectorEd25519_1); testFixedVectors(&testVectorEd25519_2); + testFixedVectors(&testVectorEd25519ctx); + testFixedVectors(&testVectorEd25519ph); } void setup()