const std = @import("../std.zig"); const sha2 = std.crypto.hash.sha2; /// The composition of two hash functions: H1 o H2, with the same API as regular hash functions. /// /// The security level of a hash cascade doesn't exceed the security level of the weakest function. /// /// However, Merkle–Damgård constructions such as SHA-256 are vulnerable to length-extension attacks, /// where under some conditions, `H(x||e)` can be efficiently computed without knowing `x`. /// The composition of two hash functions is a common defense against such attacks. /// /// This is not necessary with modern hash functions, such as SHA-3, BLAKE2 and BLAKE3. pub fn Composition(comptime H1: type, comptime H2: type) type { return struct { const Self = @This(); H1: H1, H2: H2, /// The length of the hash output, in bytes. pub const digest_length = H1.digest_length; /// The block length, in bytes. pub const block_length = H1.block_length; /// Options for both hashes. pub const Options = struct { /// Options for H1. H1: H1.Options = .{}, /// Options for H2. H2: H2.Options = .{}, }; /// Initialize the hash composition with the given options. pub fn init(options: Options) Self { return Self{ .H1 = H1.init(options.H1), .H2 = H2.init(options.H2) }; } /// Compute H1(H2(b)). pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void { var d = Self.init(options); d.update(b); d.final(out); } /// Add content to the hash. pub fn update(d: *Self, b: []const u8) void { d.H2.update(b); } /// Compute the final hash for the accumulated content: H1(H2(b)). pub fn final(d: *Self, out: *[digest_length]u8) void { var H2_digest: [H2.digest_length]u8 = undefined; d.H2.final(&H2_digest); d.H1.update(&H2_digest); d.H1.final(out); } }; } /// SHA-256(SHA-256()) pub const Sha256oSha256 = Composition(sha2.Sha256, sha2.Sha256); /// SHA-384(SHA-384()) pub const Sha384oSha384 = Composition(sha2.Sha384, sha2.Sha384); /// SHA-512(SHA-512()) pub const Sha512oSha512 = Composition(sha2.Sha512, sha2.Sha512); test "Hash composition" { const Sha256 = sha2.Sha256; const msg = "test"; var out: [Sha256oSha256.digest_length]u8 = undefined; Sha256oSha256.hash(msg, &out, .{}); var t: [Sha256.digest_length]u8 = undefined; Sha256.hash(msg, &t, .{}); var out2: [Sha256.digest_length]u8 = undefined; Sha256.hash(&t, &out2, .{}); try std.testing.expectEqualSlices(u8, &out, &out2); }