Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ed25519ph and Ed25519ctx variants according to RFC-8032 #88

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 67 additions & 12 deletions libraries/Crypto/Ed25519.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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);
}
31 changes: 28 additions & 3 deletions libraries/Crypto/Ed25519.h
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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
70 changes: 63 additions & 7 deletions libraries/Crypto/examples/TestEd25519/TestEd25519.ino
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ 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];
};

// Test vectors for Ed25519 from:
// 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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -164,6 +218,8 @@ void testFixedVectors()
{
testFixedVectors(&testVectorEd25519_1);
testFixedVectors(&testVectorEd25519_2);
testFixedVectors(&testVectorEd25519ctx);
testFixedVectors(&testVectorEd25519ph);
}

void setup()
Expand Down