From 0c7a99b38d2fb881cde5125ef0729ff0427c06a6 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 25 Oct 2020 21:55:05 +0100 Subject: [PATCH] Move ed25519 key pairs to a KeyPair structure --- lib/std/crypto/25519/ed25519.zig | 110 +++++++++++++++++-------------- lib/std/crypto/benchmark.zig | 18 ++--- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 7c2d9d16b9..842b08d706 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -5,6 +5,7 @@ // 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; @@ -15,8 +16,8 @@ pub const Ed25519 = struct { 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 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. @@ -24,46 +25,61 @@ pub const Ed25519 = struct { /// 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 { - const sk = seed orelse sk: { - var random_seed: [seed_length]u8 = undefined; - try crypto.randomBytes(&random_seed); - break :sk random_seed; - }; - var az: [Sha512.digest_length]u8 = undefined; - var h = Sha512.init(.{}); - h.update(&sk); - h.final(&az); - const p = try Curve.basePoint.clampedMul(az[0..32].*); - var keypair: [keypair_length]u8 = undefined; - mem.copy(u8, &keypair, &sk); - mem.copy(u8, keypair[seed_length..], &p.toBytes()); - return keypair; - } + /// 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, - /// 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; - } + /// 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; + try crypto.randomBytes(&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_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 { - const public_key = key_pair[32..]; + 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(key_pair[0..seed_length]); + h.update(seed); h.final(&az); h = Sha512.init(.{}); @@ -192,50 +208,44 @@ pub const Ed25519 = struct { test "ed25519 key pair creation" { var seed: [32]u8 = undefined; try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - const key_pair = try Ed25519.createKeyPair(seed); + const key_pair = try Ed25519.KeyPair.create(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"); + std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); + std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.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 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, "{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)); + 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) { - var seed: [32]u8 = undefined; - try std.crypto.randomBytes(&seed); - const key_pair = try Ed25519.createKeyPair(seed); + const key_pair = try Ed25519.KeyPair.create(null); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; try std.crypto.randomBytes(&msg1); try std.crypto.randomBytes(&msg2); const sig1 = try Ed25519.sign(&msg1, key_pair, null); const sig2 = try Ed25519.sign(&msg2, key_pair, null); - const public_key = Ed25519.publicKey(key_pair); var signature_batch = [_]Ed25519.BatchElement{ Ed25519.BatchElement{ .sig = sig1, .msg = &msg1, - .public_key = public_key, + .public_key = key_pair.public_key, }, Ed25519.BatchElement{ .sig = sig2, .msg = &msg2, - .public_key = public_key, + .public_key = key_pair.public_key, }, }; try Ed25519.verifyBatch(2, signature_batch); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index b9651209ff..d27a4624f7 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -124,10 +124,8 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c const signatures = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); + const key_pair = try Signature.KeyPair.create(null); var timer = try Timer.start(); const start = timer.lap(); @@ -149,11 +147,8 @@ pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); - const public_key = Signature.publicKey(key_pair); + const key_pair = try Signature.KeyPair.create(null); const sig = try Signature.sign(&msg, key_pair, null); var timer = try Timer.start(); @@ -161,7 +156,7 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign { var i: usize = 0; while (i < signatures_count) : (i += 1) { - try Signature.verify(sig, &msg, public_key); + try Signature.verify(sig, &msg, key_pair.public_key); mem.doNotOptimizeAway(&sig); } } @@ -176,16 +171,13 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); - const public_key = Signature.publicKey(key_pair); + const key_pair = try Signature.KeyPair.create(null); const sig = try Signature.sign(&msg, key_pair, null); var batch: [64]Signature.BatchElement = undefined; for (batch) |*element| { - element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = public_key }; + element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key }; } var timer = try Timer.start();