| ... | @@ -681,6 +681,8 @@ pub const AsconXof128 = struct { | ... | @@ -681,6 +681,8 @@ pub const AsconXof128 = struct { |
| 681 | | 681 | |
| 682 | st: AsconState, | 682 | st: AsconState, |
| 683 | squeezed: bool, | 683 | squeezed: bool, |
| | 684 | buf: [block_length]u8, |
| | 685 | buf_len: usize, |
| 684 | | 686 | |
| 685 | pub const Options = struct {}; | 687 | pub const Options = struct {}; |
| 686 | | 688 | |
| ... | @@ -698,7 +700,7 @@ pub const AsconXof128 = struct { | ... | @@ -698,7 +700,7 @@ pub const AsconXof128 = struct { |
| 698 | const words: [5]u64 = .{ iv, 0, 0, 0, 0 }; | 700 | const words: [5]u64 = .{ iv, 0, 0, 0, 0 }; |
| 699 | var st = AsconState.initFromWords(words); | 701 | var st = AsconState.initFromWords(words); |
| 700 | st.permuteR(12); | 702 | st.permuteR(12); |
| 701 | return AsconXof128{ .st = st, .squeezed = false }; | 703 | return AsconXof128{ .st = st, .squeezed = false, .buf = @splat(0), .buf_len = 0 }; |
| 702 | } | 704 | } |
| 703 | | 705 | |
| 704 | /// Hash a slice of bytes with variable-length output. | 706 | /// Hash a slice of bytes with variable-length output. |
| ... | @@ -726,24 +728,26 @@ pub const AsconXof128 = struct { | ... | @@ -726,24 +728,26 @@ pub const AsconXof128 = struct { |
| 726 | | 728 | |
| 727 | var i: usize = 0; | 729 | var i: usize = 0; |
| 728 | | 730 | |
| 729 | // Process full 64-bit blocks | 731 | if (self.buf_len > 0) { |
| 730 | while (i + 8 <= b.len) : (i += 8) { | 732 | const to_fill = @min(block_length - self.buf_len, b.len); |
| 731 | self.st.addBytes(b[i..][0..8]); | 733 | @memcpy(self.buf[self.buf_len..][0..to_fill], b[0..to_fill]); |
| | 734 | self.buf_len += to_fill; |
| | 735 | i += to_fill; |
| | 736 | if (self.buf_len == block_length) { |
| | 737 | self.st.addBytes(&self.buf); |
| | 738 | self.st.permuteR(12); |
| | 739 | self.buf_len = 0; |
| | 740 | } |
| | 741 | } |
| | 742 | |
| | 743 | while (i + block_length <= b.len) : (i += block_length) { |
| | 744 | self.st.addBytes(b[i..][0..block_length]); |
| 732 | self.st.permuteR(12); | 745 | self.st.permuteR(12); |
| 733 | } | 746 | } |
| 734 | | 747 | |
| 735 | // Store partial block for finalization | | |
| 736 | if (i < b.len) { | 748 | if (i < b.len) { |
| 737 | var padded: [8]u8 = @splat(0); | 749 | self.buf_len = b.len - i; |
| 738 | const remaining = b.len - i; | 750 | @memcpy(self.buf[0..self.buf_len], b[i..]); |
| 739 | @memcpy(padded[0..remaining], b[i..]); | | |
| 740 | padded[remaining] = 0x01; | | |
| 741 | self.st.addBytes(&padded); | | |
| 742 | } else { | | |
| 743 | // Add padding block | | |
| 744 | var padded: [8]u8 = @splat(0); | | |
| 745 | padded[0] = 0x01; | | |
| 746 | self.st.addBytes(&padded); | | |
| 747 | } | 751 | } |
| 748 | } | 752 | } |
| 749 | | 753 | |
| ... | @@ -756,7 +760,10 @@ pub const AsconXof128 = struct { | ... | @@ -756,7 +760,10 @@ pub const AsconXof128 = struct { |
| 756 | /// After first call, no more data can be absorbed with update(). | 760 | /// After first call, no more data can be absorbed with update(). |
| 757 | pub fn squeeze(self: *AsconXof128, out: []u8) void { | 761 | pub fn squeeze(self: *AsconXof128, out: []u8) void { |
| 758 | if (!self.squeezed) { | 762 | if (!self.squeezed) { |
| 759 | // First squeeze - apply final permutation | 763 | var padded: [block_length]u8 = @splat(0); |
| | 764 | @memcpy(padded[0..self.buf_len], self.buf[0..self.buf_len]); |
| | 765 | padded[self.buf_len] = 0x01; |
| | 766 | self.st.addBytes(&padded); |
| 760 | self.st.permuteR(12); | 767 | self.st.permuteR(12); |
| 761 | self.squeezed = true; | 768 | self.squeezed = true; |
| 762 | } | 769 | } |
| ... | @@ -783,6 +790,8 @@ pub const AsconCxof128 = struct { | ... | @@ -783,6 +790,8 @@ pub const AsconCxof128 = struct { |
| 783 | | 790 | |
| 784 | st: AsconState, | 791 | st: AsconState, |
| 785 | squeezed: bool, | 792 | squeezed: bool, |
| | 793 | buf: [block_length]u8, |
| | 794 | buf_len: usize, |
| 786 | | 795 | |
| 787 | pub const Options = struct { custom: []const u8 = "" }; | 796 | pub const Options = struct { custom: []const u8 = "" }; |
| 788 | | 797 | |
| ... | @@ -804,7 +813,7 @@ pub const AsconCxof128 = struct { | ... | @@ -804,7 +813,7 @@ pub const AsconCxof128 = struct { |
| 804 | var st = AsconState.initFromWords(words); | 813 | var st = AsconState.initFromWords(words); |
| 805 | st.permuteR(12); | 814 | st.permuteR(12); |
| 806 | | 815 | |
| 807 | var self = AsconCxof128{ .st = st, .squeezed = false }; | 816 | var self = AsconCxof128{ .st = st, .squeezed = false, .buf = @splat(0), .buf_len = 0 }; |
| 808 | | 817 | |
| 809 | // Process customization string - always process length and padding | 818 | // Process customization string - always process length and padding |
| 810 | // First block: length of customization string | 819 | // First block: length of customization string |
| ... | @@ -867,28 +876,30 @@ pub const AsconCxof128 = struct { | ... | @@ -867,28 +876,30 @@ pub const AsconCxof128 = struct { |
| 867 | /// | 876 | /// |
| 868 | /// Note: Cannot be called after squeeze() has been called | 877 | /// Note: Cannot be called after squeeze() has been called |
| 869 | pub fn update(self: *AsconCxof128, b: []const u8) void { | 878 | pub fn update(self: *AsconCxof128, b: []const u8) void { |
| 870 | debug.assert(!self.squeezed); | 879 | debug.assert(!self.squeezed); // Cannot update after squeezing |
| 871 | | 880 | |
| 872 | var i: usize = 0; | 881 | var i: usize = 0; |
| 873 | | 882 | |
| 874 | // Process full 64-bit blocks | 883 | if (self.buf_len > 0) { |
| 875 | while (i + 8 <= b.len) : (i += 8) { | 884 | const to_fill = @min(block_length - self.buf_len, b.len); |
| 876 | self.st.addBytes(b[i..][0..8]); | 885 | @memcpy(self.buf[self.buf_len..][0..to_fill], b[0..to_fill]); |
| | 886 | self.buf_len += to_fill; |
| | 887 | i += to_fill; |
| | 888 | if (self.buf_len == block_length) { |
| | 889 | self.st.addBytes(&self.buf); |
| | 890 | self.st.permuteR(12); |
| | 891 | self.buf_len = 0; |
| | 892 | } |
| | 893 | } |
| | 894 | |
| | 895 | while (i + block_length <= b.len) : (i += block_length) { |
| | 896 | self.st.addBytes(b[i..][0..block_length]); |
| 877 | self.st.permuteR(12); | 897 | self.st.permuteR(12); |
| 878 | } | 898 | } |
| 879 | | 899 | |
| 880 | // Store partial block for finalization | | |
| 881 | if (i < b.len) { | 900 | if (i < b.len) { |
| 882 | var padded: [8]u8 = @splat(0); | 901 | self.buf_len = b.len - i; |
| 883 | const remaining = b.len - i; | 902 | @memcpy(self.buf[0..self.buf_len], b[i..]); |
| 884 | @memcpy(padded[0..remaining], b[i..]); | | |
| 885 | padded[remaining] = 0x01; | | |
| 886 | self.st.addBytes(&padded); | | |
| 887 | } else { | | |
| 888 | // Add padding block | | |
| 889 | var padded: [8]u8 = @splat(0); | | |
| 890 | padded[0] = 0x01; | | |
| 891 | self.st.addBytes(&padded); | | |
| 892 | } | 903 | } |
| 893 | } | 904 | } |
| 894 | | 905 | |
| ... | @@ -901,7 +912,10 @@ pub const AsconCxof128 = struct { | ... | @@ -901,7 +912,10 @@ pub const AsconCxof128 = struct { |
| 901 | /// After first call, no more data can be absorbed with update(). | 912 | /// After first call, no more data can be absorbed with update(). |
| 902 | pub fn squeeze(self: *AsconCxof128, out: []u8) void { | 913 | pub fn squeeze(self: *AsconCxof128, out: []u8) void { |
| 903 | if (!self.squeezed) { | 914 | if (!self.squeezed) { |
| 904 | // First squeeze - apply final permutation | 915 | var padded: [block_length]u8 = @splat(0); |
| | 916 | @memcpy(padded[0..self.buf_len], self.buf[0..self.buf_len]); |
| | 917 | padded[self.buf_len] = 0x01; |
| | 918 | self.st.addBytes(&padded); |
| 905 | self.st.permuteR(12); | 919 | self.st.permuteR(12); |
| 906 | self.squeezed = true; | 920 | self.squeezed = true; |
| 907 | } | 921 | } |
| ... | @@ -1267,6 +1281,34 @@ test "Ascon-XOF128 official test vectors" { | ... | @@ -1267,6 +1281,34 @@ test "Ascon-XOF128 official test vectors" { |
| 1267 | } | 1281 | } |
| 1268 | } | 1282 | } |
| 1269 | | 1283 | |
| | 1284 | test "Ascon-XOF128/CXOF128 streaming chunking invariance" { |
| | 1285 | const msg = "Hello, World!"; |
| | 1286 | |
| | 1287 | // XOF128: one-shot vs split must match |
| | 1288 | var out1: [32]u8 = undefined; |
| | 1289 | var out2: [32]u8 = undefined; |
| | 1290 | var xof1 = AsconXof128.init(.{}); |
| | 1291 | xof1.update(msg); |
| | 1292 | xof1.squeeze(&out1); |
| | 1293 | var xof2 = AsconXof128.init(.{}); |
| | 1294 | xof2.update("Hello, "); |
| | 1295 | xof2.update("World!"); |
| | 1296 | xof2.squeeze(&out2); |
| | 1297 | try testing.expectEqualSlices(u8, &out1, &out2); |
| | 1298 | |
| | 1299 | // CXOF128: one-shot vs split must match |
| | 1300 | var cout1: [32]u8 = undefined; |
| | 1301 | var cout2: [32]u8 = undefined; |
| | 1302 | var cxof1 = AsconCxof128.init(.{ .custom = "cust" }); |
| | 1303 | cxof1.update(msg); |
| | 1304 | cxof1.squeeze(&cout1); |
| | 1305 | var cxof2 = AsconCxof128.init(.{ .custom = "cust" }); |
| | 1306 | cxof2.update("Hello, "); |
| | 1307 | cxof2.update("World!"); |
| | 1308 | cxof2.squeeze(&cout2); |
| | 1309 | try testing.expectEqualSlices(u8, &cout1, &cout2); |
| | 1310 | } |
| | 1311 | |
| 1270 | test "Ascon-CXOF128 official test vectors" { | 1312 | test "Ascon-CXOF128 official test vectors" { |
| 1271 | | 1313 | |
| 1272 | // Test vector 1: Empty message, empty customization, 64-byte output | 1314 | // Test vector 1: Empty message, empty customization, 64-byte output |