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