From a1aa7d52c4d2c1460442eb058d1b0a47a6e0ede0 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 9 May 2021 00:13:24 +0200 Subject: [PATCH] p256: properly handle neutral element & add AffineCoordinates struct 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. --- lib/std/crypto/pcurves/p256.zig | 64 ++++++++++++++++++++++++-------- lib/std/crypto/pcurves/tests.zig | 6 +++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/lib/std/crypto/pcurves/p256.zig b/lib/std/crypto/pcurves/p256.zig index 0b1c584fa980..8adf7f271721 100644 --- a/lib/std/crypto/pcurves/p256.zig +++ b/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"); } diff --git a/lib/std/crypto/pcurves/tests.zig b/lib/std/crypto/pcurves/tests.zig index 6d9682abf162..c4a09b8f0d76 100644 --- a/lib/std/crypto/pcurves/tests.zig +++ b/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()); +}