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 {...@@ -443,14 +443,14 @@ fn PolyVec(comptime len: u8) type {
443 }443 }
444444
445 /// Unpack vector with coefficients in [-eta, eta]445 /// 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 {
447 const poly_size = if (eta == 2) 96 else 128;447 const poly_size = if (eta == 2) 96 else 128;
448 const unpack_fn = struct {448 var result: Self = undefined;
449 fn unpack(b: []const u8) Poly {449 inline for (0..len) |i| {
450 return polyUnpackLeqEta(eta, b);450 const offset = i * poly_size;
451 }451 result.ps[i] = try polyUnpackLeqEta(eta, buf[offset..][0..poly_size]);
452 }.unpack;452 }
453 return unpackWith(poly_size, unpack_fn, buf);453 return result;
454 }454 }
455455
456 /// Pack vector of polynomials with coefficients < gamma1456 /// Pack vector of polynomials with coefficients < gamma1
...@@ -996,7 +996,7 @@ fn polyPackLeqEta(p: Poly, comptime eta: u8, buf: []u8) void {...@@ -996,7 +996,7 @@ fn polyPackLeqEta(p: Poly, comptime eta: u8, buf: []u8) void {
996996
997/// Unpack polynomial with coefficients in [Q-eta, Q+eta] from bytes.997/// Unpack polynomial with coefficients in [Q-eta, Q+eta] from bytes.
998/// Output coefficients will not be normalized, but in [q-η, q+η].998/// 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 {
1000 comptime {1000 comptime {
1001 if (eta != 2 and eta != 4) {1001 if (eta != 2 and eta != 4) {
1002 @compileError("eta must be 2 or 4");1002 @compileError("eta must be 2 or 4");
...@@ -1010,22 +1010,31 @@ fn polyUnpackLeqEta(comptime eta: u8, buf: []const u8) Poly {...@@ -1010,22 +1010,31 @@ fn polyUnpackLeqEta(comptime eta: u8, buf: []const u8) Poly {
1010 var j: usize = 0;1010 var j: usize = 0;
1011 var i: usize = 0;1011 var i: usize = 0;
1012 while (i < buf.len) : (i += 3) {1012 while (i < buf.len) : (i += 3) {
1013 p.cs[j] = Q + eta - (buf[i] & 7);1013 const values = [_]u8{
1014 p.cs[j + 1] = Q + eta - ((buf[i] >> 3) & 7);1014 buf[i] & 7,
1015 p.cs[j + 2] = Q + eta - ((buf[i] >> 6) | ((buf[i + 1] << 2) & 7));1015 (buf[i] >> 3) & 7,
1016 p.cs[j + 3] = Q + eta - ((buf[i + 1] >> 1) & 7);1016 (buf[i] >> 6) | ((buf[i + 1] << 2) & 7),
1017 p.cs[j + 4] = Q + eta - ((buf[i + 1] >> 4) & 7);1017 (buf[i + 1] >> 1) & 7,
1018 p.cs[j + 5] = Q + eta - ((buf[i + 1] >> 7) | ((buf[i + 2] << 1) & 7));1018 (buf[i + 1] >> 4) & 7,
1019 p.cs[j + 6] = Q + eta - ((buf[i + 2] >> 2) & 7);1019 (buf[i + 1] >> 7) | ((buf[i + 2] << 1) & 7),
1020 p.cs[j + 7] = Q + eta - ((buf[i + 2] >> 5) & 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 }
1021 j += 8;1027 j += 8;
1022 }1028 }
1023 } else { // eta == 41029 } else { // eta == 4
1024 // 4 bits per coefficient: unpack 2 coefficients from 1 byte1030 // 4 bits per coefficient: unpack 2 coefficients from 1 byte
1025 var j: usize = 0;1031 var j: usize = 0;
1026 for (0..buf.len) |i| {1032 for (0..buf.len) |i| {
1027 p.cs[j] = Q + eta - (buf[i] & 15);1033 const values = [_]u8{ buf[i] & 15, buf[i] >> 4 };
1028 p.cs[j + 1] = Q + eta - (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 }
1029 j += 2;1038 j += 2;
1030 }1039 }
1031 }1040 }
...@@ -1589,15 +1598,15 @@ fn MLDSAImpl(comptime p: Params) type {...@@ -1589,15 +1598,15 @@ fn MLDSAImpl(comptime p: Params) type {
1589 offset += p.tr_size;1598 offset += p.tr_size;
15901599
1591 sk.s1 = if (p.eta == 2)1600 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()])
1593 else1602 else
1594 PolyVecL.unpackLeqEta(4, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()]);1603 try PolyVecL.unpackLeqEta(4, bytes[offset..][0 .. p.l * polyLeqEtaPackedSize()]);
1595 offset += p.l * polyLeqEtaPackedSize();1604 offset += p.l * polyLeqEtaPackedSize();
15961605
1597 sk.s2 = if (p.eta == 2)1606 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()])
1599 else1608 else
1600 PolyVecK.unpackLeqEta(4, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()]);1609 try PolyVecK.unpackLeqEta(4, bytes[offset..][0 .. p.k * polyLeqEtaPackedSize()]);
1601 offset += p.k * polyLeqEtaPackedSize();1610 offset += p.k * polyLeqEtaPackedSize();
16021611
1603 sk.t0 = PolyVecK.unpackT0(bytes[offset..][0 .. p.k * polyT0PackedSize()]);1612 sk.t0 = PolyVecK.unpackT0(bytes[offset..][0 .. p.k * polyT0PackedSize()]);
...@@ -2443,7 +2452,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=2" {...@@ -2443,7 +2452,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=2" {
2443 polyPackLeqEta(p, eta, &buf);2452 polyPackLeqEta(p, eta, &buf);
24442453
2445 // Unpack it2454 // Unpack it
2446 const p2 = polyUnpackLeqEta(eta, &buf);2455 const p2 = try polyUnpackLeqEta(eta, &buf);
24472456
2448 // Should be identical2457 // Should be identical
2449 for (0..N) |i| {2458 for (0..N) |i| {
...@@ -2468,7 +2477,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=4" {...@@ -2468,7 +2477,7 @@ test "polyPackLeqEta / polyUnpackLeqEta roundtrip for eta=4" {
2468 polyPackLeqEta(p, eta, &buf);2477 polyPackLeqEta(p, eta, &buf);
24692478
2470 // Unpack it2479 // Unpack it
2471 const p2 = polyUnpackLeqEta(eta, &buf);2480 const p2 = try polyUnpackLeqEta(eta, &buf);
24722481
2473 // Should be identical2482 // Should be identical
2474 for (0..N) |i| {2483 for (0..N) |i| {
...@@ -2860,6 +2869,19 @@ test "Key generation basic - all variants" {...@@ -2860,6 +2869,19 @@ test "Key generation basic - all variants" {
2860 }2869 }
2861}2870}
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
2863test "Key generation determinism" {2885test "Key generation determinism" {
2864 if (builtin.zig_backend == .stage2_llvm and builtin.target.cpu.arch.isPowerpc64()) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/2296202886 if (builtin.zig_backend == .stage2_llvm and builtin.target.cpu.arch.isPowerpc64()) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/229620
28652887