Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/std/crypto/25519/curve25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ pub const Curve25519 = struct {
_ = ladder(p, cofactor, 4) catch |_| return error.WeakPublicKey;
return try ladder(p, s, 256);
}

/// Compute the Curve25519 equivalent to an Edwards25519 point.
pub fn fromEdwards25519(p: std.crypto.ecc.Edwards25519) !Curve25519 {
try p.clearCofactor().rejectIdentity();
const one = std.crypto.ecc.Edwards25519.Fe.one;
const x = one.add(p.y).mul(one.sub(p.y).invert()); // xMont=(1+yEd)/(1-yEd)
return Curve25519{ .x = x };
}
};

test "curve25519" {
Expand Down
6 changes: 4 additions & 2 deletions lib/std/crypto/25519/edwards25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub const Edwards25519 = struct {
pub const Fe = @import("field.zig").Fe;
/// Field arithmetic mod the order of the main subgroup.
pub const scalar = @import("scalar.zig");
/// Length in bytes of a compressed representation of a point.
pub const encoded_length: usize = 32;

x: Fe,
y: Fe,
Expand All @@ -21,7 +23,7 @@ pub const Edwards25519 = struct {
is_base: bool = false,

/// Decode an Edwards25519 point from its compressed (Y+sign) coordinates.
pub fn fromBytes(s: [32]u8) !Edwards25519 {
pub fn fromBytes(s: [encoded_length]u8) !Edwards25519 {
const z = Fe.one;
const y = Fe.fromBytes(s);
var u = y.sq();
Expand All @@ -43,7 +45,7 @@ pub const Edwards25519 = struct {
}

/// Encode an Edwards25519 point.
pub fn toBytes(p: Edwards25519) [32]u8 {
pub fn toBytes(p: Edwards25519) [encoded_length]u8 {
const zi = p.z.invert();
var s = p.y.mul(zi).toBytes();
s[31] ^= @as(u8, @boolToInt(p.x.mul(zi).isNegative())) << 7;
Expand Down
12 changes: 7 additions & 5 deletions lib/std/crypto/25519/ristretto255.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub const Ristretto255 = struct {
pub const Fe = Curve.Fe;
/// Field arithmetic mod the order of the main subgroup.
pub const scalar = Curve.scalar;
/// Length in byte of an encoded element.
pub const encoded_length: usize = 32;

p: Curve,

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

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

/// Decode a Ristretto255 representative.
pub fn fromBytes(s: [32]u8) !Ristretto255 {
pub fn fromBytes(s: [encoded_length]u8) !Ristretto255 {
try rejectNonCanonical(s);
const s_ = Fe.fromBytes(s);
const ss = s_.sq(); // s^2
Expand Down Expand Up @@ -78,7 +80,7 @@ pub const Ristretto255 = struct {
}

/// Encode to a Ristretto255 representative.
pub fn toBytes(e: Ristretto255) [32]u8 {
pub fn toBytes(e: Ristretto255) [encoded_length]u8 {
const p = &e.p;
var u1_ = p.z.add(p.y); // Z+Y
const zmy = p.z.sub(p.y); // Z-Y
Expand Down Expand Up @@ -151,7 +153,7 @@ pub const Ristretto255 = struct {
/// Multiply a Ristretto255 element with a scalar.
/// Return error.WeakPublicKey if the resulting element is
/// the identity element.
pub inline fn mul(p: Ristretto255, s: [32]u8) !Ristretto255 {
pub inline fn mul(p: Ristretto255, s: [encoded_length]u8) !Ristretto255 {
return Ristretto255{ .p = try p.p.mul(s) };
}

Expand All @@ -170,7 +172,7 @@ test "ristretto255" {
var buf: [256]u8 = undefined;
std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76");

var r: [32]u8 = undefined;
var r: [Ristretto255.encoded_length]u8 = undefined;
try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919");
var q = try Ristretto255.fromBytes(r);
q = q.dbl().add(p);
Expand Down
65 changes: 43 additions & 22 deletions lib/std/crypto/25519/x25519.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const crypto = std.crypto;
const mem = std.mem;
const fmt = std.fmt;

const Sha512 = crypto.hash.sha2.Sha512;

/// X25519 DH function.
pub const X25519 = struct {
/// The underlying elliptic curve.
Expand Down Expand Up @@ -37,33 +39,55 @@ pub const X25519 = struct {
};
var kp: KeyPair = undefined;
mem.copy(u8, &kp.secret_key, sk[0..]);
try X25519.recoverPublicKey(&kp.public_key, sk);
kp.public_key = try X25519.recoverPublicKey(sk);
return kp;
}

/// Create a key pair from an Ed25519 key pair
pub fn fromEd25519(ed25519_key_pair: crypto.sign.Ed25519.KeyPair) !KeyPair {
const seed = ed25519_key_pair.secret_key[0..32];
var az: [Sha512.digest_length]u8 = undefined;
Sha512.hash(seed, &az, .{});
var sk = az[0..32].*;
Curve.scalar.clamp(&sk);
const pk = try publicKeyFromEd25519(ed25519_key_pair.public_key);
return KeyPair{
.public_key = pk,
.secret_key = sk,
};
}
};

/// Compute the public key for a given private key.
pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void {
pub fn recoverPublicKey(secret_key: [secret_length]u8) ![public_length]u8 {
const q = try Curve.basePoint.clampedMul(secret_key);
mem.copy(u8, public_key, q.toBytes()[0..]);
return q.toBytes();
}

/// Compute the X25519 equivalent to an Ed25519 public eky.
pub fn publicKeyFromEd25519(ed25519_public_key: [crypto.sign.Ed25519.public_length]u8) ![public_length]u8 {
const pk_ed = try crypto.ecc.Edwards25519.fromBytes(ed25519_public_key);
const pk = try Curve.fromEdwards25519(pk_ed);
return pk.toBytes();
}

/// Compute the scalar product of a public key and a secret scalar.
/// Note that the output should not be used as a shared secret without
/// hashing it first.
pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void {
pub fn scalarmult(secret_key: [secret_length]u8, public_key: [public_length]u8) ![shared_length]u8 {
const q = try Curve.fromBytes(public_key).clampedMul(secret_key);
mem.copy(u8, out, q.toBytes()[0..]);
return q.toBytes();
}
};

const htest = @import("../test.zig");

test "x25519 public key calculation from secret key" {
var sk: [32]u8 = undefined;
var pk_expected: [32]u8 = undefined;
var pk_calculated: [32]u8 = undefined;
try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
try X25519.recoverPublicKey(&pk_calculated, sk);
const pk_calculated = try X25519.recoverPublicKey(sk);
std.testing.expectEqual(pk_calculated, pk_expected);
}

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

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 };

var output: [32]u8 = undefined;

try X25519.scalarmult(&output, secret_key, public_key);
const output = try X25519.scalarmult(secret_key, public_key);
std.testing.expectEqual(output, expected_output);
}

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

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 };

var output: [32]u8 = undefined;

try X25519.scalarmult(&output, secret_key, public_key);
const output = try X25519.scalarmult(secret_key, public_key);
std.testing.expectEqual(output, expected_output);
}

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

var i: usize = 0;
while (i < 1) : (i += 1) {
var output: [32]u8 = undefined;
try X25519.scalarmult(output[0..], k, u);

const output = try X25519.scalarmult(k, u);
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
}
Expand All @@ -124,9 +142,7 @@ test "x25519 rfc7748 1,000 iterations" {

var i: usize = 0;
while (i < 1000) : (i += 1) {
var output: [32]u8 = undefined;
std.testing.expect(X25519.scalarmult(output[0..], &k, &u));

const output = try X25519.scalarmult(&k, &u);
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
}
Expand All @@ -147,12 +163,17 @@ test "x25519 rfc7748 1,000,000 iterations" {

var i: usize = 0;
while (i < 1000000) : (i += 1) {
var output: [32]u8 = undefined;
std.testing.expect(X25519.scalarmult(output[0..], &k, &u));

const output = try X25519.scalarmult(&k, &u);
mem.copy(u8, u[0..], k[0..]);
mem.copy(u8, k[0..], output[0..]);
}

std.testing.expectEqual(k[0..], expected_output);
}

test "edwards25519 -> curve25519 map" {
const ed_kp = try crypto.sign.Ed25519.KeyPair.create([_]u8{0x42} ** 32);
const mont_kp = try X25519.KeyPair.fromEd25519(ed_kp);
htest.assertEqual("90e7595fc89e52fdfddce9c6a43d74dbf6047025ee0462d2d172e8b6a2841d6e", &mont_kp.secret_key);
htest.assertEqual("cc4f2cdb695dd766f34118eb67b98652fed1d8bc49c330b119bbfa8a64989378", &mont_kp.public_key);
}
12 changes: 7 additions & 5 deletions lib/std/crypto/benchmark.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,20 @@ const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);

var in: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(in[0..]);
var secret: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(secret[0..]);

var out: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(out[0..]);
var public: [DhKeyExchange.shared_length]u8 = undefined;
prng.random.bytes(public[0..]);

var timer = try Timer.start();
const start = timer.lap();
{
var i: usize = 0;
while (i < exchange_count) : (i += 1) {
try DhKeyExchange.scalarmult(&out, out, in);
const out = try DhKeyExchange.scalarmult(secret, public);
mem.copy(u8, secret[0..16], out[0..16]);
mem.copy(u8, public[0..16], out[16..32]);
mem.doNotOptimizeAway(&out);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/std/crypto/salsa20.zig
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,7 @@ pub const Box = struct {

/// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {
var p: [32]u8 = undefined;
try X25519.scalarmult(&p, secret_key, public_key);
const p = try X25519.scalarmult(secret_key, public_key);
const zero = [_]u8{0} ** 16;
return Salsa20Impl.hsalsa20(zero, p);
}
Expand Down