mirror of
https://github.com/ziglang/zig.git
synced 2025-12-16 03:03:09 +00:00
Let's follow the road paved by the removal of 'z'/'Z', the Formatter pattern is nice enough to let us remove the remaining four special cases and declare u8 slices free from any special casing!
354 lines
16 KiB
Zig
354 lines
16 KiB
Zig
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2015-2021 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 crypto = std.crypto;
|
|
const debug = std.debug;
|
|
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 secret key.
|
|
pub const secret_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;
|
|
|
|
/// An Ed25519 key pair.
|
|
pub const KeyPair = struct {
|
|
/// Public part.
|
|
public_key: [public_length]u8,
|
|
/// Secret part. What we expose as a secret key is, under the hood, the concatenation of the seed and the public key.
|
|
secret_key: [secret_length]u8,
|
|
|
|
/// Derive a key pair from an optional 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 create(seed: ?[seed_length]u8) !KeyPair {
|
|
const ss = seed orelse ss: {
|
|
var random_seed: [seed_length]u8 = undefined;
|
|
crypto.random.bytes(&random_seed);
|
|
break :ss random_seed;
|
|
};
|
|
var az: [Sha512.digest_length]u8 = undefined;
|
|
var h = Sha512.init(.{});
|
|
h.update(&ss);
|
|
h.final(&az);
|
|
const p = try Curve.basePoint.clampedMul(az[0..32].*);
|
|
var sk: [secret_length]u8 = undefined;
|
|
mem.copy(u8, &sk, &ss);
|
|
const pk = p.toBytes();
|
|
mem.copy(u8, sk[seed_length..], &pk);
|
|
|
|
return KeyPair{ .public_key = pk, .secret_key = sk };
|
|
}
|
|
|
|
/// Create a KeyPair from a secret key.
|
|
pub fn fromSecretKey(secret_key: [secret_length]u8) KeyPair {
|
|
return KeyPair{
|
|
.secret_key = secret_key,
|
|
.public_key = secret_key[seed_length..].*,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// 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, noise: ?[noise_length]u8) ![signature_length]u8 {
|
|
const seed = key_pair.secret_key[0..seed_length];
|
|
const public_key = key_pair.secret_key[seed_length..];
|
|
if (!mem.eql(u8, public_key, &key_pair.public_key)) {
|
|
return error.KeyMismatch;
|
|
}
|
|
var az: [Sha512.digest_length]u8 = undefined;
|
|
var h = Sha512.init(.{});
|
|
h.update(seed);
|
|
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();
|
|
try Curve.rejectNonCanonical(r.*);
|
|
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().mulPublic(hram);
|
|
const sb_ah = (try Curve.basePoint.mulPublic(s.*)).add(ah);
|
|
if (expected_r.sub(sb_ah).clearCofactor().rejectIdentity()) |_| {
|
|
return error.InvalidSignature;
|
|
} else |_| {}
|
|
}
|
|
|
|
/// A (signature, message, public_key) tuple for batch verification
|
|
pub const BatchElement = struct {
|
|
sig: [signature_length]u8,
|
|
msg: []const u8,
|
|
public_key: [public_length]u8,
|
|
};
|
|
|
|
/// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
|
|
pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) !void {
|
|
var r_batch: [count][32]u8 = undefined;
|
|
var s_batch: [count][32]u8 = undefined;
|
|
var a_batch: [count]Curve = undefined;
|
|
var expected_r_batch: [count]Curve = undefined;
|
|
|
|
for (signature_batch) |signature, i| {
|
|
const r = signature.sig[0..32];
|
|
const s = signature.sig[32..64];
|
|
try Curve.scalar.rejectNonCanonical(s.*);
|
|
try Curve.rejectNonCanonical(signature.public_key);
|
|
const a = try Curve.fromBytes(signature.public_key);
|
|
try a.rejectIdentity();
|
|
try Curve.rejectNonCanonical(r.*);
|
|
const expected_r = try Curve.fromBytes(r.*);
|
|
expected_r_batch[i] = expected_r;
|
|
r_batch[i] = r.*;
|
|
s_batch[i] = s.*;
|
|
a_batch[i] = a;
|
|
}
|
|
|
|
var hram_batch: [count]Curve.scalar.CompressedScalar = undefined;
|
|
for (signature_batch) |signature, i| {
|
|
var h = Sha512.init(.{});
|
|
h.update(&r_batch[i]);
|
|
h.update(&signature.public_key);
|
|
h.update(signature.msg);
|
|
var hram64: [Sha512.digest_length]u8 = undefined;
|
|
h.final(&hram64);
|
|
hram_batch[i] = Curve.scalar.reduce64(hram64);
|
|
}
|
|
|
|
var z_batch: [count]Curve.scalar.CompressedScalar = undefined;
|
|
for (z_batch) |*z| {
|
|
std.crypto.random.bytes(z[0..16]);
|
|
mem.set(u8, z[16..], 0);
|
|
}
|
|
|
|
var zs_sum = Curve.scalar.zero;
|
|
for (z_batch) |z, i| {
|
|
const zs = Curve.scalar.mul(z, s_batch[i]);
|
|
zs_sum = Curve.scalar.add(zs_sum, zs);
|
|
}
|
|
zs_sum = Curve.scalar.mul8(zs_sum);
|
|
|
|
var zhs: [count]Curve.scalar.CompressedScalar = undefined;
|
|
for (z_batch) |z, i| {
|
|
zhs[i] = Curve.scalar.mul(z, hram_batch[i]);
|
|
}
|
|
|
|
const zr = (try Curve.mulMulti(count, expected_r_batch, z_batch)).clearCofactor();
|
|
const zah = (try Curve.mulMulti(count, a_batch, zhs)).clearCofactor();
|
|
|
|
const zsb = try Curve.basePoint.mulPublic(zs_sum);
|
|
if (zr.add(zah).sub(zsb).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.KeyPair.create(seed);
|
|
var buf: [256]u8 = undefined;
|
|
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.secret_key)}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
|
|
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.public_key)}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
|
|
}
|
|
|
|
test "ed25519 signature" {
|
|
var seed: [32]u8 = undefined;
|
|
_ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
|
|
const key_pair = try Ed25519.KeyPair.create(seed);
|
|
|
|
const sig = try Ed25519.sign("test", key_pair, null);
|
|
var buf: [128]u8 = undefined;
|
|
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&sig)}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808");
|
|
try Ed25519.verify(sig, "test", key_pair.public_key);
|
|
std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key));
|
|
}
|
|
|
|
test "ed25519 batch verification" {
|
|
var i: usize = 0;
|
|
while (i < 100) : (i += 1) {
|
|
const key_pair = try Ed25519.KeyPair.create(null);
|
|
var msg1: [32]u8 = undefined;
|
|
var msg2: [32]u8 = undefined;
|
|
std.crypto.random.bytes(&msg1);
|
|
std.crypto.random.bytes(&msg2);
|
|
const sig1 = try Ed25519.sign(&msg1, key_pair, null);
|
|
const sig2 = try Ed25519.sign(&msg2, key_pair, null);
|
|
var signature_batch = [_]Ed25519.BatchElement{
|
|
Ed25519.BatchElement{
|
|
.sig = sig1,
|
|
.msg = &msg1,
|
|
.public_key = key_pair.public_key,
|
|
},
|
|
Ed25519.BatchElement{
|
|
.sig = sig2,
|
|
.msg = &msg2,
|
|
.public_key = key_pair.public_key,
|
|
},
|
|
};
|
|
try Ed25519.verifyBatch(2, signature_batch);
|
|
|
|
signature_batch[1].sig = sig1;
|
|
std.testing.expectError(error.InvalidSignature, Ed25519.verifyBatch(signature_batch.len, signature_batch));
|
|
}
|
|
}
|
|
|
|
test "ed25519 test vectors" {
|
|
const Vec = struct {
|
|
msg_hex: *const [64:0]u8,
|
|
public_key_hex: *const [64:0]u8,
|
|
sig_hex: *const [128:0]u8,
|
|
expected: ?anyerror,
|
|
};
|
|
|
|
const entries = [_]Vec{
|
|
Vec{
|
|
.msg_hex = "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6",
|
|
.public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
|
|
.sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000",
|
|
.expected = error.WeakPublicKey, // 0
|
|
},
|
|
Vec{
|
|
.msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
|
|
.public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
|
|
.sig_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
|
|
.expected = error.WeakPublicKey, // 1
|
|
},
|
|
Vec{
|
|
.msg_hex = "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab",
|
|
.public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
|
|
.sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e",
|
|
.expected = null, // 2 - small order R is acceptable
|
|
},
|
|
Vec{
|
|
.msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
|
|
.public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
|
|
.sig_hex = "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009",
|
|
.expected = null, // 3 - mixed orders
|
|
},
|
|
Vec{
|
|
.msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
|
|
.public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
|
|
.sig_hex = "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09",
|
|
.expected = null, // 4 - cofactored verification
|
|
},
|
|
Vec{
|
|
.msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
|
|
.public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
|
|
.sig_hex = "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405",
|
|
.expected = null, // 5 - cofactored verification
|
|
},
|
|
Vec{
|
|
.msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
|
|
.public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
|
|
.sig_hex = "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514",
|
|
.expected = error.NonCanonical, // 6 - S > L
|
|
},
|
|
Vec{
|
|
.msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
|
|
.public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
|
|
.sig_hex = "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a4734e74f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c2",
|
|
.expected = error.NonCanonical, // 7 - S >> L
|
|
},
|
|
Vec{
|
|
.msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
|
|
.public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
|
|
.sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f",
|
|
.expected = error.InvalidSignature, // 8 - non-canonical R
|
|
},
|
|
Vec{
|
|
.msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
|
|
.public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
|
|
.sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908",
|
|
.expected = null, // 9 - non-canonical R
|
|
},
|
|
Vec{
|
|
.msg_hex = "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b",
|
|
.public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
.sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
|
|
.expected = error.IdentityElement, // 10 - small-order A
|
|
},
|
|
Vec{
|
|
.msg_hex = "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f",
|
|
.public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
.sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
|
|
.expected = error.IdentityElement, // 11 - small-order A
|
|
},
|
|
};
|
|
for (entries) |entry, i| {
|
|
var msg: [entry.msg_hex.len / 2]u8 = undefined;
|
|
_ = try fmt.hexToBytes(&msg, entry.msg_hex);
|
|
var public_key: [32]u8 = undefined;
|
|
_ = try fmt.hexToBytes(&public_key, entry.public_key_hex);
|
|
var sig: [64]u8 = undefined;
|
|
_ = try fmt.hexToBytes(&sig, entry.sig_hex);
|
|
if (entry.expected) |error_type| {
|
|
std.testing.expectError(error_type, Ed25519.verify(sig, &msg, public_key));
|
|
} else {
|
|
try Ed25519.verify(sig, &msg, public_key);
|
|
}
|
|
}
|
|
}
|