| 1 | const std = @import("../std.zig"); |
| 2 | const sha2 = std.crypto.hash.sha2; |
| 3 | |
| 4 | /// The composition of two hash functions: H1 o H2, with the same API as regular hash functions. |
| 5 | /// |
| 6 | /// The security level of a hash cascade doesn't exceed the security level of the weakest function. |
| 7 | /// |
| 8 | /// However, Merkle–Damgård constructions such as SHA-256 are vulnerable to length-extension attacks, |
| 9 | /// where under some conditions, `H(x||e)` can be efficiently computed without knowing `x`. |
| 10 | /// The composition of two hash functions is a common defense against such attacks. |
| 11 | /// |
| 12 | /// This is not necessary with modern hash functions, such as SHA-3, BLAKE2 and BLAKE3. |
| 13 | pub fn Composition(comptime H1: type, comptime H2: type) type { |
| 14 | return struct { |
| 15 | const Self = @This(); |
| 16 | |
| 17 | H1: H1, |
| 18 | H2: H2, |
| 19 | |
| 20 | /// The length of the hash output, in bytes. |
| 21 | pub const digest_length = H1.digest_length; |
| 22 | /// The block length, in bytes. |
| 23 | pub const block_length = H1.block_length; |
| 24 | |
| 25 | /// Options for both hashes. |
| 26 | pub const Options = struct { |
| 27 | /// Options for H1. |
| 28 | H1: H1.Options = .{}, |
| 29 | /// Options for H2. |
| 30 | H2: H2.Options = .{}, |
| 31 | }; |
| 32 | |
| 33 | /// Initialize the hash composition with the given options. |
| 34 | pub fn init(options: Options) Self { |
| 35 | return Self{ .H1 = H1.init(options.H1), .H2 = H2.init(options.H2) }; |
| 36 | } |
| 37 | |
| 38 | /// Compute H1(H2(b)). |
| 39 | pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void { |
| 40 | var d = Self.init(options); |
| 41 | d.update(b); |
| 42 | d.final(out); |
| 43 | } |
| 44 | |
| 45 | /// Add content to the hash. |
| 46 | pub fn update(d: *Self, b: []const u8) void { |
| 47 | d.H2.update(b); |
| 48 | } |
| 49 | |
| 50 | /// Compute the final hash for the accumulated content: H1(H2(b)). |
| 51 | pub fn final(d: *Self, out: *[digest_length]u8) void { |
| 52 | var H2_digest: [H2.digest_length]u8 = undefined; |
| 53 | d.H2.final(&H2_digest); |
| 54 | d.H1.update(&H2_digest); |
| 55 | d.H1.final(out); |
| 56 | } |
| 57 | }; |
| 58 | } |
| 59 | |
| 60 | /// SHA-256(SHA-256()) |
| 61 | pub const Sha256oSha256 = Composition(sha2.Sha256, sha2.Sha256); |
| 62 | /// SHA-384(SHA-384()) |
| 63 | pub const Sha384oSha384 = Composition(sha2.Sha384, sha2.Sha384); |
| 64 | /// SHA-512(SHA-512()) |
| 65 | pub const Sha512oSha512 = Composition(sha2.Sha512, sha2.Sha512); |
| 66 | |
| 67 | test "Hash composition" { |
| 68 | const Sha256 = sha2.Sha256; |
| 69 | const msg = "test"; |
| 70 | |
| 71 | var out: [Sha256oSha256.digest_length]u8 = undefined; |
| 72 | Sha256oSha256.hash(msg, &out, .{}); |
| 73 | |
| 74 | var t: [Sha256.digest_length]u8 = undefined; |
| 75 | Sha256.hash(msg, &t, .{}); |
| 76 | var out2: [Sha256.digest_length]u8 = undefined; |
| 77 | Sha256.hash(&t, &out2, .{}); |
| 78 | |
| 79 | try std.testing.expectEqualSlices(u8, &out, &out2); |
| 80 | } |