feature. See also
. The project being documented here (as the example) is the Zig library itself.
ml_kem.Kyber
fn Kyber(comptime p: Params) type
File
Code
fn Kyber(comptime p: Params) type {
return struct {
pub const ciphertext_length = Poly.compressedSize(p.du) * p.k + Poly.compressedSize(p.dv);
const Self = @This();
const V = PolyVec(p.k);
const M = Mat(p.k);
pub const shared_length = common_shared_key_size;
pub const encaps_seed_length = common_encaps_seed_length;
pub const seed_length: usize = inner_seed_length + shared_length;
pub const name = p.name;
pub const EncapsulatedSecret = struct {
shared_secret: [shared_length]u8,
ciphertext: [ciphertext_length]u8,
};
pub const PublicKey = struct {
pk: InnerPk,
hpk: [h_length]u8,
pub const encoded_length = InnerPk.encoded_length;
pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret {
var m: [inner_plaintext_length]u8 = undefined;
io.random(&m);
return encapsInner(pk, &m);
}
pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret {
var m: [inner_plaintext_length]u8 = undefined;
if (p.ml_kem) {
@memcpy(&m, seed);
} else {
sha3.Sha3_256.hash(seed, &m, .{});
}
return encapsInner(pk, &m);
}
fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret {
var kr: [inner_plaintext_length + h_length]u8 = undefined;
var g = sha3.Sha3_512.init(.{});
g.update(m);
g.update(&pk.hpk);
g.final(&kr);
const ct = pk.pk.encrypt(m, kr[32..64]);
if (p.ml_kem) {
return EncapsulatedSecret{
.shared_secret = kr[0..shared_length].*,
.ciphertext = ct,
};
} else {
sha3.Sha3_256.hash(&ct, kr[32..], .{});
var ss: [shared_length]u8 = undefined;
sha3.Shake256.hash(&kr, &ss, .{});
return EncapsulatedSecret{
.shared_secret = ss,
.ciphertext = ct,
};
}
}
pub fn toBytes(pk: PublicKey) [encoded_length]u8 {
return pk.pk.toBytes();
}
pub fn fromBytes(buf: *const [encoded_length]u8) errors.NonCanonicalError!PublicKey {
var ret: PublicKey = undefined;
ret.pk = try InnerPk.fromBytes(buf[0..InnerPk.encoded_length]);
sha3.Sha3_256.hash(buf, &ret.hpk, .{});
return ret;
}
};
pub const SecretKey = struct {
sk: InnerSk,
pk: InnerPk,
hpk: [h_length]u8,
z: [shared_length]u8,
pub const encoded_length: usize =
InnerSk.encoded_length + InnerPk.encoded_length + h_length + shared_length;
pub fn decaps(sk: SecretKey, ct: *const [ciphertext_length]u8) ![shared_length]u8 {
const m2 = sk.sk.decrypt(ct);
var kr2: [64]u8 = undefined;
var g = sha3.Sha3_512.init(.{});
g.update(&m2);
g.update(&sk.hpk);
g.final(&kr2);
const ct2 = sk.pk.encrypt(&m2, kr2[32..64]);
if (p.ml_kem) {
var k_bar: [shared_length]u8 = undefined;
var j = sha3.Shake256.init(.{});
j.update(&sk.z);
j.update(ct);
j.squeeze(&k_bar);
cmov(shared_length, kr2[0..shared_length], k_bar, ctneq(ciphertext_length, ct.*, ct2));
return kr2[0..shared_length].*;
} else {
sha3.Sha3_256.hash(ct, kr2[32..], .{});
cmov(32, kr2[0..32], sk.z, ctneq(ciphertext_length, ct.*, ct2));
var ss: [shared_length]u8 = undefined;
sha3.Shake256.hash(&kr2, &ss, .{});
return ss;
}
}
pub fn toBytes(sk: SecretKey) [encoded_length]u8 {
return sk.sk.toBytes() ++ sk.pk.toBytes() ++ sk.hpk ++ sk.z;
}
pub fn fromBytes(buf: *const [encoded_length]u8) errors.NonCanonicalError!SecretKey {
var ret: SecretKey = undefined;
comptime var s: usize = 0;
ret.sk = InnerSk.fromBytes(buf[s .. s + InnerSk.encoded_length]);
s += InnerSk.encoded_length;
ret.pk = try InnerPk.fromBytes(buf[s .. s + InnerPk.encoded_length]);
s += InnerPk.encoded_length;
ret.hpk = buf[s..][0..h_length].*;
s += h_length;
ret.z = buf[s..][0..shared_length].*;
return ret;
}
};
pub const KeyPair = struct {
secret_key: SecretKey,
public_key: PublicKey,
pub fn generateDeterministic(seed: [seed_length]u8) !KeyPair {
var ret: KeyPair = undefined;
innerKeyFromSeed(
seed[0..inner_seed_length].*,
&ret.public_key.pk,
&ret.secret_key.sk,
);
ret.secret_key.pk = ret.public_key.pk;
ret.secret_key.z = seed[inner_seed_length..seed_length].*;
sha3.Sha3_256.hash(&ret.public_key.pk.toBytes(), &ret.secret_key.hpk, .{});
ret.public_key.hpk = ret.secret_key.hpk;
return ret;
}
pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
};
}
}
};
const inner_plaintext_length: usize = Poly.compressedSize(1);
const InnerPk = struct {
rho: [32]u8,
th: V,
// Cached values
aT: M,
const encoded_length = V.encoded_length + 32;
fn encrypt(
pk: InnerPk,
pt: *const [inner_plaintext_length]u8,
seed: *const [32]u8,
) [ciphertext_length]u8 {
const rh = V.noise(p.eta1, 0, seed).ntt().barrettReduce();
const e1 = V.noise(eta2, p.k, seed);
const e2 = Poly.noise(eta2, 2 * p.k, seed);
var u: V = undefined;
for (0..p.k) |i| {
// are bounded by 4.5q and so their product is bounded by 2¹⁵q
// as required for multiplication.
u.ps[i] = pk.aT.rows[i].dotHat(rh);
}
// multiplications in the inner product added a factor R⁻¹ which
// the InvNTT cancels out.
u = u.barrettReduce().invNTT().add(e1).normalize();
const v = pk.th.dotHat(rh).barrettReduce().invNTT()
.add(Poly.decompress(1, pt)).add(e2).normalize();
return u.compress(p.du) ++ v.compress(p.dv);
}
fn toBytes(pk: InnerPk) [encoded_length]u8 {
return pk.th.toBytes() ++ pk.rho;
}
fn fromBytes(buf: *const [encoded_length]u8) errors.NonCanonicalError!InnerPk {
var ret: InnerPk = undefined;
const th_bytes = buf[0..V.encoded_length];
ret.th = V.fromBytes(th_bytes).normalize();
if (p.ml_kem) {
if (!mem.eql(u8, &ret.th.toBytes(), th_bytes)) {
return error.NonCanonical;
}
}
ret.rho = buf[V.encoded_length..encoded_length].*;
ret.aT = M.uniform(ret.rho, true);
return ret;
}
};
const InnerSk = struct {
sh: V,
const encoded_length = V.encoded_length;
fn decrypt(sk: InnerSk, ct: *const [ciphertext_length]u8) [inner_plaintext_length]u8 {
const u = V.decompress(p.du, ct[0..comptime V.compressedSize(p.du)]);
const v = Poly.decompress(
p.dv,
ct[comptime V.compressedSize(p.du)..ciphertext_length],
);
return v.sub(sk.sh.dotHat(u.ntt()).barrettReduce().invNTT())
.normalize().compress(1);
}
fn toBytes(sk: InnerSk) [encoded_length]u8 {
return sk.sh.toBytes();
}
fn fromBytes(buf: *const [encoded_length]u8) InnerSk {
var ret: InnerSk = undefined;
ret.sh = V.fromBytes(buf).normalize();
return ret;
}
};
fn innerKeyFromSeed(seed: [inner_seed_length]u8, pk: *InnerPk, sk: *InnerSk) void {
var expanded_seed: [64]u8 = undefined;
var h = sha3.Sha3_512.init(.{});
h.update(&seed);
if (p.ml_kem) h.update(&[1]u8{p.k});
h.final(&expanded_seed);
pk.rho = expanded_seed[0..32].*;
const sigma = expanded_seed[32..64];
pk.aT = M.uniform(pk.rho, false);
// Sample secret vector s.
sk.sh = V.noise(p.eta1, 0, sigma).ntt().normalize();
const eh = PolyVec(p.k).noise(p.eta1, p.k, sigma).ntt();
var th: V = undefined;
for (0..p.k) |i| {
// are bounded by 4.5q and so their product is bounded by 2¹⁵q
// as required for multiplication.
// A and s were not in Montgomery form, so the Montgomery
// multiplications in the inner product added a factor R⁻¹ which
// we'll cancel out with toMont(). This will also ensure the
// coefficients of th are bounded in absolute value by q.
th.ps[i] = pk.aT.rows[i].dotHat(sk.sh).toMont();
}
pk.th = th.add(eh).normalize();
pk.aT = pk.aT.transpose();
}
};
}