Our current ed25519 implementation tries to avoid an expensive inversion in the verify function by just subtracting the expected signature R and the computed R point and rejecting a low-order result. This fails to catch invalid signatures for a few cases, one of which is shown below,
Zig:
const std = @import("std");
const Ed25519 = std.crypto.sign.Ed25519;
pub fn main() !void {
const pubkey: Ed25519.PublicKey = .{ .bytes = .{ 134, 231, 47, 92, 42, 114, 21, 21, 16, 89, 170, 21, 28, 14, 230, 248, 226, 21, 93, 48, 20, 2, 243, 93, 116, 152, 240, 120, 98, 154, 143, 121 } };
const signature: Ed25519.Signature = .fromBytes(.{ 250, 157, 222, 39, 79, 72, 32, 239, 177, 154, 137, 15, 139, 162, 216, 121, 23, 16, 164, 48, 60, 238, 244, 174, 223, 157, 221, 196, 232, 26, 31, 17, 112, 26, 89, 139, 154, 2, 174, 96, 80, 93, 208, 194, 147, 138, 26, 12, 45, 111, 253, 70, 118, 207, 180, 145, 37, 177, 158, 156, 179, 88, 218, 6 });
const message = [_]u8{ 101, 100, 50, 53, 53, 49, 57, 118, 101, 99, 116, 111, 114, 115, 32, 51 };
try signature.verify(&message, pubkey);
}
Passes.
Rust:
use ed25519_dalek::{Signature, SigningKey};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let pubkey_bytes = [134, 231, 47, 92, 42, 114, 21, 21, 16, 89, 170, 21, 28, 14, 230, 248, 226, 21, 93, 48, 20, 2, 243, 93, 116, 152, 240, 120, 98, 154, 143, 121];
let publickey = SigningKey::from_bytes(&pubkey_bytes);
let signature_bytes = [250, 157, 222, 39, 79, 72, 32, 239, 177, 154, 137, 15, 139, 162, 216, 121, 23, 16, 164, 48, 60, 238, 244, 174, 223, 157, 221, 196, 232, 26, 31, 17, 112, 26, 89, 139, 154, 2, 174, 96, 80, 93, 208, 194, 147, 138, 26, 12, 45, 111, 253, 70, 118, 207, 180, 145, 37, 177, 158, 156, 179, 88, 218, 6];
let signature = Signature::from_bytes(&signature_bytes);
let message_bytes = [101, 100, 50, 53, 53, 49, 57, 118, 101, 99, 116, 111, 114, 115, 32, 51];
publickey.verify(&message_bytes, &signature)?;
Ok(())
}
Fails.
The edd25519-dalek library takes the cost of the inversion in their verification function by compressing and then equating the compressed forms. They do the equating in constant time, although we don't care about that for our usecases.
https://github.com/dalek-cryptography/ed25519-dalek/blob/02001d8c3422fb0314b541fdb09d04760f7ab4ba/src/verifying.rs#L268
https://github.com/dalek-cryptography/ed25519-dalek/blob/02001d8c3422fb0314b541fdb09d04760f7ab4ba/src/verifying.rs#L247
cc @jedisct1
Our current ed25519 implementation tries to avoid an expensive inversion in the
verifyfunction by just subtracting the expected signatureRand the computedRpoint and rejecting a low-order result. This fails to catch invalid signatures for a few cases, one of which is shown below,Zig:
Passes.
Rust:
Fails.
The
edd25519-daleklibrary takes the cost of the inversion in their verification function by compressing and then equating the compressed forms. They do the equating in constant time, although we don't care about that for our usecases.https://github.com/dalek-cryptography/ed25519-dalek/blob/02001d8c3422fb0314b541fdb09d04760f7ab4ba/src/verifying.rs#L268
https://github.com/dalek-cryptography/ed25519-dalek/blob/02001d8c3422fb0314b541fdb09d04760f7ab4ba/src/verifying.rs#L247
cc @jedisct1