authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-10-26 12:51:57+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-10-29 14:39:58-04:00
loge59dd7eecfe9ff3a2eb82c630d5021fbd280011d
tree543cd7f00fdb323b804d092d3462d339a70a2091
parentad6e095ef676ab50799a49eb40419b1ff926e6d7

std/crypto/x25519: return encoded points directly + ed->mont map

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,6 +100,14 @@ pub const Curve25519 = struct {
100 _ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey;100 _ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey;
101 return try ladder(p, s, 256);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};
104112
105test "curve25519" {113test "curve25519" {
lib/std/crypto/25519/edwards25519.zig+4-2
...@@ -12,6 +12,8 @@ pub const Edwards25519 = struct {...@@ -12,6 +12,8 @@ pub const Edwards25519 = struct {
12 pub const Fe = @import("field.zig").Fe;12 pub const Fe = @import("field.zig").Fe;
13 /// Field arithmetic mod the order of the main subgroup.13 /// Field arithmetic mod the order of the main subgroup.
14 pub const scalar = @import("scalar.zig");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;
1517
16 x: Fe,18 x: Fe,
17 y: Fe,19 y: Fe,
...@@ -21,7 +23,7 @@ pub const Edwards25519 = struct {...@@ -21,7 +23,7 @@ pub const Edwards25519 = struct {
21 is_base: bool = false,23 is_base: bool = false,
2224
23 /// Decode an Edwards25519 point from its compressed (Y+sign) coordinates.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 const z = Fe.one;27 const z = Fe.one;
26 const y = Fe.fromBytes(s);28 const y = Fe.fromBytes(s);
27 var u = y.sq();29 var u = y.sq();
...@@ -43,7 +45,7 @@ pub const Edwards25519 = struct {...@@ -43,7 +45,7 @@ pub const Edwards25519 = struct {
43 }45 }
4446
45 /// Encode an Edwards25519 point.47 /// Encode an Edwards25519 point.
46 pub fn toBytes(p: Edwards25519) [32]u8 {48 pub fn toBytes(p: Edwards25519) [encoded_length]u8 {
47 const zi = p.z.invert();49 const zi = p.z.invert();
48 var s = p.y.mul(zi).toBytes();50 var s = p.y.mul(zi).toBytes();
49 s[31] ^= @as(u8, @boolToInt(p.x.mul(zi).isNegative())) << 7;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,6 +14,8 @@ pub const Ristretto255 = struct {
14 pub const Fe = Curve.Fe;14 pub const Fe = Curve.Fe;
15 /// Field arithmetic mod the order of the main subgroup.15 /// Field arithmetic mod the order of the main subgroup.
16 pub const scalar = Curve.scalar;16 pub const scalar = Curve.scalar;
17 /// Length in byte of an encoded element.
18 pub const encoded_length: usize = 32;
1719
18 p: Curve,20 p: Curve,
1921
...@@ -32,7 +34,7 @@ pub const Ristretto255 = struct {...@@ -32,7 +34,7 @@ pub const Ristretto255 = struct {
32 return .{ .ratio_is_square = @boolToInt(has_m_root) | @boolToInt(has_p_root), .root = x.abs() };34 return .{ .ratio_is_square = @boolToInt(has_m_root) | @boolToInt(has_p_root), .root = x.abs() };
33 }35 }
3436
35 fn rejectNonCanonical(s: [32]u8) !void {37 fn rejectNonCanonical(s: [encoded_length]u8) !void {
36 if ((s[0] & 1) != 0) {38 if ((s[0] & 1) != 0) {
37 return error.NonCanonical;39 return error.NonCanonical;
38 }40 }
...@@ -48,7 +50,7 @@ pub const Ristretto255 = struct {...@@ -48,7 +50,7 @@ pub const Ristretto255 = struct {
48 pub const basePoint = Ristretto255{ .p = Curve.basePoint };50 pub const basePoint = Ristretto255{ .p = Curve.basePoint };
4951
50 /// Decode a Ristretto255 representative.52 /// Decode a Ristretto255 representative.
51 pub fn fromBytes(s: [32]u8) !Ristretto255 {53 pub fn fromBytes(s: [encoded_length]u8) !Ristretto255 {
52 try rejectNonCanonical(s);54 try rejectNonCanonical(s);
53 const s_ = Fe.fromBytes(s);55 const s_ = Fe.fromBytes(s);
54 const ss = s_.sq(); // s^256 const ss = s_.sq(); // s^2
...@@ -78,7 +80,7 @@ pub const Ristretto255 = struct {...@@ -78,7 +80,7 @@ pub const Ristretto255 = struct {
78 }80 }
7981
80 /// Encode to a Ristretto255 representative.82 /// Encode to a Ristretto255 representative.
81 pub fn toBytes(e: Ristretto255) [32]u8 {83 pub fn toBytes(e: Ristretto255) [encoded_length]u8 {
82 const p = &e.p;84 const p = &e.p;
83 var u1_ = p.z.add(p.y); // Z+Y85 var u1_ = p.z.add(p.y); // Z+Y
84 const zmy = p.z.sub(p.y); // Z-Y86 const zmy = p.z.sub(p.y); // Z-Y
...@@ -151,7 +153,7 @@ pub const Ristretto255 = struct {...@@ -151,7 +153,7 @@ pub const Ristretto255 = struct {
151 /// Multiply a Ristretto255 element with a scalar.153 /// Multiply a Ristretto255 element with a scalar.
152 /// Return error.WeakPublicKey if the resulting element is154 /// Return error.WeakPublicKey if the resulting element is
153 /// the identity element.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 return Ristretto255{ .p = try p.p.mul(s) };157 return Ristretto255{ .p = try p.p.mul(s) };
156 }158 }
157159
...@@ -170,7 +172,7 @@ test "ristretto255" {...@@ -170,7 +172,7 @@ test "ristretto255" {
170 var buf: [256]u8 = undefined;172 var buf: [256]u8 = undefined;
171 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76");173 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76");
172174
173 var r: [32]u8 = undefined;175 var r: [Ristretto255.encoded_length]u8 = undefined;
174 try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919");176 try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919");
175 var q = try Ristretto255.fromBytes(r);177 var q = try Ristretto255.fromBytes(r);
176 q = q.dbl().add(p);178 q = q.dbl().add(p);
lib/std/crypto/25519/x25519.zig+43-22
...@@ -8,6 +8,8 @@ const crypto = std.crypto;...@@ -8,6 +8,8 @@ const crypto = std.crypto;
8const mem = std.mem;8const mem = std.mem;
9const fmt = std.fmt;9const fmt = std.fmt;
1010
11const Sha512 = crypto.hash.sha2.Sha512;
12
11/// X25519 DH function.13/// X25519 DH function.
12pub const X25519 = struct {14pub const X25519 = struct {
13 /// The underlying elliptic curve.15 /// The underlying elliptic curve.
...@@ -37,33 +39,55 @@ pub const X25519 = struct {...@@ -37,33 +39,55 @@ pub const X25519 = struct {
37 };39 };
38 var kp: KeyPair = undefined;40 var kp: KeyPair = undefined;
39 mem.copy(u8, &kp.secret_key, sk[0..]);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 return kp;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 };
4460
45 /// Compute the public key for a given private key.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 const q = try Curve.basePoint.clampedMul(secret_key);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 }
5073
51 /// Compute the scalar product of a public key and a secret scalar.74 /// Compute the scalar product of a public key and a secret scalar.
52 /// Note that the output should not be used as a shared secret without75 /// Note that the output should not be used as a shared secret without
53 /// hashing it first.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 const q = try Curve.fromBytes(public_key).clampedMul(secret_key);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};
5982
83const htest = @import("../test.zig");
84
60test "x25519 public key calculation from secret key" {85test "x25519 public key calculation from secret key" {
61 var sk: [32]u8 = undefined;86 var sk: [32]u8 = undefined;
62 var pk_expected: [32]u8 = undefined;87 var pk_expected: [32]u8 = undefined;
63 var pk_calculated: [32]u8 = undefined;
64 try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");88 try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
65 try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");89 try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
66 try X25519.recoverPublicKey(&pk_calculated, sk);90 const pk_calculated = try X25519.recoverPublicKey(sk);
67 std.testing.expectEqual(pk_calculated, pk_expected);91 std.testing.expectEqual(pk_calculated, pk_expected);
68}92}
6993
...@@ -73,9 +97,7 @@ test "x25519 rfc7748 vector1" {...@@ -73,9 +97,7 @@ test "x25519 rfc7748 vector1" {
7397
74 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 };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 };
7599
76 var output: [32]u8 = undefined;100 const output = try X25519.scalarmult(secret_key, public_key);
77
78 try X25519.scalarmult(&output, secret_key, public_key);
79 std.testing.expectEqual(output, expected_output);101 std.testing.expectEqual(output, expected_output);
80}102}
81103
...@@ -85,9 +107,7 @@ test "x25519 rfc7748 vector2" {...@@ -85,9 +107,7 @@ test "x25519 rfc7748 vector2" {
85107
86 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 };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 };
87109
88 var output: [32]u8 = undefined;110 const output = try X25519.scalarmult(secret_key, public_key);
89
90 try X25519.scalarmult(&output, secret_key, public_key);
91 std.testing.expectEqual(output, expected_output);111 std.testing.expectEqual(output, expected_output);
92}112}
93113
...@@ -100,9 +120,7 @@ test "x25519 rfc7748 one iteration" {...@@ -100,9 +120,7 @@ test "x25519 rfc7748 one iteration" {
100120
101 var i: usize = 0;121 var i: usize = 0;
102 while (i < 1) : (i += 1) {122 while (i < 1) : (i += 1) {
103 var output: [32]u8 = undefined;123 const output = try X25519.scalarmult(k, u);
104 try X25519.scalarmult(output[0..], k, u);
105
106 mem.copy(u8, u[0..], k[0..]);124 mem.copy(u8, u[0..], k[0..]);
107 mem.copy(u8, k[0..], output[0..]);125 mem.copy(u8, k[0..], output[0..]);
108 }126 }
...@@ -124,9 +142,7 @@ test "x25519 rfc7748 1,000 iterations" {...@@ -124,9 +142,7 @@ test "x25519 rfc7748 1,000 iterations" {
124142
125 var i: usize = 0;143 var i: usize = 0;
126 while (i < 1000) : (i += 1) {144 while (i < 1000) : (i += 1) {
127 var output: [32]u8 = undefined;145 const output = try X25519.scalarmult(&k, &u);
128 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
129
130 mem.copy(u8, u[0..], k[0..]);146 mem.copy(u8, u[0..], k[0..]);
131 mem.copy(u8, k[0..], output[0..]);147 mem.copy(u8, k[0..], output[0..]);
132 }148 }
...@@ -147,12 +163,17 @@ test "x25519 rfc7748 1,000,000 iterations" {...@@ -147,12 +163,17 @@ test "x25519 rfc7748 1,000,000 iterations" {
147163
148 var i: usize = 0;164 var i: usize = 0;
149 while (i < 1000000) : (i += 1) {165 while (i < 1000000) : (i += 1) {
150 var output: [32]u8 = undefined;166 const output = try X25519.scalarmult(&k, &u);
151 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
152
153 mem.copy(u8, u[0..], k[0..]);167 mem.copy(u8, u[0..], k[0..]);
154 mem.copy(u8, k[0..], output[0..]);168 mem.copy(u8, k[0..], output[0..]);
155 }169 }
156170
157 std.testing.expectEqual(k[0..], expected_output);171 std.testing.expectEqual(k[0..], expected_output);
158}172}
173
174test "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,18 +98,20 @@ const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
98pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {98pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
99 std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);99 std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
100100
101 var in: [DhKeyExchange.shared_length]u8 = undefined;101 var secret: [DhKeyExchange.shared_length]u8 = undefined;
102 prng.random.bytes(in[0..]);102 prng.random.bytes(secret[0..]);
103103
104 var out: [DhKeyExchange.shared_length]u8 = undefined;104 var public: [DhKeyExchange.shared_length]u8 = undefined;
105 prng.random.bytes(out[0..]);105 prng.random.bytes(public[0..]);
106106
107 var timer = try Timer.start();107 var timer = try Timer.start();
108 const start = timer.lap();108 const start = timer.lap();
109 {109 {
110 var i: usize = 0;110 var i: usize = 0;
111 while (i < exchange_count) : (i += 1) {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 mem.doNotOptimizeAway(&out);115 mem.doNotOptimizeAway(&out);
114 }116 }
115 }117 }
lib/std/crypto/salsa20.zig+1-2
...@@ -485,8 +485,7 @@ pub const Box = struct {...@@ -485,8 +485,7 @@ pub const Box = struct {
485485
486 /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.486 /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
487 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {487 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {
488 var p: [32]u8 = undefined;488 const p = try X25519.scalarmult(secret_key, public_key);
489 try X25519.scalarmult(&p, secret_key, public_key);
490 const zero = [_]u8{0} ** 16;489 const zero = [_]u8{0} ** 16;
491 return Salsa20Impl.hsalsa20(zero, p);490 return Salsa20Impl.hsalsa20(zero, p);
492 }491 }