Skip to content

Commit e59dd7e

Browse files
jedisct1andrewrk
authored andcommitted
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.
1 parent ad6e095 commit e59dd7e

File tree

6 files changed

+70
-36
lines changed

6 files changed

+70
-36
lines changed

lib/std/crypto/25519/curve25519.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ pub const Curve25519 = struct {
100100
_ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey;
101101
return try ladder(p, s, 256);
102102
}
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+
}
103111
};
104112

105113
test "curve25519" {

lib/std/crypto/25519/edwards25519.zig

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub const Edwards25519 = struct {
1212
pub const Fe = @import("field.zig").Fe;
1313
/// Field arithmetic mod the order of the main subgroup.
1414
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

1618
x: Fe,
1719
y: Fe,
@@ -21,7 +23,7 @@ pub const Edwards25519 = struct {
2123
is_base: bool = false,
2224

2325
/// 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 {
2527
const z = Fe.one;
2628
const y = Fe.fromBytes(s);
2729
var u = y.sq();
@@ -43,7 +45,7 @@ pub const Edwards25519 = struct {
4345
}
4446

4547
/// Encode an Edwards25519 point.
46-
pub fn toBytes(p: Edwards25519) [32]u8 {
48+
pub fn toBytes(p: Edwards25519) [encoded_length]u8 {
4749
const zi = p.z.invert();
4850
var s = p.y.mul(zi).toBytes();
4951
s[31] ^= @as(u8, @boolToInt(p.x.mul(zi).isNegative())) << 7;

lib/std/crypto/25519/ristretto255.zig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub const Ristretto255 = struct {
1414
pub const Fe = Curve.Fe;
1515
/// Field arithmetic mod the order of the main subgroup.
1616
pub const scalar = Curve.scalar;
17+
/// Length in byte of an encoded element.
18+
pub const encoded_length: usize = 32;
1719

1820
p: Curve,
1921

@@ -32,7 +34,7 @@ pub const Ristretto255 = struct {
3234
return .{ .ratio_is_square = @boolToInt(has_m_root) | @boolToInt(has_p_root), .root = x.abs() };
3335
}
3436

35-
fn rejectNonCanonical(s: [32]u8) !void {
37+
fn rejectNonCanonical(s: [encoded_length]u8) !void {
3638
if ((s[0] & 1) != 0) {
3739
return error.NonCanonical;
3840
}
@@ -48,7 +50,7 @@ pub const Ristretto255 = struct {
4850
pub const basePoint = Ristretto255{ .p = Curve.basePoint };
4951

5052
/// Decode a Ristretto255 representative.
51-
pub fn fromBytes(s: [32]u8) !Ristretto255 {
53+
pub fn fromBytes(s: [encoded_length]u8) !Ristretto255 {
5254
try rejectNonCanonical(s);
5355
const s_ = Fe.fromBytes(s);
5456
const ss = s_.sq(); // s^2
@@ -78,7 +80,7 @@ pub const Ristretto255 = struct {
7880
}
7981

8082
/// Encode to a Ristretto255 representative.
81-
pub fn toBytes(e: Ristretto255) [32]u8 {
83+
pub fn toBytes(e: Ristretto255) [encoded_length]u8 {
8284
const p = &e.p;
8385
var u1_ = p.z.add(p.y); // Z+Y
8486
const zmy = p.z.sub(p.y); // Z-Y
@@ -151,7 +153,7 @@ pub const Ristretto255 = struct {
151153
/// Multiply a Ristretto255 element with a scalar.
152154
/// Return error.WeakPublicKey if the resulting element is
153155
/// 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 {
155157
return Ristretto255{ .p = try p.p.mul(s) };
156158
}
157159

@@ -170,7 +172,7 @@ test "ristretto255" {
170172
var buf: [256]u8 = undefined;
171173
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;
174176
try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919");
175177
var q = try Ristretto255.fromBytes(r);
176178
q = q.dbl().add(p);

lib/std/crypto/25519/x25519.zig

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const crypto = std.crypto;
88
const mem = std.mem;
99
const fmt = std.fmt;
1010

11+
const Sha512 = crypto.hash.sha2.Sha512;
12+
1113
/// X25519 DH function.
1214
pub const X25519 = struct {
1315
/// The underlying elliptic curve.
@@ -37,33 +39,55 @@ pub const X25519 = struct {
3739
};
3840
var kp: KeyPair = undefined;
3941
mem.copy(u8, &kp.secret_key, sk[0..]);
40-
try X25519.recoverPublicKey(&kp.public_key, sk);
42+
kp.public_key = try X25519.recoverPublicKey(sk);
4143
return kp;
4244
}
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+
}
4359
};
4460

4561
/// 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 {
4763
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();
4972
}
5073

5174
/// Compute the scalar product of a public key and a secret scalar.
5275
/// Note that the output should not be used as a shared secret without
5376
/// 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 {
5578
const q = try Curve.fromBytes(public_key).clampedMul(secret_key);
56-
mem.copy(u8, out, q.toBytes()[0..]);
79+
return q.toBytes();
5780
}
5881
};
5982

83+
const htest = @import("../test.zig");
84+
6085
test "x25519 public key calculation from secret key" {
6186
var sk: [32]u8 = undefined;
6287
var pk_expected: [32]u8 = undefined;
63-
var pk_calculated: [32]u8 = undefined;
6488
try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
6589
try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
66-
try X25519.recoverPublicKey(&pk_calculated, sk);
90+
const pk_calculated = try X25519.recoverPublicKey(sk);
6791
std.testing.expectEqual(pk_calculated, pk_expected);
6892
}
6993

@@ -73,9 +97,7 @@ test "x25519 rfc7748 vector1" {
7397

7498
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;
77-
78-
try X25519.scalarmult(&output, secret_key, public_key);
100+
const output = try X25519.scalarmult(secret_key, public_key);
79101
std.testing.expectEqual(output, expected_output);
80102
}
81103

@@ -85,9 +107,7 @@ test "x25519 rfc7748 vector2" {
85107

86108
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;
89-
90-
try X25519.scalarmult(&output, secret_key, public_key);
110+
const output = try X25519.scalarmult(secret_key, public_key);
91111
std.testing.expectEqual(output, expected_output);
92112
}
93113

@@ -100,9 +120,7 @@ test "x25519 rfc7748 one iteration" {
100120

101121
var i: usize = 0;
102122
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);
106124
mem.copy(u8, u[0..], k[0..]);
107125
mem.copy(u8, k[0..], output[0..]);
108126
}
@@ -124,9 +142,7 @@ test "x25519 rfc7748 1,000 iterations" {
124142

125143
var i: usize = 0;
126144
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);
130146
mem.copy(u8, u[0..], k[0..]);
131147
mem.copy(u8, k[0..], output[0..]);
132148
}
@@ -147,12 +163,17 @@ test "x25519 rfc7748 1,000,000 iterations" {
147163

148164
var i: usize = 0;
149165
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);
153167
mem.copy(u8, u[0..], k[0..]);
154168
mem.copy(u8, k[0..], output[0..]);
155169
}
156170

157171
std.testing.expectEqual(k[0..], expected_output);
158172
}
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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,20 @@ const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
9898
pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
9999
std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
100100

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..]);
103103

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..]);
106106

107107
var timer = try Timer.start();
108108
const start = timer.lap();
109109
{
110110
var i: usize = 0;
111111
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]);
113115
mem.doNotOptimizeAway(&out);
114116
}
115117
}

lib/std/crypto/salsa20.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,7 @@ pub const Box = struct {
485485

486486
/// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
487487
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);
490489
const zero = [_]u8{0} ** 16;
491490
return Salsa20Impl.hsalsa20(zero, p);
492491
}

0 commit comments

Comments
 (0)