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

Instead of multiple references to an anonymous structure to represent affine coordinates, add an actual `AffineCoordinates` structure.

Also properly handle the neutral element during coordinate conversion and fix mixed addition.

And comptime the small precomputation table for basepoint multiplication.
This commit is contained in:
Frank Denis 2021-05-09 18:20:43 +02:00 committed by GitHub
parent f67e756211
commit 2d11967734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 16 deletions

View File

@ -49,21 +49,26 @@ pub const P256 = struct {
} }
/// Create a point from affine coordinates after checking that they match the curve equation. /// 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 {
const x = p.x;
const y = p.y;
const x3AxB = x.sq().mul(x).sub(x).sub(x).sub(x).add(B); const x3AxB = x.sq().mul(x).sub(x).sub(x).sub(x).add(B);
const yy = y.sq(); 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; return error.InvalidEncoding;
} }
const p: P256 = .{ .x = x, .y = y, .z = Fe.one }; var ret = P256{ .x = x, .y = y, .z = Fe.one };
return p; ret.z.cMov(P256.identityElement.z, is_identity);
return ret;
} }
/// Create a point from serialized affine coordinates. /// Create a point from serialized affine coordinates.
pub fn fromSerializedAffineCoordinates(xs: [32]u8, ys: [32]u8, endian: builtin.Endian) (NonCanonicalError || EncodingError)!P256 { pub fn fromSerializedAffineCoordinates(xs: [32]u8, ys: [32]u8, endian: builtin.Endian) (NonCanonicalError || EncodingError)!P256 {
const x = try Fe.fromBytes(xs, endian); const x = try Fe.fromBytes(xs, endian);
const y = try Fe.fromBytes(ys, 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. /// Recover the Y coordinate from the X coordinate.
@ -96,7 +101,7 @@ pub const P256 = struct {
if (encoded.len != 64) return error.InvalidEncoding; if (encoded.len != 64) return error.InvalidEncoding;
const x = try Fe.fromBytes(encoded[0..32].*, .Big); const x = try Fe.fromBytes(encoded[0..32].*, .Big);
const y = try Fe.fromBytes(encoded[32..64].*, .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, else => return error.InvalidEncoding,
} }
@ -177,7 +182,7 @@ pub const P256 = struct {
/// Add P256 points, the second being specified using affine coordinates. /// Add P256 points, the second being specified using affine coordinates.
// Algorithm 5 from https://eprint.iacr.org/2015/1060.pdf // 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 t0 = p.x.mul(q.x);
var t1 = p.y.mul(q.y); var t1 = p.y.mul(q.y);
var t3 = q.x.add(q.y); var t3 = q.x.add(q.y);
@ -194,9 +199,9 @@ pub const P256 = struct {
Z3 = X3.dbl(); Z3 = X3.dbl();
X3 = X3.add(Z3); X3 = X3.add(Z3);
Z3 = t1.sub(X3); Z3 = t1.sub(X3);
X3 = t1.dbl(); X3 = t1.add(X3);
Y3 = B.mul(Y3); Y3 = B.mul(Y3);
t1 = p.z.add(p.z); t1 = p.z.dbl();
var t2 = t1.add(p.z); var t2 = t1.add(p.z);
Y3 = Y3.sub(t2); Y3 = Y3.sub(t2);
Y3 = Y3.sub(t0); Y3 = Y3.sub(t0);
@ -214,14 +219,16 @@ pub const P256 = struct {
Z3 = t4.mul(Z3); Z3 = t4.mul(Z3);
t1 = t3.mul(t0); t1 = t3.mul(t0);
Z3 = Z3.add(t1); Z3 = Z3.add(t1);
return .{ var ret = P256{
.x = X3, .x = X3,
.y = Y3, .y = Y3,
.z = Z3, .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 // Algorithm 4 from https://eprint.iacr.org/2015/1060.pdf
pub fn add(p: P256, q: P256) P256 { pub fn add(p: P256, q: P256) P256 {
var t0 = p.x.mul(q.x); var t0 = p.x.mul(q.x);
@ -274,18 +281,19 @@ pub const P256 = struct {
}; };
} }
// Subtract P256 points. /// Subtract P256 points.
pub fn sub(p: P256, q: P256) P256 { pub fn sub(p: P256, q: P256) P256 {
return p.add(q.neg()); return p.add(q.neg());
} }
/// Return affine coordinates. /// 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 zinv = p.z.invert();
const ret = .{ var ret = AffineCoordinates{
.x = p.x.mul(zinv), .x = p.x.mul(zinv),
.y = p.y.mul(zinv), .y = p.y.mul(zinv),
}; };
ret.cMov(AffineCoordinates.identityElement, @boolToInt(p.x.isZero()));
return ret; return ret;
} }
@ -382,11 +390,21 @@ pub const P256 = struct {
return pc; 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. /// Multiply an elliptic curve point by a scalar.
/// Return error.IdentityElement if the result is the identity element. /// Return error.IdentityElement if the result is the identity element.
pub fn mul(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 { pub fn mul(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 {
const s = if (endian == .Little) s_ else Fe.orderSwap(s_); 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(); try p.rejectIdentity();
const xpc = precompute(p, 15); const xpc = precompute(p, 15);
break :pc xpc; break :pc xpc;
@ -398,7 +416,7 @@ pub const P256 = struct {
/// This can be used for signature verification. /// This can be used for signature verification.
pub fn mulPublic(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 { pub fn mulPublic(p: P256, s_: [32]u8, endian: builtin.Endian) IdentityElementError!P256 {
const s = if (endian == .Little) s_ else Fe.orderSwap(s_); 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(); try p.rejectIdentity();
const xpc = precompute(p, 8); const xpc = precompute(p, 8);
break :pc xpc; break :pc xpc;
@ -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" { test "p256" {
_ = @import("tests.zig"); _ = @import("tests.zig");
} }

View File

@ -101,3 +101,9 @@ test "p256 field element non-canonical encoding" {
const s = [_]u8{0xff} ** 32; const s = [_]u8{0xff} ** 32;
try testing.expectError(error.NonCanonical, P256.Fe.fromBytes(s, .Little)); 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());
}