return 0;
}
-void crypto_sign_public_key(u8 public_key[32], const u8 secret_key[32])
+void crypto_eddsa_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32])
{
u8 a[64];
- crypto_blake2b(a, secret_key, 32);
- crypto_eddsa_trim_scalar(a, a);
- crypto_eddsa_scalarbase(public_key, a);
+ COPY(a, seed, 32); // a[ 0..31] = seed
+ crypto_wipe(seed, 32);
+ COPY(secret_key, a, 32); // secret key = seed
+ crypto_blake2b(a, a, 32); // a[ 0..31] = scalar
+ crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar
+ crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B
+ COPY(secret_key + 32, public_key, 32); // secret key includes public half
+ WIPE_BUFFER(a);
}
static void hash_reduce(u8 h[32],
crypto_eddsa_reduce(h, hash);
}
-void crypto_sign(u8 signature[64],
- const u8 secret_key[32],
- const u8 public_key[32],
- const u8 *message, size_t message_size)
-{
- u8 a[64];
+// Digital signature of a message with from a secret key.
+//
+// The secret key comprises two parts:
+// - The seed that generates the key (secret_key[ 0..31])
+// - The public key (secret_key[32..63])
+//
+// The seed and the public key are bundled together to make sure users
+// don't use mismatched seeds and public keys, which would instantly
+// leak the secret scalar and allow forgeries (allowing this to happen
+// has resulted in critical vulnerabilities in the wild).
+//
+// The seed is hashed to derive the secret scalar and a secret prefix.
+// The sole purpose of the prefix is to generate a secret random nonce.
+// The properties of that nonce must be as follows:
+// - Unique: we need a different one for each message.
+// - Secret: third parties must not be able to predict it.
+// - Random: any detectable bias would break all security.
+//
+// There are two ways to achieve these properties. The obvious one is
+// to simply generate a random number. Here that would be a parameter
+// (Monocypher doesn't have an RNG). It works, but then users may reuse
+// the nonce by accident, which _also_ leaks the secret scalar and
+// allows forgeries. This has happened in the wild too.
+//
+// This is no good, so instead we generate that nonce deterministically
+// by reducing modulo L a hash of the secret prefix and the message.
+// The secret prefix makes the nonce unpredictable, the message makes it
+// unique, and the hash/reduce removes all bias.
+//
+// The cost of that safety is hashing the message twice. If that cost
+// is unacceptable, there are two alternatives:
+//
+// - Signing a hash of the message instead of the message itself. This
+// is fine as long as the hash is collision resistant. It is not
+// compatible with existing "pure" signatures, but at least it's safe.
+//
+// - Using a random nonce. Please exercise **EXTREME CAUTION** if you
+// ever do that. It is absolutely **critical** that the nonce is
+// really an unbiased random number between 0 and L-1, never reused,
+// and wiped immediately.
+//
+// To lower the likelihood of complete catastrophe if the RNG is
+// either flawed or misused, you can hash the RNG output together with
+// the secret prefix and the beginning of the message, and use the
+// reduction of that hash instead of the RNG output itself. It's not
+// foolproof (you'd need to hash the whole message) but it helps.
+//
+// Signing a message involves the following operations:
+//
+// scalar, prefix = HASH(secret_key)
+// r = HASH(prefix || message) % L
+// R = [r]B
+// h = HASH(R || public_key || message) % L
+// S = ((h * a) + r) % L
+// signature = R || S
+void crypto_eddsa_sign(u8 signature [64], const u8 secret_key[32],
+ const u8 *message, size_t message_size)
+{
+ u8 a[64]; // secret scalar and prefix
+ u8 r[32]; // secret deterministic "random" nonce
+ u8 h[32]; // publically verifiable hash of the message (not wiped)
+ u8 R[32]; // first half of the signature (allows overlapping inputs)
+
crypto_blake2b(a, secret_key, 32);
crypto_eddsa_trim_scalar(a, a);
- u8 pk[32]; // not secret, not wiped
- if (public_key == 0) {
- crypto_eddsa_scalarbase(pk, a);
- } else {
- COPY(pk, public_key, 32);
- }
-
- // Deterministic part of EdDSA: Construct a nonce by hashing the message
- // instead of generating a random number.
- // An actual random number would work just fine, and would save us
- // the trouble of hashing the message twice. If we did that
- // however, the user could fuck it up and reuse the nonce.
- u8 r[32];
hash_reduce(r, a + 32, 32, message, message_size, 0, 0);
-
- // First half of the signature R = [r]B
- u8 R[32]; // Not secret, not wiped
crypto_eddsa_scalarbase(R, r);
-
- // Second half of the signature
- u8 h_ram[32];
- hash_reduce(h_ram, R, 32, pk, 32, message, message_size);
+ hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size);
COPY(signature, R, 32);
- crypto_eddsa_mul_add(signature + 32, h_ram, a, r); // s = h_ram * a + r
+ crypto_eddsa_mul_add(signature + 32, h, a, r);
WIPE_BUFFER(a);
WIPE_BUFFER(r);
- WIPE_BUFFER(h_ram);
}
-int crypto_check(const u8 signature[64], const u8 public_key[32],
- const u8 *message, size_t message_size)
+// To verify a signature, there are 3 steps:
+//
+// S, R = signature
+// h = HASH(R || public_key || message) % L
+// R' = [s]B - [h]public_key
+//
+// For the signature to be valid, **TWO** conditions must hold:
+//
+// - Computing R' must succeed.
+// - R and R' must be identical (duh).
+//
+// Don't you **ever** forget to check the return value of
+// `crypto_eddsa_r_check()`.
+int crypto_eddsa_check(const u8 signature[64], const u8 public_key[32],
+ const u8 *message, size_t message_size)
{
u8 h_ram [32];
u8 r_check[32];
// Signatures (EdDSA with curve25519 + BLAKE2b)
// --------------------------------------------
-
-// Generate public key
-void crypto_sign_public_key(uint8_t public_key[32],
- const uint8_t secret_key[32]);
-
-// Direct interface
-void crypto_sign(uint8_t signature [64],
- const uint8_t secret_key[32],
- const uint8_t public_key[32], // optional, may be 0
- const uint8_t *message, size_t message_size);
-int crypto_check(const uint8_t signature [64],
- const uint8_t public_key[32],
- const uint8_t *message, size_t message_size);
+void crypto_eddsa_key_pair(uint8_t secret_key[64],
+ uint8_t public_key[32],
+ uint8_t seed[32]);
+void crypto_eddsa_sign(uint8_t signature [64],
+ const uint8_t secret_key[32],
+ const uint8_t *message, size_t message_size);
+int crypto_eddsa_check(const uint8_t signature [64],
+ const uint8_t public_key[32],
+ const uint8_t *message, size_t message_size);
////////////////////////////
/// Low level primitives ///
///////////////
/// Ed25519 ///
///////////////
-void crypto_ed25519_public_key(u8 public_key[32], const u8 secret_key[32])
+void crypto_ed25519_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32])
{
u8 a[64];
- crypto_sha512(a, secret_key, 32);
- crypto_eddsa_trim_scalar(a, a);
- crypto_eddsa_scalarbase(public_key, a);
+ COPY(a, seed, 32); // a[ 0..31] = seed
+ crypto_wipe(seed, 32);
+ COPY(secret_key, a, 32); // secret key = seed
+ crypto_sha512(a, a, 32); // a[ 0..31] = scalar
+ crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar
+ crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B
+ COPY(secret_key + 32, public_key, 32); // secret key includes public half
+ WIPE_BUFFER(a);
}
static void hash_reduce(u8 h[32],
crypto_eddsa_reduce(h, hash);
}
-void crypto_ed25519_sign(u8 signature [64],
- const u8 secret_key[32],
- const u8 public_key[32],
+void crypto_ed25519_sign(u8 signature [64], const u8 secret_key[32],
const u8 *message, size_t message_size)
{
- u8 a[64];
+ u8 a[64]; // secret scalar and prefix
+ u8 r[32]; // secret deterministic "random" nonce
+ u8 h[32]; // publically verifiable hash of the message (not wiped)
+ u8 R[32]; // first half of the signature (allows overlapping inputs)
+
crypto_sha512(a, secret_key, 32);
crypto_eddsa_trim_scalar(a, a);
- u8 pk[32]; // not secret, not wiped
- if (public_key == 0) {
- crypto_eddsa_scalarbase(pk, a);
- } else {
- COPY(pk, public_key, 32);
- }
-
- // Deterministic part of EdDSA: Construct a nonce by hashing the message
- // instead of generating a random number.
- // An actual random number would work just fine, and would save us
- // the trouble of hashing the message twice. If we did that
- // however, the user could fuck it up and reuse the nonce.
- u8 r[32];
hash_reduce(r, a + 32, 32, message, message_size, 0, 0);
-
- // First half of the signature R = [r]B
- u8 R[32]; // Not secret, not wiped
crypto_eddsa_scalarbase(R, r);
-
- // Second half of the signature
- u8 h_ram[32];
- hash_reduce(h_ram, R, 32, pk, 32, message, message_size);
+ hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size);
COPY(signature, R, 32);
- crypto_eddsa_mul_add(signature + 32, h_ram, a, r); // s = h_ram * a + r
+ crypto_eddsa_mul_add(signature + 32, h, a, r);
WIPE_BUFFER(a);
WIPE_BUFFER(r);
- WIPE_BUFFER(h_ram);
}
-int crypto_ed25519_check(const u8 signature [64],
- const u8 public_key[32],
+int crypto_ed25519_check(const u8 signature[64], const u8 public_key[32],
const u8 *message, size_t message_size)
{
u8 h_ram [32];
// Ed25519
// -------
-
-void crypto_ed25519_public_key(uint8_t public_key[32],
- const uint8_t secret_key[32]);
-
+// Signatures (EdDSA with curve25519 + BLAKE2b)
+// --------------------------------------------
+void crypto_ed25519_key_pair(uint8_t secret_key[64],
+ uint8_t public_key[32],
+ uint8_t seed[32]);
void crypto_ed25519_sign(uint8_t signature [64],
const uint8_t secret_key[32],
- const uint8_t public_key[32], // optional, may be 0
const uint8_t *message, size_t message_size);
int crypto_ed25519_check(const uint8_t signature [64],
const uint8_t public_key[32],
vector public_k = next_input(reader);
vector msg = next_input(reader);
vector out = next_output(reader);
- u8 out2[64];
-
- // Sign with cached public key, then by reconstructing the key
- crypto_sign(out.buf, secret_k.buf, public_k.buf, msg.buf, msg.size);
- crypto_sign(out2 , secret_k.buf, 0 , msg.buf, msg.size);
- // Compare signatures (must be the same)
- if (memcmp(out.buf, out2, out.size)) {
- printf("FAILURE: reconstructing public key"
- " yields different signature\n");
- exit(1);
- }
+ u8 fat_secret_key[64];
+ memcpy(fat_secret_key , secret_k.buf, 32);
+ memcpy(fat_secret_key + 32, public_k.buf, 32);
+ crypto_eddsa_sign(out.buf, fat_secret_key, msg.buf, msg.size);
}
static void edDSA_pk(vector_reader *reader)
{
vector in = next_input(reader);
vector out = next_output(reader);
- crypto_sign_public_key(out.buf, in.buf);
+ u8 seed [32];
+ u8 secret_key[64];
+ u8 public_key[32];
+ memcpy(seed, in.buf, 32);
+ crypto_eddsa_key_pair(secret_key, public_key, seed);
+ memcpy(out.buf, public_key, 32);
+
+ u8 zeroes[32] = {0};
+ if (memcmp(seed, zeroes, 32)) {
+ printf("FAILURE: seed has not been wiped\n");
+ exit(1);
+ }
+ if (memcmp(secret_key, in.buf, 32)) {
+ printf("FAILURE: first half of secret key is not the seed\n");
+ exit(1);
+ }
+ if (memcmp(secret_key + 32, public_key, 32)) {
+ printf("FAILURE: second half of secret key is not the public key\n");
+ exit(1);
+ }
}
static void ed_25519(vector_reader *reader)
vector public_k = next_input(reader);
vector msg = next_input(reader);
vector out = next_output(reader);
- u8 out2[64];
-
- // Sign with cached public key, then by reconstructing the key
- crypto_ed25519_sign(out.buf, secret_k.buf, public_k.buf, msg.buf, msg.size);
- crypto_ed25519_sign(out2 , secret_k.buf, 0 , msg.buf, msg.size);
- // Compare signatures (must be the same)
- if (memcmp(out.buf, out2, out.size)) {
- printf("FAILURE: reconstructing public key"
- " yields different signature\n");
- exit(1);
- }
+ u8 fat_secret_key[64];
+ memcpy(fat_secret_key , secret_k.buf, 32);
+ memcpy(fat_secret_key + 32, public_k.buf, 32);
+ crypto_ed25519_sign(out.buf, fat_secret_key, msg.buf, msg.size);
}
static void ed_25519_pk(vector_reader *reader)
{
vector in = next_input(reader);
vector out = next_output(reader);
- crypto_ed25519_public_key(out.buf, in.buf);
+ u8 seed [32];
+ u8 secret_key[64];
+ u8 public_key[32];
+ memcpy(seed, in.buf, 32);
+ crypto_ed25519_key_pair(secret_key, public_key, seed);
+ memcpy(out.buf, public_key, 32);
+
+ u8 zeroes[32] = {0};
+ if (memcmp(seed, zeroes, 32)) {
+ printf("FAILURE: seed has not been wiped\n");
+ exit(1);
+ }
+ if (memcmp(secret_key, in.buf, 32)) {
+ printf("FAILURE: first half of secret key is not the seed\n");
+ exit(1);
+ }
+ if (memcmp(secret_key + 32, public_key, 32)) {
+ printf("FAILURE: second half of secret key is not the public key\n");
+ exit(1);
+ }
}
static void ed_25519_check(vector_reader *reader)
int status = 0;
FOR (i, 0, MESSAGE_SIZE) {
RANDOM_INPUT(message, MESSAGE_SIZE);
- RANDOM_INPUT(sk, 32);
- u8 pk [32]; crypto_sign_public_key(pk, sk);
- u8 signature[64]; crypto_sign(signature, sk, pk, message, i);
- status |= crypto_check(signature, pk, message, i);
+ RANDOM_INPUT(seed, 32);
+ u8 sk [64];
+ u8 pk [32];
+ u8 signature[64];
+ crypto_eddsa_key_pair(sk, pk, seed);
+ crypto_eddsa_sign(signature, sk, message, i);
+ status |= crypto_eddsa_check(signature, pk, message, i);
// reject forgeries
u8 zero [64] = {0};
- u8 forgery[64]; FOR (j, 0, 64) { forgery[j] = signature[j] + 1; }
- status |= !crypto_check(zero , pk, message, i);
- status |= !crypto_check(forgery, pk, message, i);
+ status |= !crypto_eddsa_check(zero , pk, message, i);
+ FOR (j, 0, 64) {
+ u8 forgery[64];
+ memcpy(forgery, signature, 64);
+ forgery[j] = signature[j] + 1;
+ status |= !crypto_eddsa_check(forgery, pk, message, i);
+ }
}
printf("%s: EdDSA (roundtrip)\n", status != 0 ? "FAILED" : "OK");
return status;
RANDOM_INPUT(message, MESSAGE_SIZE);
RANDOM_INPUT(pk, 32);
RANDOM_INPUT(signature , 64);
- status |= ~crypto_check(signature, pk, message, MESSAGE_SIZE);
+ status |= ~crypto_eddsa_check(signature, pk, message, MESSAGE_SIZE);
}
// Testing S == L (for code coverage)
RANDOM_INPUT(message, MESSAGE_SIZE);
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
};
- status |= ~crypto_check(signature, pk, message, MESSAGE_SIZE);
+ status |= ~crypto_eddsa_check(signature, pk, message, MESSAGE_SIZE);
printf("%s: EdDSA (random)\n", status != 0 ? "FAILED" : "OK");
return status;
#undef INPUT_SIZE
#define INPUT_SIZE (MESSAGE_SIZE + (2 * 64)) // total input size
RANDOM_INPUT(input, INPUT_SIZE);
- RANDOM_INPUT(sk , 32 );
- u8 pk [32]; crypto_sign_public_key(pk, sk);
+ RANDOM_INPUT(seed, 32);
+ u8 sk [64];
+ u8 pk [32];
u8 signature[64];
- crypto_sign(signature, sk, pk, input + 64, MESSAGE_SIZE);
- crypto_sign(input+i , sk, pk, input + 64, MESSAGE_SIZE);
+ crypto_eddsa_key_pair(sk, pk, seed);
+ crypto_eddsa_sign(signature, sk, input + 64, MESSAGE_SIZE);
+ crypto_eddsa_sign(input+i , sk, input + 64, MESSAGE_SIZE);
status |= memcmp(signature, input + i, 64);
}
printf("%s: EdDSA (overlap)\n", status != 0 ? "FAILED" : "OK");
{
int status = 0;
FOR (i, 0, 32) {
- RANDOM_INPUT(ed_private, 32);
- u8 ed_public[32]; crypto_sign_public_key (ed_public, ed_private);
- u8 x_private[32]; crypto_from_eddsa_private(x_private, ed_private);
- u8 x_public1[32]; crypto_from_eddsa_public (x_public1, ed_public);
- u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private);
+ RANDOM_INPUT(ed_seed, 32);
+ u8 secret [64];
+ u8 public [32]; crypto_eddsa_key_pair(secret, public, ed_seed);
+ u8 x_private[32]; crypto_from_eddsa_private(x_private, secret);
+ u8 x_public1[32]; crypto_from_eddsa_public (x_public1, public);
+ u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private);
status |= memcmp(x_public1, x_public2, 32);
}
printf("%s: from_eddsa\n", status != 0 ? "FAILED" : "OK");
{
int status = 0;
FOR (i, 0, 32) {
- RANDOM_INPUT(ed_private, 32);
- u8 ed_public[32]; crypto_ed25519_public_key (ed_public, ed_private);
- u8 x_private[32]; crypto_from_ed25519_private(x_private, ed_private);
- u8 x_public1[32]; crypto_from_ed25519_public (x_public1, ed_public);
- u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private);
+ RANDOM_INPUT(ed_seed, 32);
+ u8 secret [64];
+ u8 public [32]; crypto_ed25519_key_pair(secret, public, ed_seed);
+ u8 x_private[32]; crypto_from_ed25519_private(x_private, secret);
+ u8 x_public1[32]; crypto_from_ed25519_public (x_public1, public);
+ u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private);
status |= memcmp(x_public1, x_public2, 32);
}
printf("%s: from_ed25519\n", status != 0 ? "FAILED" : "OK");