authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-15 10:03:55+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-10-15 10:03:55+02:00
log692a774deb45a7326e4783f8ce828865c3559f4b
tree90bb4f454069fe1a3a67ee8bf3fe7fec4dcb7cbc
parent9343d0fe4bb700cf58bc861596cf0d9f2093b494
parent3ba52a1741f6d1514d818dfc032094a16edad9db

Merge pull request 'crypto.ml_dsa: reject invalid encodings of small secret polynomials' (#37091) from jedisct1/zig:mldsacoefcheck into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37091

1 files changed, 46 insertions(+), 24 deletions(-)

lib/std/crypto/ml_dsa.zig+46-24
......@@ -443,14 +443,14 @@ fn PolyVec(comptime len: u8) type {
443443 }
444444
445445 /// Unpack vector with coefficients in [-eta, eta]
446 fn unpackLeqEta(comptime eta: u8, buf: []const u8) Self {
446 fn unpackLeqEta(comptime eta: u8, buf: []const u8) EncodingError!Self {
447447 const poly_size = if (eta == 2) 96 else 128;
448 const unpack_fn = struct {
449 fn unpack(b: []const u8) Poly {
450 return polyUnpackLeqEta(eta, b);
451 }
452 }.unpack;
453 return unpackWith(poly_size, unpack_fn, buf);
448 var result: Self = undefined;
449 inline for (0..len) |i| {
450 const offset = i * poly_size;
451 result.ps[i] = try polyUnpackLeqEta(eta, buf[offset..][0..poly_size]);
452 }
453 return result;
454454 }
455455
456456 /// Pack vector of polynomials with coefficients < gamma1
......@@ -996,7 +996,7 @@ fn polyPackLeqEta(p: Poly, comptime eta: u8, buf: []u8) void {
996996
997997/// Unpack polynomial with coefficients in [Q-eta, Q+eta] from bytes.
998998/// Output coefficients will not be normalized, but in [q-η, q+η].
999fn polyUnpackLeqEta(comptime eta: u8, buf: []const u8) Poly {
999fn polyUnpackLeqEta(comptime eta: u8, buf: []const u8) EncodingError!Poly {
10001000 comptime {
10011001 if (eta != 2 and eta != 4) {
10021002 @compileError("eta must be 2 or 4");
......@@ -1010,22 +1010,31 @@ fn polyUnpackLeqEta(comptime eta: u8, buf: []const u8) Poly {
10101010 var j: usize = 0;
10111011 var i: usize = 0;
10121012 while (i < buf.len) : (i += 3) {
1013 p.cs[j] = Q + eta - (buf[i] & 7);
1014 p.cs[j + 1] = Q + eta - ((buf[i] >> 3) & 7);
1015 p.cs[j + 2] = Q + eta - ((buf[i] >> 6) | ((buf[i + 1] << 2) & 7));
1016 p.cs[j + 3] = Q + eta - ((buf[i + 1] >> 1) & 7);
1017 p.cs[j + 4] = Q + eta - ((buf[i + 1] >> 4) & 7);
1018 p.cs[j + 5] = Q + eta - ((buf[i + 1] >> 7) | ((buf[i + 2] << 1) & 7));
1019 p.cs[j + 6] = Q + eta - ((buf[i + 2] >> 2) & 7);
1020 p.cs[j + 7] = Q + eta - ((buf[i + 2] >> 5) & 7);
1013 const values = [_]u8{
1014 buf[i] & 7,
1015 (buf[i] >> 3) & 7,
1016 (buf[i] >> 6) | ((buf[i + 1] << 2) & 7),
1017 (buf[i + 1] >> 1) & 7,
1018 (buf[i + 1] >> 4) & 7,
1019 (buf[i + 1] >> 7) | ((buf[i + 2] << 1) & 7),
1020 (buf[i + 2] >> 2) & 7,
1021 (buf[i + 2] >> 5) & 7,
1022 };
1023 for (p.cs[j..][0..values.len], values) |*coefficient, value| {
1024 if (value > 2 * eta) return error.InvalidEncoding;
1025 coefficient.* = Q + eta - value;
1026 }
10211027 j += 8;
10221028 }
10231029 } else { // eta == 4
10241030 // 4 bits per coefficient: unpack 2 coefficients from 1 byte
10251031 var j: usize = 0;
10261032 for (0..buf.len) |i| {
1027 p.cs[j] = Q + eta - (buf[i] & 15);
1028 p.cs[j + 1] = Q + eta - (buf[i] >> 4);
1033 const values = [_]u8{ buf[i] & 15, buf[i] >> 4 };
1034 for (p.cs[j..][0..values.len], values) |*coefficient, value| {
1035 if (value > 2 * eta) return error.InvalidEncoding;
1036 coefficient.* = Q + eta - value;
1037 }
10291038 j += 2;
10301039 }
10311040 }
......@@ -1589,15 +1598,15 @@ fn MLDSAImpl(comptime p: Params) type {
15891598 offset += p.tr_size;
15901599
15911600 sk.s1 = if (p.eta == 2)
1592 PolyVecL.unpackLeqEta(2, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()])
1601 try PolyVecL.unpackLeqEta(2, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()])
15931602 else
1594 PolyVecL.unpackLeqEta(4, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()]);
1603 try PolyVecL.unpackLeqEta(4, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()]);
15951604 offset += p.l * polyLeqEtaPackedSize();
15961605
15971606 sk.s2 = if (p.eta == 2)
1598 PolyVecK.unpackLeqEta(2, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()])
1607 try PolyVecK.unpackLeqEta(2, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()])
15991608 else
1600 PolyVecK.unpackLeqEta(4, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()]);
1609 try PolyVecK.unpackLeqEta(4, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()]);
16011610 offset += p.k * polyLeqEtaPackedSize();
16021611
16031612 sk.t0 = PolyVecK.unpackT0(bytes[offset..][0 .. p.k * polyT0PackedSize()]);
......@@ -2443,7 +2452,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=2" {
24432452 polyPackLeqEta(p, eta, &buf);
24442453
24452454 // Unpack it
2446 const p2 = polyUnpackLeqEta(eta, &buf);
2455 const p2 = try polyUnpackLeqEta(eta, &buf);
24472456
24482457 // Should be identical
24492458 for (0..N) |i| {
......@@ -2468,7 +2477,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=4" {
24682477 polyPackLeqEta(p, eta, &buf);
24692478
24702479 // Unpack it
2471 const p2 = polyUnpackLeqEta(eta, &buf);
2480 const p2 = try polyUnpackLeqEta(eta, &buf);
24722481
24732482 // Should be identical
24742483 for (0..N) |i| {
......@@ -2860,6 +2869,19 @@ test "Key generation basic - all variants" {
28602869 }
28612870}
28622871
2872test "Reject reserved eta coefficients encodings" {
2873 inline for (.{
2874 .{ .variant = MLDSA44, .reserved = 7 },
2875 .{ .variant = MLDSA65, .reserved = 15 },
2876 }) |config| {
2877 var encoded: [config.variant.SecretKey.encoded_length]u8 = @splat(0);
2878 _ = try config.variant.SecretKey.fromBytes(encoded);
2879 const s1_offset = 32 + 32 + config.variant.params.tr_size;
2880 encoded[s1_offset] = config.reserved;
2881 try testing.expectError(error.InvalidEncoding, config.variant.SecretKey.fromBytes(encoded));
2882 }
2883}
2884
28632885test "Key generation determinism" {
28642886 if (builtin.zig_backend == .stage2_llvm and builtin.target.cpu.arch.isPowerpc64()) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/229620
28652887