Skip to content
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

p256: properly handle neutral element & add AffineCoordinates struct #8718

Merged
merged 1 commit into from
May 9, 2021
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
64 changes: 48 additions & 16 deletions lib/std/crypto/pcurves/p256.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,26 @@ pub const P256 = struct {
}

/// Create a point from affine coordinates after checking that they match the curve equation.
pub fn fromAffineCoordinates(x: Fe, y: Fe) EncodingError!P256 {
pub fn fromAffineCoordinates(p: AffineCoordinates) EncodingError!P256 {
Copy link

@Mouvedia Mouvedia May 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI
this should not really be considered breaking because p256.zig was added very recently (#8627)

const x = p.x;
const y = p.y;
const x3AxB = x.sq().mul(x).sub(x).sub(x).sub(x).add(B);
const yy = y.sq();
if (!x3AxB.equivalent(yy)) {
const on_curve = @boolToInt(x3AxB.equivalent(yy));
const is_identity = @boolToInt(x.equivalent(AffineCoordinates.identityElement.x)) & @boolToInt(y.equivalent(AffineCoordinates.identityElement.y));
if ((on_curve | is_identity) == 0) {
return error.InvalidEncoding;
}
const p: P256 = .{ .x = x, .y = y, .z = Fe.one };
return p;
var ret = P256{ .x = x, .y = y, .z = Fe.one };
ret.z.cMov(P256.identityElement.z, is_identity);
return ret;
}

/// Create a point from serialized affine coordinates.
pub fn fromSerializedAffineCoordinates(xs: [32]u8, ys: [32]u8, endian: builtin.Endian) (NonCanonicalError || EncodingError)!P256 {
const x = try Fe.fromBytes(xs, endian);
const y = try Fe.fromBytes(ys, endian);
return fromAffineCoordinates(x, y);
return fromAffineCoordinates(.{ .x = x, .y = y });
}

/// Recover the Y coordinate from the X coordinate.
Expand Down Expand Up @@ -96,7 +101,7 @@ pub const P256 = struct {
if (encoded.len != 64) return error.InvalidEncoding;
const x = try Fe.fromBytes(encoded[0..32].*, .Big);
const y = try Fe.fromBytes(encoded[32..64].*, .Big);
return P256.fromAffineCoordinates(x, y);
return P256.fromAffineCoordinates(.{ .x = x, .y = y });
},
else => return error.InvalidEncoding,
}
Expand Down Expand Up @@ -177,7 +182,7 @@ pub const P256 = struct {

/// Add P256 points, the second being specified using affine coordinates.
// Algorithm 5 from https://eprint.iacr.org/2015/1060.pdf
pub fn addMixed(p: P256, q: struct { x: Fe, y: Fe }) P256 {
pub fn addMixed(p: P256, q: AffineCoordinates) P256 {
var t0 = p.x.mul(q.x);
var t1 = p.y.mul(q.y);
var t3 = q.x.add(q.y);
Expand All @@ -194,9 +199,9 @@ pub const P256 = struct {
Z3 = X3.dbl();
X3 = X3.add(Z3);
Z3 = t1.sub(X3);
X3 = t1.dbl();
X3 = t1.add(X3);
Y3 = B.mul(Y3);
t1 = p.z.add(p.z);
t1 = p.z.dbl();
var t2 = t1.add(p.z);
Y3 = Y3.sub(t2);
Y3 = Y3.sub(t0);
Expand All @@ -214,14 +219,16 @@ pub const P256 = struct {
Z3 = t4.mul(Z3);
t1 = t3.mul(t0);
Z3 = Z3.add(t1);
return .{
var ret = P256{
.x = X3,
.y = Y3,
.z = Z3,
};
ret.cMov(p, @boolToInt(q.x.isZero()));
return ret;
}

// Add P256 points.
/// Add P256 points.
// Algorithm 4 from https://eprint.iacr.org/2015/1060.pdf
pub fn add(p: P256, q: P256) P256 {
var t0 = p.x.mul(q.x);
Expand Down Expand Up @@ -274,18 +281,19 @@ pub const P256 = struct {
};
}

// Subtract P256 points.
/// Subtract P256 points.
pub fn sub(p: P256, q: P256) P256 {
return p.add(q.neg());
}

/// Return affine coordinates.
pub fn affineCoordinates(p: P256) struct { x: Fe, y: Fe } {
pub fn affineCoordinates(p: P256) AffineCoordinates {
const zinv = p.z.invert();
const ret = .{
var ret = AffineCoordinates{
.x = p.x.mul(zinv),
.y = p.y.mul(zinv),
};
ret.cMov(AffineCoordinates.identityElement, @boolToInt(p.x.isZero()));
return ret;
}

Expand Down Expand Up @@ -382,11 +390,21 @@ pub const P256 = struct {
return pc;
}

const basePointPc = comptime pc: {
@setEvalBranchQuota(50000);
break :pc precompute(P256.basePoint, 15);
};

const basePointPc8 = comptime pc: {
@setEvalBranchQuota(50000);
break :pc precompute(P256.basePoint, 8);
};

/// Multiply an elliptic curve point by a scalar.
/// Return error.IdentityElement if the result is the identity element.
pub fn mul(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 {
const s = if (endian == .Little) s_ else Fe.orderSwap(s_);
const pc = if (p.is_base) precompute(P256.basePoint, 15) else pc: {
const pc = if (p.is_base) basePointPc else pc: {
try p.rejectIdentity();
const xpc = precompute(p, 15);
break :pc xpc;
Expand All @@ -398,7 +416,7 @@ pub const P256 = struct {
/// This can be used for signature verification.
pub fn mulPublic(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 {
const s = if (endian == .Little) s_ else Fe.orderSwap(s_);
const pc = if (p.is_base) precompute(P256.basePoint, 8) else pc: {
const pc = if (p.is_base) basePointPc8 else pc: {
try p.rejectIdentity();
const xpc = precompute(p, 8);
break :pc xpc;
Expand All @@ -407,6 +425,20 @@ pub const P256 = struct {
}
};

/// A point in affine coordinates.
pub const AffineCoordinates = struct {
x: P256.Fe,
y: P256.Fe,

/// Identity element in affine coordinates.
pub const identityElement = AffineCoordinates{ .x = P256.identityElement.x, .y = P256.identityElement.y };

fn cMov(p: *AffineCoordinates, a: AffineCoordinates, c: u1) void {
p.x.cMov(a.x, c);
p.y.cMov(a.y, c);
}
};

test "p256" {
_ = @import("tests.zig");
}
6 changes: 6 additions & 0 deletions lib/std/crypto/pcurves/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,9 @@ test "p256 field element non-canonical encoding" {
const s = [_]u8{0xff} ** 32;
try testing.expectError(error.NonCanonical, P256.Fe.fromBytes(s, .Little));
}

test "p256 neutral element decoding" {
try testing.expectError(error.InvalidEncoding, P256.fromAffineCoordinates(.{ .x = P256.Fe.zero, .y = P256.Fe.zero }));
const p = try P256.fromAffineCoordinates(.{ .x = P256.Fe.zero, .y = P256.Fe.one });
try testing.expectError(error.IdentityElement, p.rejectIdentity());
}