| author | |
| committer | |
| log | 48fd92365a93a06a57ae0270be5f2614804e5749 |
| tree | 0f9c1fb8ecf0fe46d0729130e0bbb8a78e3bcf1d |
| parent | 902dc8c721c762bc5d1b9786bad47b21da45042c |
| signature |
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 { |
| 76 | 76 | pub const Sha1 = @import("crypto/sha1.zig").Sha1; |
| 77 | 77 | pub const sha2 = @import("crypto/sha2.zig"); |
| 78 | 78 | pub const sha3 = @import("crypto/sha3.zig"); |
| 79 | pub const composition = @import("crypto/hash_composition.zig"); | |
| 79 | 80 | }; |
| 80 | 81 | |
| 81 | 82 | /// Key derivation functions. |
| ... | ... | @@ -215,6 +216,7 @@ test { |
| 215 | 216 | _ = hash.Sha1; |
| 216 | 217 | _ = hash.sha2; |
| 217 | 218 | _ = hash.sha3; |
| 219 | _ = hash.composition; | |
| 218 | 220 | |
| 219 | 221 | _ = kdf.hkdf; |
| 220 | 222 |
lib/std/crypto/ecdsa.zig+19-1| ... | ... | @@ -18,6 +18,10 @@ pub const EcdsaP256Sha3_256 = Ecdsa(crypto.ecc.P256, crypto.hash.sha3.Sha3_256); |
| 18 | 18 | pub const EcdsaP384Sha384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha384); |
| 19 | 19 | /// ECDSA over P-384 with SHA3-384. |
| 20 | 20 | pub const EcdsaP256Sha3_384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha3.Sha3_384); |
| 21 | /// ECDSA over Secp256k1 with SHA-256. | |
| 22 | pub const EcdsaSecp256k1Sha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.sha2.Sha256); | |
| 23 | /// ECDSA over Secp256k1 with SHA-256(SHA-256()) -- The Bitcoin signature system. | |
| 24 | pub const EcdsaSecp256k1Sha256oSha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.composition.Sha256oSha256); | |
| 21 | 25 | |
| 22 | 26 | /// Elliptic Curve Digital Signature Algorithm (ECDSA). |
| 23 | 27 | pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { |
| ... | ... | @@ -293,7 +297,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { |
| 293 | 297 | }; |
| 294 | 298 | } |
| 295 | 299 | |
| 296 | test "ECDSA - Basic operations" { | |
| 300 | test "ECDSA - Basic operations over EcdsaP384Sha384" { | |
| 297 | 301 | const Scheme = EcdsaP384Sha384; |
| 298 | 302 | const kp = try Scheme.KeyPair.create(null); |
| 299 | 303 | const msg = "test"; |
| ... | ... | @@ -307,6 +311,20 @@ test "ECDSA - Basic operations" { |
| 307 | 311 | try sig2.verify(msg, kp.public_key); |
| 308 | 312 | } |
| 309 | 313 | |
| 314 | test "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 | ||
| 310 | 328 | const TestVector = struct { |
| 311 | 329 | key: []const u8, |
| 312 | 330 | msg: []const u8, |
lib/std/crypto/hash_composition.zig created+80| ... | ... | @@ -0,0 +1,80 @@ |
| 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 | } |