Commit 8a00bd4ce6

Frank Denis <124872+jedisct1@users.noreply.github.com>
2024-11-19 18:05:09
std.crypto: make the key pair API creation consistent (#21955)
Our key pair creation API was ugly and inconsistent between ecdsa keys and other keys. The same `generate()` function can now be used to generate key pairs, and that function cannot fail. For deterministic keys, a `generateDeterministic()` function is available for all key types. Fix comments and compilation of the benchmark by the way. Fixes #21002
1 parent 94be75a
lib/std/crypto/25519/ed25519.zig
@@ -245,7 +245,9 @@ pub const Ed25519 = struct {
         /// Secret scalar.
         secret_key: SecretKey,
 
-        /// Derive a key pair from an optional secret seed.
+        /// Deterministically derive a key pair from a cryptograpically secure secret seed.
+        ///
+        /// Except in tests, applications should generally call `generate()` instead of this function.
         ///
         /// As in RFC 8032, an Ed25519 public key is generated by hashing
         /// the secret key using the SHA-512 function, and interpreting the
@@ -253,20 +255,15 @@ pub const Ed25519 = struct {
         ///
         /// 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) IdentityElementError!KeyPair {
-            const ss = seed orelse ss: {
-                var random_seed: [seed_length]u8 = undefined;
-                crypto.random.bytes(&random_seed);
-                break :ss random_seed;
-            };
+        pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair {
             var az: [Sha512.digest_length]u8 = undefined;
             var h = Sha512.init(.{});
-            h.update(&ss);
+            h.update(&seed);
             h.final(&az);
             const pk_p = Curve.basePoint.clampedMul(az[0..32].*) catch return error.IdentityElement;
             const pk_bytes = pk_p.toBytes();
             var sk_bytes: [SecretKey.encoded_length]u8 = undefined;
-            sk_bytes[0..ss.len].* = ss;
+            sk_bytes[0..seed_length].* = seed;
             sk_bytes[seed_length..].* = pk_bytes;
             return KeyPair{
                 .public_key = PublicKey.fromBytes(pk_bytes) catch unreachable,
@@ -274,7 +271,22 @@ pub const Ed25519 = struct {
             };
         }
 
-        /// Create a KeyPair from a secret key.
+        /// Generate a new, random key pair.
+        ///
+        /// `crypto.random.bytes` must be supported by the target.
+        pub fn generate() KeyPair {
+            var random_seed: [seed_length]u8 = undefined;
+            while (true) {
+                crypto.random.bytes(&random_seed);
+                return generateDeterministic(random_seed) catch {
+                    @branchHint(.unlikely);
+                    continue;
+                };
+            }
+        }
+
+        /// Create a key pair from an existing secret key.
+        ///
         /// Note that with EdDSA, storing the seed, and recovering the key pair
         /// from it is recommended over storing the entire secret key.
         /// The seed of an exiting key pair can be obtained with
@@ -285,7 +297,7 @@ pub const Ed25519 = struct {
             // With runtime safety, we can still afford checking that the public key is correct.
             if (std.debug.runtime_safety) {
                 const pk_p = try Curve.fromBytes(secret_key.publicKeyBytes());
-                const recomputed_kp = try create(secret_key.seed());
+                const recomputed_kp = try generateDeterministic(secret_key.seed());
                 debug.assert(mem.eql(u8, &recomputed_kp.public_key.toBytes(), &pk_p.toBytes()));
             }
             return KeyPair{
@@ -492,7 +504,7 @@ pub const Ed25519 = struct {
 test "key pair creation" {
     var seed: [32]u8 = undefined;
     _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
-    const key_pair = try Ed25519.KeyPair.create(seed);
+    const key_pair = try Ed25519.KeyPair.generateDeterministic(seed);
     var buf: [256]u8 = undefined;
     try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.secret_key.toBytes())}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
     try std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{s}", .{std.fmt.fmtSliceHexUpper(&key_pair.public_key.toBytes())}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
@@ -501,7 +513,7 @@ test "key pair creation" {
 test "signature" {
     var seed: [32]u8 = undefined;
     _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
-    const key_pair = try Ed25519.KeyPair.create(seed);
+    const key_pair = try Ed25519.KeyPair.generateDeterministic(seed);
 
     const sig = try key_pair.sign("test", null);
     var buf: [128]u8 = undefined;
@@ -513,7 +525,7 @@ test "signature" {
 test "batch verification" {
     var i: usize = 0;
     while (i < 100) : (i += 1) {
-        const key_pair = try Ed25519.KeyPair.create(null);
+        const key_pair = Ed25519.KeyPair.generate();
         var msg1: [32]u8 = undefined;
         var msg2: [32]u8 = undefined;
         crypto.random.bytes(&msg1);
@@ -645,7 +657,7 @@ test "with blind keys" {
     const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair;
 
     // Create a standard Ed25519 key pair
-    const kp = try Ed25519.KeyPair.create(null);
+    const kp = Ed25519.KeyPair.generate();
 
     // Create a random blinding seed
     var blind: [32]u8 = undefined;
@@ -665,7 +677,7 @@ test "with blind keys" {
 }
 
 test "signatures with streaming" {
-    const kp = try Ed25519.KeyPair.create(null);
+    const kp = Ed25519.KeyPair.generate();
 
     var signer = try kp.signer(null);
     signer.update("mes");
@@ -681,7 +693,7 @@ test "signatures with streaming" {
 }
 
 test "key pair from secret key" {
-    const kp = try Ed25519.KeyPair.create(null);
+    const kp = Ed25519.KeyPair.generate();
     const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key);
     try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
     try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
lib/std/crypto/25519/x25519.zig
@@ -29,19 +29,29 @@ pub const X25519 = struct {
         /// Secret part.
         secret_key: [secret_length]u8,
 
-        /// Create a new key pair using an optional seed.
-        pub fn create(seed: ?[seed_length]u8) IdentityElementError!KeyPair {
-            const sk = seed orelse sk: {
-                var random_seed: [seed_length]u8 = undefined;
-                crypto.random.bytes(&random_seed);
-                break :sk random_seed;
+        /// Deterministically derive a key pair from a cryptograpically secure secret seed.
+        ///
+        /// Except in tests, applications should generally call `generate()` instead of this function.
+        pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair {
+            const kp = KeyPair{
+                .public_key = try X25519.recoverPublicKey(seed),
+                .secret_key = seed,
             };
-            var kp: KeyPair = undefined;
-            kp.secret_key = sk;
-            kp.public_key = try X25519.recoverPublicKey(sk);
             return kp;
         }
 
+        /// Generate a new, random key pair.
+        pub fn generate() KeyPair {
+            var random_seed: [seed_length]u8 = undefined;
+            while (true) {
+                crypto.random.bytes(&random_seed);
+                return generateDeterministic(random_seed) catch {
+                    @branchHint(.unlikely);
+                    continue;
+                };
+            }
+        }
+
         /// Create a key pair from an Ed25519 key pair
         pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) (IdentityElementError || EncodingError)!KeyPair {
             const seed = ed25519_key_pair.secret_key.seed();
@@ -171,7 +181,7 @@ test "rfc7748 1,000,000 iterations" {
 }
 
 test "edwards25519 -> curve25519 map" {
-    const ed_kp = try crypto.sign.Ed25519.KeyPair.create([_]u8{0x42} ** 32);
+    const ed_kp = try crypto.sign.Ed25519.KeyPair.generateDeterministic([_]u8{0x42} ** 32);
     const mont_kp = try X25519.KeyPair.fromEd25519(ed_kp);
     try htest.assertEqual("90e7595fc89e52fdfddce9c6a43d74dbf6047025ee0462d2d172e8b6a2841d6e", &mont_kp.secret_key);
     try htest.assertEqual("cc4f2cdb695dd766f34118eb67b98652fed1d8bc49c330b119bbfa8a64989378", &mont_kp.public_key);
lib/std/crypto/tls/Client.zig
@@ -1649,10 +1649,10 @@ const KeyShare = struct {
 
     fn init(seed: [112]u8) error{IdentityElement}!KeyShare {
         return .{
-            .ml_kem768_kp = try .create(null),
-            .secp256r1_kp = try .create(seed[0..32].*),
-            .secp384r1_kp = try .create(seed[32..80].*),
-            .x25519_kp = try .create(seed[80..112].*),
+            .ml_kem768_kp = .generate(),
+            .secp256r1_kp = try .generateDeterministic(seed[0..32].*),
+            .secp384r1_kp = try .generateDeterministic(seed[32..80].*),
+            .x25519_kp = try .generateDeterministic(seed[80..112].*),
             .sk_buf = undefined,
             .sk_len = 0,
         };
lib/std/crypto/benchmark.zig
@@ -140,7 +140,7 @@ const signatures = [_]Crypto{
 
 pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
     const msg = [_]u8{0} ** 64;
-    const key_pair = try Signature.KeyPair.create(null);
+    const key_pair = Signature.KeyPair.generate();
 
     var timer = try Timer.start();
     const start = timer.lap();
@@ -163,7 +163,7 @@ const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .na
 
 pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
     const msg = [_]u8{0} ** 64;
-    const key_pair = try Signature.KeyPair.create(null);
+    const key_pair = Signature.KeyPair.generate();
     const sig = try key_pair.sign(&msg, null);
 
     var timer = try Timer.start();
@@ -187,7 +187,7 @@ const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed2551
 
 pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
     const msg = [_]u8{0} ** 64;
-    const key_pair = try Signature.KeyPair.create(null);
+    const key_pair = Signature.KeyPair.generate();
     const sig = try key_pair.sign(&msg, null);
 
     var batch: [64]Signature.BatchElement = undefined;
@@ -219,7 +219,7 @@ const kems = [_]Crypto{
 };
 
 pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
-    const key_pair = try Kem.KeyPair.create(null);
+    const key_pair = Kem.KeyPair.generate();
 
     var timer = try Timer.start();
     const start = timer.lap();
@@ -239,7 +239,7 @@ pub fn benchmarkKem(comptime Kem: anytype, comptime kems_count: comptime_int) !u
 }
 
 pub fn benchmarkKemDecaps(comptime Kem: anytype, comptime kems_count: comptime_int) !u64 {
-    const key_pair = try Kem.KeyPair.create(null);
+    const key_pair = Kem.KeyPair.generate();
 
     const e = key_pair.public_key.encaps(null);
 
@@ -266,7 +266,7 @@ pub fn benchmarkKemKeyGen(comptime Kem: anytype, comptime kems_count: comptime_i
     {
         var i: usize = 0;
         while (i < kems_count) : (i += 1) {
-            const key_pair = try Kem.KeyPair.create(null);
+            const key_pair = Kem.KeyPair.generate();
             mem.doNotOptimizeAway(&key_pair);
         }
     }
@@ -409,7 +409,7 @@ fn benchmarkPwhash(
     comptime count: comptime_int,
 ) !f64 {
     const password = "testpass" ** 2;
-    const opts = .{
+    const opts = ty.HashOptions{
         .allocator = allocator,
         .params = @as(*const ty.Params, @ptrCast(@alignCast(params))).*,
         .encoding = .phc,
lib/std/crypto/ecdsa.zig
@@ -296,21 +296,28 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
             /// Secret scalar.
             secret_key: SecretKey,
 
-            /// Create a new random key pair. `crypto.random.bytes` must be supported for the target.
-            pub fn generate() IdentityElementError!KeyPair {
-                var random_seed: [seed_length]u8 = undefined;
-                crypto.random.bytes(&random_seed);
-                return create(random_seed);
-            }
-
-            /// Create a new key pair. The seed must be secret and indistinguishable from random.
-            pub fn create(seed: [seed_length]u8) IdentityElementError!KeyPair {
+            /// Deterministically derive a key pair from a cryptograpically secure secret seed.
+            ///
+            /// Except in tests, applications should generally call `generate()` instead of this function.
+            pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair {
                 const h = [_]u8{0x00} ** Hash.digest_length;
                 const k0 = [_]u8{0x01} ** SecretKey.encoded_length;
                 const secret_key = deterministicScalar(h, k0, seed).toBytes(.big);
                 return fromSecretKey(SecretKey{ .bytes = secret_key });
             }
 
+            /// Generate a new, random key pair.
+            pub fn generate() KeyPair {
+                var random_seed: [seed_length]u8 = undefined;
+                while (true) {
+                    crypto.random.bytes(&random_seed);
+                    return generateDeterministic(random_seed) catch {
+                        @branchHint(.unlikely);
+                        continue;
+                    };
+                }
+            }
+
             /// Return the public key corresponding to the secret key.
             pub fn fromSecretKey(secret_key: SecretKey) IdentityElementError!KeyPair {
                 const public_key = try Curve.basePoint.mul(secret_key.bytes, .big);
@@ -387,7 +394,7 @@ test "Basic operations over EcdsaP384Sha384" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
 
     const Scheme = EcdsaP384Sha384;
-    const kp = try Scheme.KeyPair.generate();
+    const kp = Scheme.KeyPair.generate();
     const msg = "test";
 
     var noise: [Scheme.noise_length]u8 = undefined;
@@ -403,7 +410,7 @@ test "Basic operations over Secp256k1" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
 
     const Scheme = EcdsaSecp256k1Sha256oSha256;
-    const kp = try Scheme.KeyPair.generate();
+    const kp = Scheme.KeyPair.generate();
     const msg = "test";
 
     var noise: [Scheme.noise_length]u8 = undefined;
@@ -419,7 +426,7 @@ test "Basic operations over EcdsaP384Sha256" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
 
     const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256);
-    const kp = try Scheme.KeyPair.generate();
+    const kp = Scheme.KeyPair.generate();
     const msg = "test";
 
     var noise: [Scheme.noise_length]u8 = undefined;
@@ -893,7 +900,7 @@ test "Sec1 encoding/decoding" {
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
 
     const Scheme = EcdsaP384Sha384;
-    const kp = try Scheme.KeyPair.generate();
+    const kp = Scheme.KeyPair.generate();
     const pk = kp.public_key;
     const pk_compressed_sec1 = pk.toCompressedSec1();
     const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1);
lib/std/crypto/ml_kem.zig
@@ -370,15 +370,10 @@ fn Kyber(comptime p: Params) type {
             secret_key: SecretKey,
             public_key: PublicKey,
 
-            /// Create a new key pair.
-            /// If seed is null, a random seed will be generated.
-            /// If a seed is provided, the key pair will be deterministic.
-            pub fn create(seed_: ?[seed_length]u8) !KeyPair {
-                const seed = seed_ orelse sk: {
-                    var random_seed: [seed_length]u8 = undefined;
-                    crypto.random.bytes(&random_seed);
-                    break :sk random_seed;
-                };
+            /// Deterministically derive a key pair from a cryptograpically secure secret seed.
+            ///
+            /// Except in tests, applications should generally call `generate()` instead of this function.
+            pub fn generateDeterministic(seed: [seed_length]u8) !KeyPair {
                 var ret: KeyPair = undefined;
                 ret.secret_key.z = seed[inner_seed_length..seed_length].*;
 
@@ -399,6 +394,18 @@ fn Kyber(comptime p: Params) type {
 
                 return ret;
             }
+
+            /// Generate a new, random key pair.
+            pub fn generate() KeyPair {
+                var random_seed: [seed_length]u8 = undefined;
+                while (true) {
+                    crypto.random.bytes(&random_seed);
+                    return generateDeterministic(random_seed) catch {
+                        @branchHint(.unlikely);
+                        continue;
+                    };
+                }
+            }
         };
 
         // Size of plaintexts of the in
@@ -1698,7 +1705,7 @@ test "Test happy flow" {
     inline for (modes) |mode| {
         for (0..10) |i| {
             seed[0] = @as(u8, @intCast(i));
-            const kp = try mode.KeyPair.create(seed);
+            const kp = try mode.KeyPair.generateDeterministic(seed);
             const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes());
             try testing.expectEqual(sk, kp.secret_key);
             const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes());
@@ -1745,7 +1752,7 @@ test "NIST KAT test" {
             g2.fill(kseed[0..32]);
             g2.fill(kseed[32..64]);
             g2.fill(&eseed);
-            const kp = try mode.KeyPair.create(kseed);
+            const kp = try mode.KeyPair.generateDeterministic(kseed);
             const e = kp.public_key.encaps(eseed);
             const ss2 = try kp.secret_key.decaps(&e.ciphertext);
             try testing.expectEqual(ss2, e.shared_secret);
lib/std/crypto/salsa20.zig
@@ -535,7 +535,7 @@ pub const SealedBox = struct {
     /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
     pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
         debug.assert(c.len == m.len + seal_length);
-        var ekp = try KeyPair.create(null);
+        var ekp = KeyPair.generate();
         const nonce = createNonce(ekp.public_key, public_key);
         c[0..public_length].* = ekp.public_key;
         try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
@@ -607,8 +607,8 @@ test "xsalsa20poly1305 box" {
     crypto.random.bytes(&msg);
     crypto.random.bytes(&nonce);
 
-    const kp1 = try Box.KeyPair.create(null);
-    const kp2 = try Box.KeyPair.create(null);
+    const kp1 = Box.KeyPair.generate();
+    const kp2 = Box.KeyPair.generate();
     try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
     try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
 }
@@ -619,7 +619,7 @@ test "xsalsa20poly1305 sealedbox" {
     var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
     crypto.random.bytes(&msg);
 
-    const kp = try Box.KeyPair.create(null);
+    const kp = Box.KeyPair.generate();
     try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
     try SealedBox.open(msg2[0..], boxed[0..], kp);
 }