Commit 2d11967734

Frank Denis <124872+jedisct1@users.noreply.github.com>
2021-05-09 18:20:43
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.
1 parent f67e756
Changed files (2)
lib
std
crypto
lib/std/crypto/pcurves/p256.zig
@@ -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 {
+        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.
@@ -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,
         }
@@ -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);
@@ -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);
@@ -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);
@@ -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;
     }
 
@@ -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;
@@ -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;
@@ -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");
 }
lib/std/crypto/pcurves/tests.zig
@@ -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());
+}