| author | |
| committer | |
| log | e59dd7eecfe9ff3a2eb82c630d5021fbd280011d |
| tree | 543cd7f00fdb323b804d092d3462d339a70a2091 |
| parent | ad6e095ef676ab50799a49eb40419b1ff926e6d7 |
Leverage result location semantics for X25519 like we do everywhere
else in 25519/*
Also add the edwards25519->curve25519 map by the way since many
applications seem to use this to share the same key pair for encryption
and signature.6 files changed, 70 insertions(+), 36 deletions(-)
lib/std/crypto/25519/curve25519.zig+8| ... | ... | @@ -100,6 +100,14 @@ pub const Curve25519 = struct { |
| 100 | 100 | _ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey; |
| 101 | 101 | return try ladder(p, s, 256); |
| 102 | 102 | } |
| 103 | ||
| 104 | /// Compute the Curve25519 equivalent to an Edwards25519 point. | |
| 105 | pub fn fromEdwards25519(p: std.crypto.ecc.Edwards25519) !Curve25519 { | |
| 106 | try p.clearCofactor().rejectIdentity(); | |
| 107 | const one = std.crypto.ecc.Edwards25519.Fe.one; | |
| 108 | const x = one.add(p.y).mul(one.sub(p.y).invert()); // xMont=(1+yEd)/(1-yEd) | |
| 109 | return Curve25519{ .x = x }; | |
| 110 | } | |
| 103 | 111 | }; |
| 104 | 112 | |
| 105 | 113 | test "curve25519" { |
lib/std/crypto/25519/edwards25519.zig+4-2| ... | ... | @@ -12,6 +12,8 @@ pub const Edwards25519 = struct { |
| 12 | 12 | pub const Fe = @import("field.zig").Fe; |
| 13 | 13 | /// Field arithmetic mod the order of the main subgroup. |
| 14 | 14 | pub const scalar = @import("scalar.zig"); |
| 15 | /// Length in bytes of a compressed representation of a point. | |
| 16 | pub const encoded_length: usize = 32; | |
| 15 | 17 | |
| 16 | 18 | x: Fe, |
| 17 | 19 | y: Fe, |
| ... | ... | @@ -21,7 +23,7 @@ pub const Edwards25519 = struct { |
| 21 | 23 | is_base: bool = false, |
| 22 | 24 | |
| 23 | 25 | /// Decode an Edwards25519 point from its compressed (Y+sign) coordinates. |
| 24 | pub fn fromBytes(s: [32]u8) !Edwards25519 { | |
| 26 | pub fn fromBytes(s: [encoded_length]u8) !Edwards25519 { | |
| 25 | 27 | const z = Fe.one; |
| 26 | 28 | const y = Fe.fromBytes(s); |
| 27 | 29 | var u = y.sq(); |
| ... | ... | @@ -43,7 +45,7 @@ pub const Edwards25519 = struct { |
| 43 | 45 | } |
| 44 | 46 | |
| 45 | 47 | /// Encode an Edwards25519 point. |
| 46 | pub fn toBytes(p: Edwards25519) [32]u8 { | |
| 48 | pub fn toBytes(p: Edwards25519) [encoded_length]u8 { | |
| 47 | 49 | const zi = p.z.invert(); |
| 48 | 50 | var s = p.y.mul(zi).toBytes(); |
| 49 | 51 | s[31] ^= @as(u8, @boolToInt(p.x.mul(zi).isNegative())) << 7; |
lib/std/crypto/25519/ristretto255.zig+7-5| ... | ... | @@ -14,6 +14,8 @@ pub const Ristretto255 = struct { |
| 14 | 14 | pub const Fe = Curve.Fe; |
| 15 | 15 | /// Field arithmetic mod the order of the main subgroup. |
| 16 | 16 | pub const scalar = Curve.scalar; |
| 17 | /// Length in byte of an encoded element. | |
| 18 | pub const encoded_length: usize = 32; | |
| 17 | 19 | |
| 18 | 20 | p: Curve, |
| 19 | 21 | |
| ... | ... | @@ -32,7 +34,7 @@ pub const Ristretto255 = struct { |
| 32 | 34 | return .{ .ratio_is_square = @boolToInt(has_m_root) | @boolToInt(has_p_root), .root = x.abs() }; |
| 33 | 35 | } |
| 34 | 36 | |
| 35 | fn rejectNonCanonical(s: [32]u8) !void { | |
| 37 | fn rejectNonCanonical(s: [encoded_length]u8) !void { | |
| 36 | 38 | if ((s[0] & 1) != 0) { |
| 37 | 39 | return error.NonCanonical; |
| 38 | 40 | } |
| ... | ... | @@ -48,7 +50,7 @@ pub const Ristretto255 = struct { |
| 48 | 50 | pub const basePoint = Ristretto255{ .p = Curve.basePoint }; |
| 49 | 51 | |
| 50 | 52 | /// Decode a Ristretto255 representative. |
| 51 | pub fn fromBytes(s: [32]u8) !Ristretto255 { | |
| 53 | pub fn fromBytes(s: [encoded_length]u8) !Ristretto255 { | |
| 52 | 54 | try rejectNonCanonical(s); |
| 53 | 55 | const s_ = Fe.fromBytes(s); |
| 54 | 56 | const ss = s_.sq(); // s^2 |
| ... | ... | @@ -78,7 +80,7 @@ pub const Ristretto255 = struct { |
| 78 | 80 | } |
| 79 | 81 | |
| 80 | 82 | /// Encode to a Ristretto255 representative. |
| 81 | pub fn toBytes(e: Ristretto255) [32]u8 { | |
| 83 | pub fn toBytes(e: Ristretto255) [encoded_length]u8 { | |
| 82 | 84 | const p = &e.p; |
| 83 | 85 | var u1_ = p.z.add(p.y); // Z+Y |
| 84 | 86 | const zmy = p.z.sub(p.y); // Z-Y |
| ... | ... | @@ -151,7 +153,7 @@ pub const Ristretto255 = struct { |
| 151 | 153 | /// Multiply a Ristretto255 element with a scalar. |
| 152 | 154 | /// Return error.WeakPublicKey if the resulting element is |
| 153 | 155 | /// the identity element. |
| 154 | pub inline fn mul(p: Ristretto255, s: [32]u8) !Ristretto255 { | |
| 156 | pub inline fn mul(p: Ristretto255, s: [encoded_length]u8) !Ristretto255 { | |
| 155 | 157 | return Ristretto255{ .p = try p.p.mul(s) }; |
| 156 | 158 | } |
| 157 | 159 | |
| ... | ... | @@ -170,7 +172,7 @@ test "ristretto255" { |
| 170 | 172 | var buf: [256]u8 = undefined; |
| 171 | 173 | std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76"); |
| 172 | 174 | |
| 173 | var r: [32]u8 = undefined; | |
| 175 | var r: [Ristretto255.encoded_length]u8 = undefined; | |
| 174 | 176 | try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); |
| 175 | 177 | var q = try Ristretto255.fromBytes(r); |
| 176 | 178 | q = q.dbl().add(p); |
lib/std/crypto/25519/x25519.zig+43-22| ... | ... | @@ -8,6 +8,8 @@ const crypto = std.crypto; |
| 8 | 8 | const mem = std.mem; |
| 9 | 9 | const fmt = std.fmt; |
| 10 | 10 | |
| 11 | const Sha512 = crypto.hash.sha2.Sha512; | |
| 12 | ||
| 11 | 13 | /// X25519 DH function. |
| 12 | 14 | pub const X25519 = struct { |
| 13 | 15 | /// The underlying elliptic curve. |
| ... | ... | @@ -37,33 +39,55 @@ pub const X25519 = struct { |
| 37 | 39 | }; |
| 38 | 40 | var kp: KeyPair = undefined; |
| 39 | 41 | mem.copy(u8, &kp.secret_key, sk[0..]); |
| 40 | try X25519.recoverPublicKey(&kp.public_key, sk); | |
| 42 | kp.public_key = try X25519.recoverPublicKey(sk); | |
| 41 | 43 | return kp; |
| 42 | 44 | } |
| 45 | ||
| 46 | /// Create a key pair from an Ed25519 key pair | |
| 47 | pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) !KeyPair { | |
| 48 | const seed = ed25519_key_pair.secret_key[0..32]; | |
| 49 | var az: [Sha512.digest_length]u8 = undefined; | |
| 50 | Sha512.hash(seed, &az, .{}); | |
| 51 | var sk = az[0..32].*; | |
| 52 | Curve.scalar.clamp(&sk); | |
| 53 | const pk = try publicKeyFromEd25519(ed25519_key_pair.public_key); | |
| 54 | return KeyPair{ | |
| 55 | .public_key = pk, | |
| 56 | .secret_key = sk, | |
| 57 | }; | |
| 58 | } | |
| 43 | 59 | }; |
| 44 | 60 | |
| 45 | 61 | /// Compute the public key for a given private key. |
| 46 | pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void { | |
| 62 | pub fn recoverPublicKey(secret_key: [secret_length]u8) ![public_length]u8 { | |
| 47 | 63 | const q = try Curve.basePoint.clampedMul(secret_key); |
| 48 | mem.copy(u8, public_key, q.toBytes()[0..]); | |
| 64 | return q.toBytes(); | |
| 65 | } | |
| 66 | ||
| 67 | /// Compute the X25519 equivalent to an Ed25519 public eky. | |
| 68 | pub fn publicKeyFromEd25519(ed25519_public_key: [crypto.sign.Ed25519.public_length]u8) ![public_length]u8 { | |
| 69 | const pk_ed = try crypto.ecc.Edwards25519.fromBytes(ed25519_public_key); | |
| 70 | const pk = try Curve.fromEdwards25519(pk_ed); | |
| 71 | return pk.toBytes(); | |
| 49 | 72 | } |
| 50 | 73 | |
| 51 | 74 | /// Compute the scalar product of a public key and a secret scalar. |
| 52 | 75 | /// Note that the output should not be used as a shared secret without |
| 53 | 76 | /// hashing it first. |
| 54 | pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void { | |
| 77 | pub fn scalarmult(secret_key: [secret_length]u8, public_key: [public_length]u8) ![shared_length]u8 { | |
| 55 | 78 | const q = try Curve.fromBytes(public_key).clampedMul(secret_key); |
| 56 | mem.copy(u8, out, q.toBytes()[0..]); | |
| 79 | return q.toBytes(); | |
| 57 | 80 | } |
| 58 | 81 | }; |
| 59 | 82 | |
| 83 | const htest = @import("../test.zig"); | |
| 84 | ||
| 60 | 85 | test "x25519 public key calculation from secret key" { |
| 61 | 86 | var sk: [32]u8 = undefined; |
| 62 | 87 | var pk_expected: [32]u8 = undefined; |
| 63 | var pk_calculated: [32]u8 = undefined; | |
| 64 | 88 | try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); |
| 65 | 89 | try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); |
| 66 | try X25519.recoverPublicKey(&pk_calculated, sk); | |
| 90 | const pk_calculated = try X25519.recoverPublicKey(sk); | |
| 67 | 91 | std.testing.expectEqual(pk_calculated, pk_expected); |
| 68 | 92 | } |
| 69 | 93 | |
| ... | ... | @@ -73,9 +97,7 @@ test "x25519 rfc7748 vector1" { |
| 73 | 97 | |
| 74 | 98 | const expected_output = [32]u8{ 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, 0x85, 0x52 }; |
| 75 | 99 | |
| 76 | var output: [32]u8 = undefined; | |
| 77 | ||
| 78 | try X25519.scalarmult(&output, secret_key, public_key); | |
| 100 | const output = try X25519.scalarmult(secret_key, public_key); | |
| 79 | 101 | std.testing.expectEqual(output, expected_output); |
| 80 | 102 | } |
| 81 | 103 | |
| ... | ... | @@ -85,9 +107,7 @@ test "x25519 rfc7748 vector2" { |
| 85 | 107 | |
| 86 | 108 | const expected_output = [32]u8{ 0x95, 0xcb, 0xde, 0x94, 0x76, 0xe8, 0x90, 0x7d, 0x7a, 0xad, 0xe4, 0x5c, 0xb4, 0xb8, 0x73, 0xf8, 0x8b, 0x59, 0x5a, 0x68, 0x79, 0x9f, 0xa1, 0x52, 0xe6, 0xf8, 0xf7, 0x64, 0x7a, 0xac, 0x79, 0x57 }; |
| 87 | 109 | |
| 88 | var output: [32]u8 = undefined; | |
| 89 | ||
| 90 | try X25519.scalarmult(&output, secret_key, public_key); | |
| 110 | const output = try X25519.scalarmult(secret_key, public_key); | |
| 91 | 111 | std.testing.expectEqual(output, expected_output); |
| 92 | 112 | } |
| 93 | 113 | |
| ... | ... | @@ -100,9 +120,7 @@ test "x25519 rfc7748 one iteration" { |
| 100 | 120 | |
| 101 | 121 | var i: usize = 0; |
| 102 | 122 | while (i < 1) : (i += 1) { |
| 103 | var output: [32]u8 = undefined; | |
| 104 | try X25519.scalarmult(output[0..], k, u); | |
| 105 | ||
| 123 | const output = try X25519.scalarmult(k, u); | |
| 106 | 124 | mem.copy(u8, u[0..], k[0..]); |
| 107 | 125 | mem.copy(u8, k[0..], output[0..]); |
| 108 | 126 | } |
| ... | ... | @@ -124,9 +142,7 @@ test "x25519 rfc7748 1,000 iterations" { |
| 124 | 142 | |
| 125 | 143 | var i: usize = 0; |
| 126 | 144 | while (i < 1000) : (i += 1) { |
| 127 | var output: [32]u8 = undefined; | |
| 128 | std.testing.expect(X25519.scalarmult(output[0..], &k, &u)); | |
| 129 | ||
| 145 | const output = try X25519.scalarmult(&k, &u); | |
| 130 | 146 | mem.copy(u8, u[0..], k[0..]); |
| 131 | 147 | mem.copy(u8, k[0..], output[0..]); |
| 132 | 148 | } |
| ... | ... | @@ -147,12 +163,17 @@ test "x25519 rfc7748 1,000,000 iterations" { |
| 147 | 163 | |
| 148 | 164 | var i: usize = 0; |
| 149 | 165 | while (i < 1000000) : (i += 1) { |
| 150 | var output: [32]u8 = undefined; | |
| 151 | std.testing.expect(X25519.scalarmult(output[0..], &k, &u)); | |
| 152 | ||
| 166 | const output = try X25519.scalarmult(&k, &u); | |
| 153 | 167 | mem.copy(u8, u[0..], k[0..]); |
| 154 | 168 | mem.copy(u8, k[0..], output[0..]); |
| 155 | 169 | } |
| 156 | 170 | |
| 157 | 171 | std.testing.expectEqual(k[0..], expected_output); |
| 158 | 172 | } |
| 173 | ||
| 174 | test "edwards25519 -> curve25519 map" { | |
| 175 | const ed_kp = try crypto.sign.Ed25519.KeyPair.create([_]u8{0x42} ** 32); | |
| 176 | const mont_kp = try X25519.KeyPair.fromEd25519(ed_kp); | |
| 177 | htest.assertEqual("90e7595fc89e52fdfddce9c6a43d74dbf6047025ee0462d2d172e8b6a2841d6e", &mont_kp.secret_key); | |
| 178 | htest.assertEqual("cc4f2cdb695dd766f34118eb67b98652fed1d8bc49c330b119bbfa8a64989378", &mont_kp.public_key); | |
| 179 | } |
lib/std/crypto/benchmark.zig+7-5| ... | ... | @@ -98,18 +98,20 @@ const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }}; |
| 98 | 98 | pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 { |
| 99 | 99 | std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length); |
| 100 | 100 | |
| 101 | var in: [DhKeyExchange.shared_length]u8 = undefined; | |
| 102 | prng.random.bytes(in[0..]); | |
| 101 | var secret: [DhKeyExchange.shared_length]u8 = undefined; | |
| 102 | prng.random.bytes(secret[0..]); | |
| 103 | 103 | |
| 104 | var out: [DhKeyExchange.shared_length]u8 = undefined; | |
| 105 | prng.random.bytes(out[0..]); | |
| 104 | var public: [DhKeyExchange.shared_length]u8 = undefined; | |
| 105 | prng.random.bytes(public[0..]); | |
| 106 | 106 | |
| 107 | 107 | var timer = try Timer.start(); |
| 108 | 108 | const start = timer.lap(); |
| 109 | 109 | { |
| 110 | 110 | var i: usize = 0; |
| 111 | 111 | while (i < exchange_count) : (i += 1) { |
| 112 | try DhKeyExchange.scalarmult(&out, out, in); | |
| 112 | const out = try DhKeyExchange.scalarmult(secret, public); | |
| 113 | mem.copy(u8, secret[0..16], out[0..16]); | |
| 114 | mem.copy(u8, public[0..16], out[16..32]); | |
| 113 | 115 | mem.doNotOptimizeAway(&out); |
| 114 | 116 | } |
| 115 | 117 | } |
lib/std/crypto/salsa20.zig+1-2| ... | ... | @@ -485,8 +485,7 @@ pub const Box = struct { |
| 485 | 485 | |
| 486 | 486 | /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key. |
| 487 | 487 | pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 { |
| 488 | var p: [32]u8 = undefined; | |
| 489 | try X25519.scalarmult(&p, secret_key, public_key); | |
| 488 | const p = try X25519.scalarmult(secret_key, public_key); | |
| 490 | 489 | const zero = [_]u8{0} ** 16; |
| 491 | 490 | return Salsa20Impl.hsalsa20(zero, p); |
| 492 | 491 | } |