| ... | ... | @@ -681,6 +681,8 @@ pub const AsconXof128 = struct { |
| 681 | 681 | |
| 682 | 682 | st: AsconState, |
| 683 | 683 | squeezed: bool, |
| 684 | buf: [block_length]u8, |
| 685 | buf_len: usize, |
| 684 | 686 | |
| 685 | 687 | pub const Options = struct {}; |
| 686 | 688 | |
| ... | ... | @@ -698,7 +700,7 @@ pub const AsconXof128 = struct { |
| 698 | 700 | const words: [5]u64 = .{ iv, 0, 0, 0, 0 }; |
| 699 | 701 | var st = AsconState.initFromWords(words); |
| 700 | 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 | 706 | /// Hash a slice of bytes with variable-length output. |
| ... | ... | @@ -726,24 +728,26 @@ pub const AsconXof128 = struct { |
| 726 | 728 | |
| 727 | 729 | var i: usize = 0; |
| 728 | 730 | |
| 729 | | // Process full 64-bit blocks |
| 730 | | while (i + 8 <= b.len) : (i += 8) { |
| 731 | | self.st.addBytes(b[i..][0..8]); |
| 731 | if (self.buf_len > 0) { |
| 732 | const to_fill = @min(block_length - self.buf_len, b.len); |
| 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 | 745 | self.st.permuteR(12); |
| 733 | 746 | } |
| 734 | 747 | |
| 735 | | // Store partial block for finalization |
| 736 | 748 | if (i < b.len) { |
| 737 | | var padded: [8]u8 = @splat(0); |
| 738 | | const remaining = b.len - 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); |
| 749 | self.buf_len = b.len - i; |
| 750 | @memcpy(self.buf[0..self.buf_len], b[i..]); |
| 747 | 751 | } |
| 748 | 752 | } |
| 749 | 753 | |
| ... | ... | @@ -756,7 +760,10 @@ pub const AsconXof128 = struct { |
| 756 | 760 | /// After first call, no more data can be absorbed with update(). |
| 757 | 761 | pub fn squeeze(self: *AsconXof128, out: []u8) void { |
| 758 | 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 | 767 | self.st.permuteR(12); |
| 761 | 768 | self.squeezed = true; |
| 762 | 769 | } |
| ... | ... | @@ -783,6 +790,8 @@ pub const AsconCxof128 = struct { |
| 783 | 790 | |
| 784 | 791 | st: AsconState, |
| 785 | 792 | squeezed: bool, |
| 793 | buf: [block_length]u8, |
| 794 | buf_len: usize, |
| 786 | 795 | |
| 787 | 796 | pub const Options = struct { custom: []const u8 = "" }; |
| 788 | 797 | |
| ... | ... | @@ -804,7 +813,7 @@ pub const AsconCxof128 = struct { |
| 804 | 813 | var st = AsconState.initFromWords(words); |
| 805 | 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 | 818 | // Process customization string - always process length and padding |
| 810 | 819 | // First block: length of customization string |
| ... | ... | @@ -867,28 +876,30 @@ pub const AsconCxof128 = struct { |
| 867 | 876 | /// |
| 868 | 877 | /// Note: Cannot be called after squeeze() has been called |
| 869 | 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 | 881 | var i: usize = 0; |
| 873 | 882 | |
| 874 | | // Process full 64-bit blocks |
| 875 | | while (i + 8 <= b.len) : (i += 8) { |
| 876 | | self.st.addBytes(b[i..][0..8]); |
| 883 | if (self.buf_len > 0) { |
| 884 | const to_fill = @min(block_length - self.buf_len, b.len); |
| 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 | 897 | self.st.permuteR(12); |
| 878 | 898 | } |
| 879 | 899 | |
| 880 | | // Store partial block for finalization |
| 881 | 900 | if (i < b.len) { |
| 882 | | var padded: [8]u8 = @splat(0); |
| 883 | | const remaining = b.len - 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); |
| 901 | self.buf_len = b.len - i; |
| 902 | @memcpy(self.buf[0..self.buf_len], b[i..]); |
| 892 | 903 | } |
| 893 | 904 | } |
| 894 | 905 | |
| ... | ... | @@ -901,7 +912,10 @@ pub const AsconCxof128 = struct { |
| 901 | 912 | /// After first call, no more data can be absorbed with update(). |
| 902 | 913 | pub fn squeeze(self: *AsconCxof128, out: []u8) void { |
| 903 | 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 | 919 | self.st.permuteR(12); |
| 906 | 920 | self.squeezed = true; |
| 907 | 921 | } |
| ... | ... | @@ -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 | 1312 | test "Ascon-CXOF128 official test vectors" { |
| 1271 | 1313 | |
| 1272 | 1314 | // Test vector 1: Empty message, empty customization, 64-byte output |