master
 1const std = @import("../std.zig");
 2const 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.
13pub 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())
61pub const Sha256oSha256 = Composition(sha2.Sha256, sha2.Sha256);
62/// SHA-384(SHA-384())
63pub const Sha384oSha384 = Composition(sha2.Sha384, sha2.Sha384);
64/// SHA-512(SHA-512())
65pub const Sha512oSha512 = Composition(sha2.Sha512, sha2.Sha512);
66
67test "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}