Skip to content

ed25519 signature verification low order check isn't strong enough #24834

@Rexicon226

Description

@Rexicon226

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    standard libraryThis issue involves writing Zig code for the standard library.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions