authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2022-07-01 11:37:41+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-07-01 11:37:41+02:00
log48fd92365a93a06a57ae0270be5f2614804e5749
tree0f9c1fb8ecf0fe46d0729130e0bbb8a78e3bcf1d
parent902dc8c721c762bc5d1b9786bad47b21da45042c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std.crypto.hash: allow creating hash functions from compositions (#11965)

A hash function cascade was a common way to avoid length-extension attacks with traditional hash functions such as the SHA-2 family. Add `std.crypto.hash.composition` to do exactly that using arbitrary hash functions, and pre-define the common SHA2-based ones. With this, we can now sign and verify Bitcoin signatures in pure Zig.

3 files changed, 101 insertions(+), 1 deletions(-)

lib/std/crypto.zig+2
......@@ -76,6 +76,7 @@ pub const hash = struct {
7676 pub const Sha1 = @import("crypto/sha1.zig").Sha1;
7777 pub const sha2 = @import("crypto/sha2.zig");
7878 pub const sha3 = @import("crypto/sha3.zig");
79 pub const composition = @import("crypto/hash_composition.zig");
7980};
8081
8182/// Key derivation functions.
......@@ -215,6 +216,7 @@ test {
215216 _ = hash.Sha1;
216217 _ = hash.sha2;
217218 _ = hash.sha3;
219 _ = hash.composition;
218220
219221 _ = kdf.hkdf;
220222
lib/std/crypto/ecdsa.zig+19-1
......@@ -18,6 +18,10 @@ pub const EcdsaP256Sha3_256 = Ecdsa(crypto.ecc.P256, crypto.hash.sha3.Sha3_256);
1818pub const EcdsaP384Sha384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha384);
1919/// ECDSA over P-384 with SHA3-384.
2020pub const EcdsaP256Sha3_384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha3.Sha3_384);
21/// ECDSA over Secp256k1 with SHA-256.
22pub const EcdsaSecp256k1Sha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.sha2.Sha256);
23/// ECDSA over Secp256k1 with SHA-256(SHA-256()) -- The Bitcoin signature system.
24pub const EcdsaSecp256k1Sha256oSha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.composition.Sha256oSha256);
2125
2226/// Elliptic Curve Digital Signature Algorithm (ECDSA).
2327pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
......@@ -293,7 +297,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
293297 };
294298}
295299
296test "ECDSA - Basic operations" {
300test "ECDSA - Basic operations over EcdsaP384Sha384" {
297301 const Scheme = EcdsaP384Sha384;
298302 const kp = try Scheme.KeyPair.create(null);
299303 const msg = "test";
......@@ -307,6 +311,20 @@ test "ECDSA - Basic operations" {
307311 try sig2.verify(msg, kp.public_key);
308312}
309313
314test "ECDSA - Basic operations over Secp256k1" {
315 const Scheme = EcdsaSecp256k1Sha256oSha256;
316 const kp = try Scheme.KeyPair.create(null);
317 const msg = "test";
318
319 var noise: [Scheme.noise_length]u8 = undefined;
320 crypto.random.bytes(&noise);
321 const sig = try kp.sign(msg, noise);
322 try sig.verify(msg, kp.public_key);
323
324 const sig2 = try kp.sign(msg, null);
325 try sig2.verify(msg, kp.public_key);
326}
327
310328const TestVector = struct {
311329 key: []const u8,
312330 msg: []const u8,
lib/std/crypto/hash_composition.zig created+80
......@@ -0,0 +1,80 @@
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}