| ... | @@ -5,6 +5,7 @@ | ... | @@ -5,6 +5,7 @@ |
| 5 | // and substantial portions of the software. | 5 | // and substantial portions of the software. |
| 6 | const std = @import("std"); | 6 | const std = @import("std"); |
| 7 | const crypto = std.crypto; | 7 | const crypto = std.crypto; |
| | 8 | const debug = std.debug; |
| 8 | const fmt = std.fmt; | 9 | const fmt = std.fmt; |
| 9 | const mem = std.mem; | 10 | const mem = std.mem; |
| 10 | const Sha512 = std.crypto.hash.sha2.Sha512; | 11 | const Sha512 = std.crypto.hash.sha2.Sha512; |
| ... | @@ -15,8 +16,8 @@ pub const Ed25519 = struct { | ... | @@ -15,8 +16,8 @@ pub const Ed25519 = struct { |
| 15 | pub const Curve = @import("edwards25519.zig").Edwards25519; | 16 | pub const Curve = @import("edwards25519.zig").Edwards25519; |
| 16 | /// Length (in bytes) of a seed required to create a key pair. | 17 | /// Length (in bytes) of a seed required to create a key pair. |
| 17 | pub const seed_length = 32; | 18 | pub const seed_length = 32; |
| 18 | /// Length (in bytes) of a compressed key pair. | 19 | /// Length (in bytes) of a compressed secret key. |
| 19 | pub const keypair_length = 64; | 20 | pub const secret_length = 64; |
| 20 | /// Length (in bytes) of a compressed public key. | 21 | /// Length (in bytes) of a compressed public key. |
| 21 | pub const public_length = 32; | 22 | pub const public_length = 32; |
| 22 | /// Length (in bytes) of a signature. | 23 | /// Length (in bytes) of a signature. |
| ... | @@ -24,46 +25,61 @@ pub const Ed25519 = struct { | ... | @@ -24,46 +25,61 @@ pub const Ed25519 = struct { |
| 24 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. | 25 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. |
| 25 | pub const noise_length = 32; | 26 | pub const noise_length = 32; |
| 26 | | 27 | |
| 27 | /// Derive a key pair from a secret seed. | 28 | /// An Ed25519 key pair. |
| 28 | /// | 29 | pub const KeyPair = struct { |
| 29 | /// As in RFC 8032, an Ed25519 public key is generated by hashing | 30 | /// Public part. |
| 30 | /// the secret key using the SHA-512 function, and interpreting the | 31 | public_key: [public_length]u8, |
| 31 | /// bit-swapped, clamped lower-half of the output as the secret scalar. | 32 | /// Secret part. What we expose as a secret key is, under the hood, the concatenation of the seed and the public key. |
| 32 | /// | 33 | secret_key: [secret_length]u8, |
| 33 | /// For this reason, an EdDSA secret key is commonly called a seed, | | |
| 34 | /// from which the actual secret is derived. | | |
| 35 | pub fn createKeyPair(seed: ?[seed_length]u8) ![keypair_length]u8 { | | |
| 36 | const sk = seed orelse sk: { | | |
| 37 | var random_seed: [seed_length]u8 = undefined; | | |
| 38 | try crypto.randomBytes(&random_seed); | | |
| 39 | break :sk random_seed; | | |
| 40 | }; | | |
| 41 | var az: [Sha512.digest_length]u8 = undefined; | | |
| 42 | var h = Sha512.init(.{}); | | |
| 43 | h.update(&sk); | | |
| 44 | h.final(&az); | | |
| 45 | const p = try Curve.basePoint.clampedMul(az[0..32].*); | | |
| 46 | var keypair: [keypair_length]u8 = undefined; | | |
| 47 | mem.copy(u8, &keypair, &sk); | | |
| 48 | mem.copy(u8, keypair[seed_length..], &p.toBytes()); | | |
| 49 | return keypair; | | |
| 50 | } | | |
| 51 | | 34 | |
| 52 | /// Return the public key for a given key pair. | 35 | /// Derive a key pair from an optional secret seed. |
| 53 | pub fn publicKey(key_pair: [keypair_length]u8) [public_length]u8 { | 36 | /// |
| 54 | var public_key: [public_length]u8 = undefined; | 37 | /// As in RFC 8032, an Ed25519 public key is generated by hashing |
| 55 | mem.copy(u8, public_key[0..], key_pair[seed_length..]); | 38 | /// the secret key using the SHA-512 function, and interpreting the |
| 56 | return public_key; | 39 | /// bit-swapped, clamped lower-half of the output as the secret scalar. |
| 57 | } | 40 | /// |
| | 41 | /// For this reason, an EdDSA secret key is commonly called a seed, |
| | 42 | /// from which the actual secret is derived. |
| | 43 | pub fn create(seed: ?[seed_length]u8) !KeyPair { |
| | 44 | const ss = seed orelse ss: { |
| | 45 | var random_seed: [seed_length]u8 = undefined; |
| | 46 | try crypto.randomBytes(&random_seed); |
| | 47 | break :ss random_seed; |
| | 48 | }; |
| | 49 | var az: [Sha512.digest_length]u8 = undefined; |
| | 50 | var h = Sha512.init(.{}); |
| | 51 | h.update(&ss); |
| | 52 | h.final(&az); |
| | 53 | const p = try Curve.basePoint.clampedMul(az[0..32].*); |
| | 54 | var sk: [secret_length]u8 = undefined; |
| | 55 | mem.copy(u8, &sk, &ss); |
| | 56 | const pk = p.toBytes(); |
| | 57 | mem.copy(u8, sk[seed_length..], &pk); |
| | 58 | |
| | 59 | return KeyPair{ .public_key = pk, .secret_key = sk }; |
| | 60 | } |
| | 61 | |
| | 62 | /// Create a KeyPair from a secret key. |
| | 63 | pub fn fromSecretKey(secret_key: [secret_length]u8) KeyPair { |
| | 64 | return KeyPair{ |
| | 65 | .secret_key = secret_key, |
| | 66 | .public_key = secret_key[seed_length..].*, |
| | 67 | }; |
| | 68 | } |
| | 69 | }; |
| 58 | | 70 | |
| 59 | /// Sign a message using a key pair, and optional random noise. | 71 | /// Sign a message using a key pair, and optional random noise. |
| 60 | /// Having noise creates non-standard, non-deterministic signatures, | 72 | /// Having noise creates non-standard, non-deterministic signatures, |
| 61 | /// but has been proven to increase resilience against fault attacks. | 73 | /// but has been proven to increase resilience against fault attacks. |
| 62 | pub fn sign(msg: []const u8, key_pair: [keypair_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 { | 74 | pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) ![signature_length]u8 { |
| 63 | const public_key = key_pair[32..]; | 75 | const seed = key_pair.secret_key[0..seed_length]; |
| | 76 | const public_key = key_pair.secret_key[seed_length..]; |
| | 77 | if (!mem.eql(u8, public_key, &key_pair.public_key)) { |
| | 78 | return error.KeyMismatch; |
| | 79 | } |
| 64 | var az: [Sha512.digest_length]u8 = undefined; | 80 | var az: [Sha512.digest_length]u8 = undefined; |
| 65 | var h = Sha512.init(.{}); | 81 | var h = Sha512.init(.{}); |
| 66 | h.update(key_pair[0..seed_length]); | 82 | h.update(seed); |
| 67 | h.final(&az); | 83 | h.final(&az); |
| 68 | | 84 | |
| 69 | h = Sha512.init(.{}); | 85 | h = Sha512.init(.{}); |
| ... | @@ -192,50 +208,44 @@ pub const Ed25519 = struct { | ... | @@ -192,50 +208,44 @@ pub const Ed25519 = struct { |
| 192 | test "ed25519 key pair creation" { | 208 | test "ed25519 key pair creation" { |
| 193 | var seed: [32]u8 = undefined; | 209 | var seed: [32]u8 = undefined; |
| 194 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); | 210 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 195 | const key_pair = try Ed25519.createKeyPair(seed); | 211 | const key_pair = try Ed25519.KeyPair.create(seed); |
| 196 | var buf: [256]u8 = undefined; | 212 | var buf: [256]u8 = undefined; |
| 197 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | 213 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); |
| 198 | | 214 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); |
| 199 | const public_key = Ed25519.publicKey(key_pair); | | |
| 200 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); | | |
| 201 | } | 215 | } |
| 202 | | 216 | |
| 203 | test "ed25519 signature" { | 217 | test "ed25519 signature" { |
| 204 | var seed: [32]u8 = undefined; | 218 | var seed: [32]u8 = undefined; |
| 205 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); | 219 | try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 206 | const key_pair = try Ed25519.createKeyPair(seed); | 220 | const key_pair = try Ed25519.KeyPair.create(seed); |
| 207 | | 221 | |
| 208 | const sig = try Ed25519.sign("test", key_pair, null); | 222 | const sig = try Ed25519.sign("test", key_pair, null); |
| 209 | var buf: [128]u8 = undefined; | 223 | var buf: [128]u8 = undefined; |
| 210 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); | 224 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); |
| 211 | const public_key = Ed25519.publicKey(key_pair); | 225 | try Ed25519.verify(sig, "test", key_pair.public_key); |
| 212 | try Ed25519.verify(sig, "test", public_key); | 226 | std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key)); |
| 213 | std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key)); | | |
| 214 | } | 227 | } |
| 215 | | 228 | |
| 216 | test "ed25519 batch verification" { | 229 | test "ed25519 batch verification" { |
| 217 | var i: usize = 0; | 230 | var i: usize = 0; |
| 218 | while (i < 100) : (i += 1) { | 231 | while (i < 100) : (i += 1) { |
| 219 | var seed: [32]u8 = undefined; | 232 | const key_pair = try Ed25519.KeyPair.create(null); |
| 220 | try std.crypto.randomBytes(&seed); | | |
| 221 | const key_pair = try Ed25519.createKeyPair(seed); | | |
| 222 | var msg1: [32]u8 = undefined; | 233 | var msg1: [32]u8 = undefined; |
| 223 | var msg2: [32]u8 = undefined; | 234 | var msg2: [32]u8 = undefined; |
| 224 | try std.crypto.randomBytes(&msg1); | 235 | try std.crypto.randomBytes(&msg1); |
| 225 | try std.crypto.randomBytes(&msg2); | 236 | try std.crypto.randomBytes(&msg2); |
| 226 | const sig1 = try Ed25519.sign(&msg1, key_pair, null); | 237 | const sig1 = try Ed25519.sign(&msg1, key_pair, null); |
| 227 | const sig2 = try Ed25519.sign(&msg2, key_pair, null); | 238 | const sig2 = try Ed25519.sign(&msg2, key_pair, null); |
| 228 | const public_key = Ed25519.publicKey(key_pair); | | |
| 229 | var signature_batch = [_]Ed25519.BatchElement{ | 239 | var signature_batch = [_]Ed25519.BatchElement{ |
| 230 | Ed25519.BatchElement{ | 240 | Ed25519.BatchElement{ |
| 231 | .sig = sig1, | 241 | .sig = sig1, |
| 232 | .msg = &msg1, | 242 | .msg = &msg1, |
| 233 | .public_key = public_key, | 243 | .public_key = key_pair.public_key, |
| 234 | }, | 244 | }, |
| 235 | Ed25519.BatchElement{ | 245 | Ed25519.BatchElement{ |
| 236 | .sig = sig2, | 246 | .sig = sig2, |
| 237 | .msg = &msg2, | 247 | .msg = &msg2, |
| 238 | .public_key = public_key, | 248 | .public_key = key_pair.public_key, |
| 239 | }, | 249 | }, |
| 240 | }; | 250 | }; |
| 241 | try Ed25519.verifyBatch(2, signature_batch); | 251 | try Ed25519.verifyBatch(2, signature_batch); |