zig/lib/std/crypto/25519/ed25519.zig
Frank Denis f3667e8a80 std/crypto/25519: do cofactored ed25519 verification
This is slightly slower but makes our verification function compatible
with batch signatures. Which, in turn, makes blockchain people happy.
And we want to make our users happy.

Add convenience functions to substract edwards25519 points and to
clear the cofactor.
2020-10-15 18:49:10 -04:00

140 lines
5.8 KiB
Zig

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;
const Sha512 = std.crypto.hash.sha2.Sha512;
/// Ed25519 (EdDSA) signatures.
pub const Ed25519 = struct {
/// The underlying elliptic curve.
pub const Curve = @import("edwards25519.zig").Edwards25519;
/// Length (in bytes) of a seed required to create a key pair.
pub const seed_length = 32;
/// Length (in bytes) of a compressed key pair.
pub const keypair_length = 64;
/// Length (in bytes) of a compressed public key.
pub const public_length = 32;
/// Length (in bytes) of a signature.
pub const signature_length = 64;
/// Length (in bytes) of optional random bytes, for non-deterministic signatures.
pub const noise_length = 32;
/// Derive a key pair from a secret seed.
///
/// As in RFC 8032, an Ed25519 public key is generated by hashing
/// the secret key using the SHA-512 function, and interpreting the
/// bit-swapped, clamped lower-half of the output as the secret scalar.
///
/// For this reason, an EdDSA secret key is commonly called a seed,
/// from which the actual secret is derived.
pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 {
var az: [Sha512.digest_length]u8 = undefined;
var h = Sha512.init(.{});
h.update(&seed);
h.final(&az);
const p = try Curve.basePoint.clampedMul(az[0..32].*);
var keypair: [keypair_length]u8 = undefined;
mem.copy(u8, &keypair, &seed);
mem.copy(u8, keypair[seed_length..], &p.toBytes());
return keypair;
}
/// Return the public key for a given key pair.
pub fn publicKey(key_pair: [keypair_length]u8) [public_length]u8 {
var public_key: [public_length]u8 = undefined;
mem.copy(u8, public_key[0..], key_pair[seed_length..]);
return public_key;
}
/// Sign a message using a key pair, and optional random noise.
/// Having noise creates non-standard, non-deterministic signatures,
/// but has been proven to increase resilience against fault attacks.
pub fn sign(msg: []const u8, key_pair: [keypair_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 {
const public_key = key_pair[32..];
var az: [Sha512.digest_length]u8 = undefined;
var h = Sha512.init(.{});
h.update(key_pair[0..seed_length]);
h.final(&az);
h = Sha512.init(.{});
if (noise) |*z| {
h.update(z);
}
h.update(az[32..]);
h.update(msg);
var nonce64: [64]u8 = undefined;
h.final(&nonce64);
const nonce = Curve.scalar.reduce64(nonce64);
const r = try Curve.basePoint.mul(nonce);
var sig: [signature_length]u8 = undefined;
mem.copy(u8, sig[0..32], &r.toBytes());
mem.copy(u8, sig[32..], public_key);
h = Sha512.init(.{});
h.update(&sig);
h.update(msg);
var hram64: [Sha512.digest_length]u8 = undefined;
h.final(&hram64);
const hram = Curve.scalar.reduce64(hram64);
var x = az[0..32];
Curve.scalar.clamp(x);
const s = Curve.scalar.mulAdd(hram, x.*, nonce);
mem.copy(u8, sig[32..], s[0..]);
return sig;
}
/// Verify an Ed25519 signature given a message and a public key.
/// Returns error.InvalidSignature is the signature verification failed.
pub fn verify(sig: [signature_length]u8, msg: []const u8, public_key: [public_length]u8) !void {
const r = sig[0..32];
const s = sig[32..64];
try Curve.scalar.rejectNonCanonical(s.*);
try Curve.rejectNonCanonical(public_key);
const a = try Curve.fromBytes(public_key);
try a.rejectIdentity();
const expected_r = try Curve.fromBytes(r.*);
var h = Sha512.init(.{});
h.update(r);
h.update(&public_key);
h.update(msg);
var hram64: [Sha512.digest_length]u8 = undefined;
h.final(&hram64);
const hram = Curve.scalar.reduce64(hram64);
const ah = try a.neg().mul(hram);
const sb_ah = (try Curve.basePoint.mul(s.*)).add(ah);
if (expected_r.sub(sb_ah).clearCofactor().rejectIdentity()) |_| {
return error.InvalidSignature;
} else |_| {}
}
};
test "ed25519 key pair creation" {
var seed: [32]u8 = undefined;
try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
const key_pair = try Ed25519.createKeyPair(seed);
var buf: [256]u8 = undefined;
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
const public_key = Ed25519.publicKey(key_pair);
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
}
test "ed25519 signature" {
var seed: [32]u8 = undefined;
try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
const key_pair = try Ed25519.createKeyPair(seed);
const sig = try Ed25519.sign("test", key_pair, null);
var buf: [128]u8 = undefined;
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808");
const public_key = Ed25519.publicKey(key_pair);
try Ed25519.verify(sig, "test", public_key);
std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key));
}