Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

HybridKem

Constructs a hybrid KEM type from the given parameters.

A hybrid KEM combines a post-quantum KEM with a traditional elliptic curve Diffie-Hellman key exchange. The shared secrets from both components are combined using the C2PRI combiner construction with SHA3-256.

The resulting type provides:

hybrid_kem.HybridKem
pub fn HybridKem(comptime params: Params) type

File

lib/std/crypto/hybrid_kem.zig:109

Code

pub fn HybridKem(comptime params: Params) type {
    return struct {
        const is_nist_curve = params.Group == P256Group or params.Group == P384Group;

        fn expandRandomnessSeed(seed: [32]u8) ![params.Group.seed_length]u8 {
            if (!is_nist_curve) return seed;
            var xof = params.Xof.init(.{});
            xof.update(&seed);
            var expanded: [params.Group.seed_length]u8 = undefined;
            xof.squeeze(&expanded);
            return expanded;
        }

        fn expandDecapsKeyG(seed: [params.Nseed]u8) !struct {
            ek_pq: params.PqKem.PublicKey,
            ek_t: [params.Group.element_length]u8,
            dk_pq: params.PqKem.SecretKey,
            dk_t: [params.Group.scalar_length]u8,
        } {
            var xof = params.Xof.init(.{});
            xof.update(&seed);
            var seeds: [params.pq_nseed + params.Group.seed_length]u8 = undefined;
            xof.squeeze(&seeds);

            const kp_pq = try params.PqKem.KeyPair.generateDeterministic(seeds[0..params.pq_nseed].*);
            const dk_t = try params.Group.randomScalar(seeds[params.pq_nseed..]);
            const ek_t_point = try params.Group.mulBase(dk_t);

            return .{
                .ek_pq = kp_pq.public_key,
                .ek_t = if (is_nist_curve) params.Group.encodePoint(ek_t_point) else ek_t_point,
                .dk_pq = kp_pq.secret_key,
                .dk_t = dk_t,
            };
        }

        fn c2priCombiner(ss_pq: [32]u8, ss_t: [params.Group.scalar_length]u8, ct_t: []const u8, ek_t: []const u8) [params.Nss]u8 {
            var hasher = sha3.Sha3_256.init(.{});
            hasher.update(&ss_pq);
            hasher.update(&ss_t);
            hasher.update(ct_t);
            hasher.update(ek_t);
            hasher.update(params.label);
            var output: [params.Nss]u8 = undefined;
            hasher.final(&output);
            return output;
        }

        /// A hybrid KEM public key (encapsulation key).
        ///
        /// The public key is the concatenation of the post-quantum KEM public key
        /// and the traditional elliptic curve public key.
        pub const PublicKey = struct {
            bytes: [params.Nek]u8,

            /// Size of a serialized representation of the key, in bytes.
            pub const encoded_length = params.Nek;

            /// Serializes the key into a byte array.
            pub fn toBytes(self: PublicKey) [encoded_length]u8 {
                return self.bytes;
            }

            /// Deserializes the key from a byte array.
            pub fn fromBytes(buf: *const [encoded_length]u8) PublicKey {
                return .{ .bytes = buf.* };
            }

            /// Generates a shared secret, encapsulated for the public key,
            /// using random bytes.
            ///
            /// This is recommended over `encapsDeterministic`.
            pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret {
                var seed_pq: [32]u8 = undefined;
                io.random(&seed_pq);
                var seed_t: [32]u8 = undefined;
                io.random(&seed_t);
                var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t);
                return encapsInner(pk, &seed_pq, &seed_t_expanded);
            }

            /// Generates a shared secret, encapsulated for the public key,
            /// using the provided seed.
            ///
            /// Calling `encaps` instead is recommended.
            pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret {
                if (seed.len < 32) return error.InsufficientRandomness;
                var seed_pq: [32]u8 = seed[0..32].*;
                var seed_t_expanded: [params.Group.seed_length]u8 = undefined;

                const t_randomness = seed[32..];
                if (t_randomness.len < params.Group.seed_length) {
                    // Provided randomness is shorter than seed_length, use it directly
                    // (test vectors provide just enough for randomScalar)
                    @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
                    // Pad the rest with zeros if needed (shouldn't be used by randomScalar)
                    if (t_randomness.len < params.Group.seed_length) {
                        @memset(seed_t_expanded[t_randomness.len..], 0);
                    }
                } else {
                    // Full randomness provided
                    @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
                }

                return encapsInner(pk, &seed_pq, &seed_t_expanded);
            }

            fn encapsInner(
                pk: PublicKey,
                seed_pq: *[32]u8,
                seed_t_expanded: *[params.Group.seed_length]u8,
            ) !EncapsulatedSecret {
                const pq_nek = params.PqKem.PublicKey.encoded_length;
                const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]);
                const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length];

                const pq_encap = ek_pq.encapsDeterministic(seed_pq);
                const sk_e = try params.Group.randomScalar(seed_t_expanded);
                const ct_t_point = try params.Group.mulBase(sk_e);
                const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point;

                const ek_t_point = if (is_nist_curve) try params.Group.decodePoint(ek_t) else ek_t.*;
                const ss_t = params.Group.elementToSharedSecret(try params.Group.mul(ek_t_point, sk_e));

                var ct_h: [params.Nct]u8 = undefined;
                @memcpy(ct_h[0..pq_encap.ciphertext.len], &pq_encap.ciphertext);
                @memcpy(ct_h[pq_encap.ciphertext.len..], &ct_t);

                return .{
                    .shared_secret = c2priCombiner(pq_encap.shared_secret, ss_t, &ct_t, ek_t),
                    .ciphertext = ct_h,
                };
            }
        };

        /// A hybrid KEM secret key (decapsulation key).
        ///
        /// The secret key is stored as a seed from which the actual key material
        /// is derived on demand. This is more compact than storing expanded keys.
        pub const SecretKey = struct {
            seed: [params.Nseed]u8,

            /// Size of a serialized representation of the key, in bytes.
            pub const encoded_length = params.Ndk;

            /// Serializes the key into a byte array.
            pub fn toBytes(self: SecretKey) [encoded_length]u8 {
                return self.seed;
            }

            /// Deserializes the key from a byte array.
            pub fn fromBytes(buf: *const [encoded_length]u8) SecretKey {
                return .{ .seed = buf.* };
            }

            /// Decapsulates the shared secret from the ciphertext using the secret key.
            pub fn decaps(self: SecretKey, ct: *const [params.Nct]u8) ![params.Nss]u8 {
                const expanded = try expandDecapsKeyG(self.seed);
                const pq_ct_len = params.PqKem.ciphertext_length;
                const ct_t = ct[pq_ct_len..][0..params.Group.element_length];

                const ss_pq = try expanded.dk_pq.decaps(ct[0..pq_ct_len]);
                const ct_t_point = if (is_nist_curve) try params.Group.decodePoint(ct_t) else ct_t.*;
                const ss_t = params.Group.elementToSharedSecret(try params.Group.mul(ct_t_point, expanded.dk_t));

                return c2priCombiner(ss_pq, ss_t, ct_t, &expanded.ek_t);
            }
        };

        /// A hybrid KEM key pair.
        pub const KeyPair = struct {
            public_key: PublicKey,
            secret_key: SecretKey,

            /// Deterministically derives a key pair from a cryptographically secure seed.
            ///
            /// Except in tests, applications should generally call `generate()` instead.
            pub fn generateDeterministic(seed: [params.Nseed]u8) !KeyPair {
                const expanded = try expandDecapsKeyG(seed);
                var ek_bytes: [params.Nek]u8 = undefined;
                const pq_ek = expanded.ek_pq.toBytes();
                @memcpy(ek_bytes[0..pq_ek.len], &pq_ek);
                @memcpy(ek_bytes[pq_ek.len..], &expanded.ek_t);
                return .{ .public_key = .{ .bytes = ek_bytes }, .secret_key = .{ .seed = seed } };
            }

            /// Generates a new random key pair.
            pub fn generate(io: std.Io) !KeyPair {
                var seed: [params.Nseed]u8 = undefined;
                io.random(&seed);
                return generateDeterministic(seed);
            }
        };

        /// An encapsulated shared secret with its ciphertext.
        pub const EncapsulatedSecret = struct {
            /// Length in bytes of the shared secret.
            pub const shared_length = params.Nss;
            /// Length in bytes of the ciphertext.
            pub const ciphertext_length = params.Nct;

            /// The shared secret (output of the combiner function).
            shared_secret: [shared_length]u8,
            /// The ciphertext to be transmitted to the decapsulating party.
            ciphertext: [ciphertext_length]u8,
        };
    };
}