diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 522a545fcf..d9b369a0b9 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -76,6 +76,7 @@ pub const hash = struct { pub const Sha1 = @import("crypto/sha1.zig").Sha1; pub const sha2 = @import("crypto/sha2.zig"); pub const sha3 = @import("crypto/sha3.zig"); + pub const composition = @import("crypto/hash_composition.zig"); }; /// Key derivation functions. @@ -215,6 +216,7 @@ test { _ = hash.Sha1; _ = hash.sha2; _ = hash.sha3; + _ = hash.composition; _ = kdf.hkdf; diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig index 72c75ca163..4623574b37 100644 --- a/lib/std/crypto/ecdsa.zig +++ b/lib/std/crypto/ecdsa.zig @@ -18,6 +18,10 @@ pub const EcdsaP256Sha3_256 = Ecdsa(crypto.ecc.P256, crypto.hash.sha3.Sha3_256); pub const EcdsaP384Sha384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha384); /// ECDSA over P-384 with SHA3-384. pub const EcdsaP256Sha3_384 = Ecdsa(crypto.ecc.P384, crypto.hash.sha3.Sha3_384); +/// ECDSA over Secp256k1 with SHA-256. +pub const EcdsaSecp256k1Sha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.sha2.Sha256); +/// ECDSA over Secp256k1 with SHA-256(SHA-256()) -- The Bitcoin signature system. +pub const EcdsaSecp256k1Sha256oSha256 = Ecdsa(crypto.ecc.Secp256k1, crypto.hash.composition.Sha256oSha256); /// Elliptic Curve Digital Signature Algorithm (ECDSA). pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { @@ -293,7 +297,7 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { }; } -test "ECDSA - Basic operations" { +test "ECDSA - Basic operations over EcdsaP384Sha384" { const Scheme = EcdsaP384Sha384; const kp = try Scheme.KeyPair.create(null); const msg = "test"; @@ -307,6 +311,20 @@ test "ECDSA - Basic operations" { try sig2.verify(msg, kp.public_key); } +test "ECDSA - Basic operations over Secp256k1" { + const Scheme = EcdsaSecp256k1Sha256oSha256; + const kp = try Scheme.KeyPair.create(null); + const msg = "test"; + + var noise: [Scheme.noise_length]u8 = undefined; + crypto.random.bytes(&noise); + const sig = try kp.sign(msg, noise); + try sig.verify(msg, kp.public_key); + + const sig2 = try kp.sign(msg, null); + try sig2.verify(msg, kp.public_key); +} + const TestVector = struct { key: []const u8, msg: []const u8, diff --git a/lib/std/crypto/hash_composition.zig b/lib/std/crypto/hash_composition.zig new file mode 100644 index 0000000000..1ffa3d4c47 --- /dev/null +++ b/lib/std/crypto/hash_composition.zig @@ -0,0 +1,80 @@ +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); +}