Incorrect serialization of elliptic curve point whose X = 0
guidovranken opened this issue · 5 comments
guidovranken commented
Zig Version
zig-linux-x86_64-0.11.0-dev.3395+1e7dcaa3a
Steps to Reproduce and Observed Behavior
const std = @import("std");
const P256 = std.crypto.ecc.P256;
pub fn main() !void {
var ax: [32]u8 = [32]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
var ay: [32]u8 = [32]u8{
0x99, 0xB7, 0xA3, 0x86, 0xF1, 0xD0, 0x7C, 0x29, 0xDB, 0xCC, 0x42, 0xA2, 0x7B, 0x5F, 0x94, 0x49,
0xAB, 0xE3, 0xD5, 0x0D, 0xE2, 0x51, 0x78, 0xE8, 0xD7, 0x40, 0x7A, 0x95, 0xE8, 0xB0, 0x6C, 0x0B,
};
var bx: [32]u8 = [32]u8{
0xC2, 0x24, 0x2B, 0xE3, 0x59, 0x87, 0x9E, 0xCF, 0x8A, 0x92, 0xB8, 0xD9, 0x79, 0xC6, 0xDC, 0x96,
0xD9, 0x00, 0x5A, 0x00, 0x23, 0x6B, 0xA2, 0x0E, 0x7E, 0xB2, 0x46, 0x5F, 0xE7, 0x68, 0x29, 0xB4,
};
var by: [32]u8 = [32]u8{
0x43, 0x20, 0x84, 0x08, 0x5D, 0x73, 0xE7, 0xBF, 0x62, 0x48, 0x25, 0x88, 0x0C, 0x59, 0x08, 0xA4,
0x49, 0x08, 0x59, 0x76, 0x42, 0xFD, 0xE9, 0xE4, 0x40, 0xB3, 0xB8, 0x36, 0xA1, 0xB9, 0x05, 0xA6,
};
var a = P256.fromSerializedAffineCoordinates(ax, ay, .Big) catch return;
var b = P256.fromSerializedAffineCoordinates(bx, by, .Big) catch return;
var r = a.add(b).toUncompressedSec1();
var i: u32 = 0;
while (i < 65): (i+=1) {
std.debug.print("0x{x} ", .{r[i]});
}
std.debug.print("\n", .{});
}
Output:
0x4 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1
E.g. point (0, 1)
Expected Behavior
0x4 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x66 0x48 0x5c 0x78 0x0e 0x2f 0x83 0xd7 0x24 0x33 0xbd 0x5d 0x84 0xa0 0x6b 0xb6 0x54 0x1c 0x2a 0xf3 0x1d 0xae 0x87 0x17 0x28 0xbf 0x85 0x6a 0x17 0x4f 0x93 0xf4
E.g. point (0, 46263761741508638697010950048709651021688891777877937875096931459006746039284)
guidovranken commented
guidovranken commented
Also calling .neg()
on a
returns (0, 1) (it shouldn't)
jedisct1 commented
Thanks Guido! I'm gonna take a look at this shortly.
My intuition is that the issue is in the conversion to affine coordinates. The computations themselves are fine. Here's a simpler way to highlight the problem.
const std = @import("std");
const P256 = std.crypto.ecc.P256;
pub fn main() !void {
var ax: [32]u8 = [32]u8{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
var ay: [32]u8 = [32]u8{
0x99, 0xB7, 0xA3, 0x86, 0xF1, 0xD0, 0x7C, 0x29, 0xDB, 0xCC, 0x42, 0xA2, 0x7B, 0x5F, 0x94, 0x49,
0xAB, 0xE3, 0xD5, 0x0D, 0xE2, 0x51, 0x78, 0xE8, 0xD7, 0x40, 0x7A, 0x95, 0xE8, 0xB0, 0x6C, 0x0B,
};
var a = P256.fromSerializedAffineCoordinates(ax, ay, .Big) catch return;
std.debug.print(" a.x={s}\n", .{std.fmt.bytesToHex(a.x.toBytes(.Big), .lower)});
std.debug.print(" a.y={s}\n", .{std.fmt.bytesToHex(a.y.toBytes(.Big), .lower)});
std.debug.print(" a.z={s}\n", .{std.fmt.bytesToHex(a.z.toBytes(.Big), .lower)});
std.debug.print(" a.Z={s}\n", .{std.fmt.bytesToHex(a.z.invert().toBytes(.Big), .lower)});
const af = a.affineCoordinates();
std.debug.print("af.x={s}\n", .{std.fmt.bytesToHex(af.x.toBytes(.Big), .lower)});
std.debug.print("af.y={s}\n", .{std.fmt.bytesToHex(af.y.toBytes(.Big), .lower)});
}
With Z = 1/z = 1
, coordinates shouldn't change.
jedisct1 commented
When serializing, we were assuming that when the X coordinate was 0, it represented the point at infinity. Which is not the case if Z != 0. The PR above fixes it. Thanks!
guidovranken commented
Thanks. Changed the title for posterity.