Skip to content

Commit

Permalink
Add ML-DSA IPD instances to dilithium
Browse files Browse the repository at this point in the history
Co-Authored-By: Amos Treiber<[email protected]>
  • Loading branch information
FAlbertDev committed Jul 29, 2024
1 parent fe81d65 commit 7f6c0c7
Show file tree
Hide file tree
Showing 22 changed files with 4,403 additions and 40 deletions.
6 changes: 6 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@
1.3.6.1.4.1.25258.1.10.2 = Dilithium-6x5-AES-r3
1.3.6.1.4.1.25258.1.10.3 = Dilithium-8x7-AES-r3

# ML-DSA Initial Public Draft
# (taken from the IETF hackathon: https://github.com/IETF-Hackathon/pqc-certificates/blob/f9ecf761c3b4f3a84520536b7ce3175e8c7726fd/docs/oid_mapping.md#nist-draft-standard-algorithm-oids)
1.3.6.1.4.1.2.267.12.4.4 = ML-DSA-4x4-IPD
1.3.6.1.4.1.2.267.12.6.5 = ML-DSA-6x5-IPD
1.3.6.1.4.1.2.267.12.8.7 = ML-DSA-8x7-IPD

# HSS-LMS
# draft-gazdag-x509-hash-sigs-01
1.2.840.113549.1.9.16.3.17 = HSS-LMS
Expand Down
5 changes: 4 additions & 1 deletion src/cli/speed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2193,7 +2193,10 @@ class Speed final : public Command {
Botan::DilithiumMode::Dilithium6x5,
Botan::DilithiumMode::Dilithium6x5_AES,
Botan::DilithiumMode::Dilithium8x7,
Botan::DilithiumMode::Dilithium8x7_AES};
Botan::DilithiumMode::Dilithium8x7_AES,
Botan::DilithiumMode::ML_DSA4x4_IPD,
Botan::DilithiumMode::ML_DSA6x5_IPD,
Botan::DilithiumMode::ML_DSA8x7_IPD};

for(auto modet : all_modes) {
Botan::DilithiumMode mode(modet);
Expand Down
8 changes: 7 additions & 1 deletion src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* OID maps
*
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-07-23
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-07-26
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
Expand Down Expand Up @@ -142,6 +142,9 @@ std::unordered_map<std::string, std::string> OID_Map::load_oid2str_map() {
{"1.3.36.3.3.2.8.1.1.9", "brainpool320r1"},
{"1.3.6.1.4.1.11591.15.1", "OpenPGP.Ed25519"},
{"1.3.6.1.4.1.11591.4.11", "Scrypt"},
{"1.3.6.1.4.1.2.267.12.4.4", "ML-DSA-4x4-IPD"},
{"1.3.6.1.4.1.2.267.12.6.5", "ML-DSA-6x5-IPD"},
{"1.3.6.1.4.1.2.267.12.8.7", "ML-DSA-8x7-IPD"},
{"1.3.6.1.4.1.25258.1.10.1", "Dilithium-4x4-AES-r3"},
{"1.3.6.1.4.1.25258.1.10.2", "Dilithium-6x5-AES-r3"},
{"1.3.6.1.4.1.25258.1.10.3", "Dilithium-8x7-AES-r3"},
Expand Down Expand Up @@ -424,6 +427,9 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"Kyber-768-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 2})},
{"MD5", OID({1, 2, 840, 113549, 2, 5})},
{"MGF1", OID({1, 2, 840, 113549, 1, 1, 8})},
{"ML-DSA-4x4-IPD", OID({1, 3, 6, 1, 4, 1, 2, 267, 12, 4, 4})},
{"ML-DSA-6x5-IPD", OID({1, 3, 6, 1, 4, 1, 2, 267, 12, 6, 5})},
{"ML-DSA-8x7-IPD", OID({1, 3, 6, 1, 4, 1, 2, 267, 12, 8, 7})},
{"McEliece", OID({1, 3, 6, 1, 4, 1, 25258, 1, 3})},
{"Microsoft SmartcardLogon", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 2})},
{"Microsoft UPN", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 3})},
Expand Down
3 changes: 1 addition & 2 deletions src/lib/pubkey/dilithium/dilithium/dilithium_modern.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ namespace Botan {

class Dilithium_Common_Symmetric_Primitives : public Dilithium_Symmetric_Primitives {
public:
Dilithium_Common_Symmetric_Primitives(size_t collision_strength_in_bytes) :
Dilithium_Symmetric_Primitives(collision_strength_in_bytes) {}
Dilithium_Common_Symmetric_Primitives(const DilithiumConstants& mode) : Dilithium_Symmetric_Primitives(mode) {}

Botan::XOF& XOF(XofType type, std::span<const uint8_t> seed, uint16_t nonce) const override {
auto& xof = [&]() -> Botan::XOF& {
Expand Down
5 changes: 2 additions & 3 deletions src/lib/pubkey/dilithium/dilithium_aes/dilithium_aes.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Asymmetric primitives for dilithium AES
* Symmetric primitives for dilithium AES
* (C) 2022 Jack Lloyd
* (C) 2022 Manuel Glaser, Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity
*
Expand All @@ -22,8 +22,7 @@ namespace Botan {

class Dilithium_AES_Symmetric_Primitives : public Dilithium_Symmetric_Primitives {
public:
Dilithium_AES_Symmetric_Primitives(size_t collision_strength_in_bytes) :
Dilithium_Symmetric_Primitives(collision_strength_in_bytes) {}
Dilithium_AES_Symmetric_Primitives(const DilithiumConstants& mode) : Dilithium_Symmetric_Primitives(mode) {}

// AES mode always uses AES-256, regardless of the XofType
Botan::XOF& XOF(XofType /* type */, std::span<const uint8_t> seed, uint16_t nonce) const final {
Expand Down
21 changes: 16 additions & 5 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ DilithiumMode::Mode dilithium_mode_from_string(std::string_view str) {
if(str == "Dilithium-8x7-AES-r3") {
return DilithiumMode::Dilithium8x7_AES;
}
if(str == "ML-DSA-4x4-IPD") {
return DilithiumMode::ML_DSA4x4_IPD;
}
if(str == "ML-DSA-6x5-IPD") {
return DilithiumMode::ML_DSA6x5_IPD;
}
if(str == "ML-DSA-8x7-IPD") {
return DilithiumMode::ML_DSA8x7_IPD;
}

throw Invalid_Argument(fmt("'{}' is not a valid Dilithium mode name", str));
}
Expand Down Expand Up @@ -74,6 +83,12 @@ std::string DilithiumMode::to_string() const {
return "Dilithium-8x7-r3";
case DilithiumMode::Dilithium8x7_AES:
return "Dilithium-8x7-AES-r3";
case DilithiumMode::ML_DSA4x4_IPD:
return "ML-DSA-4x4-IPD";
case DilithiumMode::ML_DSA6x5_IPD:
return "ML-DSA-6x5-IPD";
case DilithiumMode::ML_DSA8x7_IPD:
return "ML-DSA-8x7-IPD";
}

BOTAN_ASSERT_UNREACHABLE();
Expand Down Expand Up @@ -210,11 +225,7 @@ class Dilithium_Signature_Operation final : public PK_Ops::Signature {
const auto& mode = m_priv_key->mode();
const auto& sympri = mode.symmetric_primitives();

// TODO: ML-DSA generates rhoprime differently, namely
// rhoprime = H(K, rnd, mu) with rnd being 32 random bytes or 32 zero bytes
const auto rhoprime = (m_randomized)
? rng.random_vec<DilithiumSeedRhoPrime>(DilithiumConstants::SEED_RHOPRIME_BYTES)
: sympri.H(m_priv_key->signing_seed(), mu);
const auto rhoprime = sympri.calc_rhoprime(rng, m_priv_key->signing_seed(), mu, m_randomized);
CT::poison(rhoprime);

// Note: nonce (as requested by `polyvecl_uniform_gamma1`) is actually just uint16_t
Expand Down
14 changes: 13 additions & 1 deletion src/lib/pubkey/dilithium/dilithium_common/dilithium.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,17 @@ namespace Botan {

class BOTAN_PUBLIC_API(3, 0) DilithiumMode {
public:
enum Mode { Dilithium4x4 = 1, Dilithium4x4_AES, Dilithium6x5, Dilithium6x5_AES, Dilithium8x7, Dilithium8x7_AES };
enum Mode {
Dilithium4x4 = 1,
Dilithium4x4_AES,
Dilithium6x5,
Dilithium6x5_AES,
Dilithium8x7,
Dilithium8x7_AES,
ML_DSA4x4_IPD,
ML_DSA6x5_IPD,
ML_DSA8x7_IPD
};

public:
DilithiumMode(Mode mode) : m_mode(mode) {}
Expand All @@ -37,6 +47,8 @@ class BOTAN_PUBLIC_API(3, 0) DilithiumMode {

bool is_modern() const { return !is_aes(); }

bool is_ipd() const { return m_mode == ML_DSA4x4_IPD || m_mode == ML_DSA6x5_IPD || m_mode == ML_DSA8x7_IPD; }

Mode mode() const { return m_mode; }

private:
Expand Down
4 changes: 2 additions & 2 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ decode_private_key(StrongSpan<const DilithiumSerializedPrivateKey> sk, const Dil

auto rho = slicer.copy<DilithiumSeedRho>(DilithiumConstants::SEED_RHO_BYTES);
auto key = slicer.copy<DilithiumSigningSeedK>(DilithiumConstants::SEED_SIGNING_KEY_BYTES);
auto tr = slicer.copy<DilithiumHashedPublicKey>(DilithiumConstants::PUBLIC_KEY_HASH_BYTES);
auto tr = slicer.copy<DilithiumHashedPublicKey>(mode.public_key_hash_bytes());

DilithiumPolyVec s1(mode.l());
for(auto& p : s1) {
Expand Down Expand Up @@ -417,7 +417,7 @@ std::optional<std::tuple<DilithiumCommitmentHash, DilithiumPolyVec, DilithiumPol
BufferSlicer slicer(sig);
BOTAN_ASSERT_NOMSG(slicer.remaining() == mode.signature_bytes());

auto commitment_hash = slicer.copy<DilithiumCommitmentHash>(DilithiumConstants::COMMITMENT_HASH_C1_BYTES);
auto commitment_hash = slicer.copy<DilithiumCommitmentHash>(mode.commitment_hash_full_bytes());

DilithiumPolyVec response(mode.l());
for(auto& p : response) {
Expand Down
34 changes: 31 additions & 3 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,36 @@

namespace Botan {

DilithiumConstants::DilithiumConstants(DilithiumMode mode) : m_mode(mode) {
namespace {
uint32_t public_key_hash_size(DilithiumMode mode) {
// ML-KEM IPD l. 297:
// [...] ML-DSA increases the length of tr to 512 bits [...]
return mode.is_ipd() ? 64 : 32;
}

uint32_t commitment_hash_full_size(DilithiumMode mode) {
// ML-KEM IPD l. 297-298:
// [ML-DSA] increases the length of c~ to 384 and 512 bits, respectively,
// for the parameter sets ML-DSA-65 and ML-DSA-87.
switch(mode.mode()) {
case Botan::DilithiumMode::ML_DSA6x5_IPD:
return 48;
case Botan::DilithiumMode::ML_DSA8x7_IPD:
return 64;
default:
return 32;
}
}
} // namespace

DilithiumConstants::DilithiumConstants(DilithiumMode mode) :
m_mode(mode),
m_public_key_hash_bytes(public_key_hash_size(m_mode)),
m_commitment_hash_full_bytes(commitment_hash_full_size(m_mode)) {
switch(m_mode.mode()) {
case Botan::DilithiumMode::Dilithium4x4:
case Botan::DilithiumMode::Dilithium4x4_AES:
case Botan::DilithiumMode::ML_DSA4x4_IPD:
m_tau = DilithiumTau::_39;
m_lambda = DilithiumLambda::_128;
m_gamma1 = DilithiumGamma1::ToThe17th;
Expand All @@ -31,6 +57,7 @@ DilithiumConstants::DilithiumConstants(DilithiumMode mode) : m_mode(mode) {
break;
case Botan::DilithiumMode::Dilithium6x5:
case Botan::DilithiumMode::Dilithium6x5_AES:
case Botan::DilithiumMode::ML_DSA6x5_IPD:
m_tau = DilithiumTau::_49;
m_lambda = DilithiumLambda::_192;
m_gamma1 = DilithiumGamma1::ToThe19th;
Expand All @@ -43,6 +70,7 @@ DilithiumConstants::DilithiumConstants(DilithiumMode mode) : m_mode(mode) {
break;
case Botan::DilithiumMode::Dilithium8x7:
case Botan::DilithiumMode::Dilithium8x7_AES:
case Botan::DilithiumMode::ML_DSA8x7_IPD:
m_tau = DilithiumTau::_60;
m_lambda = DilithiumLambda::_256;
m_gamma1 = DilithiumGamma1::ToThe19th;
Expand All @@ -63,9 +91,9 @@ DilithiumConstants::DilithiumConstants(DilithiumMode mode) : m_mode(mode) {
const auto hint_bytes = m_omega + m_k;

m_private_key_bytes =
SEED_RHO_BYTES + SEED_SIGNING_KEY_BYTES + PUBLIC_KEY_HASH_BYTES + s1_bytes + s2_bytes + t0_bytes;
SEED_RHO_BYTES + SEED_SIGNING_KEY_BYTES + m_public_key_hash_bytes + s1_bytes + s2_bytes + t0_bytes;
m_public_key_bytes = SEED_RHO_BYTES + t1_bytes;
m_signature_bytes = COMMITMENT_HASH_FULL_BYTES + z_bytes + hint_bytes;
m_signature_bytes = m_commitment_hash_full_bytes + z_bytes + hint_bytes;
m_serialized_commitment_bytes = 32 * m_k * bitlen(((Q - 1) / (2 * m_gamma2)) - 1);

m_symmetric_primitives = Dilithium_Symmetric_Primitives::create(*this);
Expand Down
13 changes: 9 additions & 4 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ class DilithiumConstants final {
static constexpr size_t SEED_RANDOMNESS_BYTES = 32;
static constexpr size_t SEED_RHO_BYTES = 32;
static constexpr size_t SEED_RHOPRIME_BYTES = 64;
// Byte length of 'rnd' (IPD Algorithm 2 l. 7 'rnd' - only used for ML-DSA)
static constexpr size_t OPTIONAL_RANDOMNESS_BYTES = 32;
static constexpr size_t SEED_SIGNING_KEY_BYTES = 32;
static constexpr size_t MESSAGE_HASH_BYTES = 64;
static constexpr size_t PUBLIC_KEY_HASH_BYTES = 32;
static constexpr size_t COMMITMENT_HASH_FULL_BYTES = 32;
static constexpr size_t COMMITMENT_HASH_C1_BYTES = 32;

/// @}
Expand Down Expand Up @@ -118,8 +118,11 @@ class DilithiumConstants final {
/// maximal hamming weight of the hint polynomial vector 'h'
DilithiumOmega omega() const { return m_omega; }

/// length of the entire commitment hash in bytes
size_t commitment_hash_full_bytes() const { return COMMITMENT_HASH_FULL_BYTES; }
/// length of the public key hash 'tr' in bytes (differs between R3 and IPD)
size_t public_key_hash_bytes() const { return m_public_key_hash_bytes; }

/// length of the entire commitment hash 'c~' in bytes (differs between R3 and IPD)
size_t commitment_hash_full_bytes() const { return m_commitment_hash_full_bytes; }

/// @}

Expand Down Expand Up @@ -159,6 +162,8 @@ class DilithiumConstants final {
DilithiumEta m_eta;
DilithiumBeta m_beta;
DilithiumOmega m_omega;
uint32_t m_public_key_hash_bytes;
uint32_t m_commitment_hash_full_bytes;

uint32_t m_private_key_bytes;
uint32_t m_public_key_bytes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,37 @@ namespace Botan {
std::unique_ptr<Dilithium_Symmetric_Primitives> Dilithium_Symmetric_Primitives::create(const DilithiumConstants& mode) {
#if BOTAN_HAS_DILITHIUM
if(mode.is_modern()) {
return std::make_unique<Dilithium_Common_Symmetric_Primitives>(mode.commitment_hash_full_bytes());
return std::make_unique<Dilithium_Common_Symmetric_Primitives>(mode);
}
#endif

#if BOTAN_HAS_DILITHIUM_AES
if(mode.is_aes()) {
return std::make_unique<Dilithium_AES_Symmetric_Primitives>(mode.commitment_hash_full_bytes());
return std::make_unique<Dilithium_AES_Symmetric_Primitives>(mode);
}
#endif

throw Not_Implemented("requested Dilithium mode is not enabled in this build");
}

DilithiumSeedRhoPrime Dilithium_Symmetric_Primitives::calc_rhoprime(RandomNumberGenerator& rng,
StrongSpan<const DilithiumSigningSeedK> k,
StrongSpan<const DilithiumMessageRepresentative> mu,
bool randomized) const {
if(m_mode.is_ipd()) {
// ML-KEM IPD, Algor. 2, l. 7,8:
// rnd <- {0, 1}^256 (For the optional deterministic variant, substitute rnd <- {0}^256)
// p' <- H(K || rnd || mu, 512)
auto rnd = (randomized)
? rng.random_vec<DilithiumOptionalRandomness>(DilithiumConstants::OPTIONAL_RANDOMNESS_BYTES)
: DilithiumOptionalRandomness(DilithiumConstants::OPTIONAL_RANDOMNESS_BYTES);
return H(k, rnd, mu);

} else /* is Dilithium R3 */ {
// Dilitihium R3, Figure 4, l. 12:
// p' in {0, 1}^512 := H(K || mu) (or p' <- {0, 1}^512 for randomized signing)
return (randomized) ? rng.random_vec<DilithiumSeedRhoPrime>(DilithiumConstants::SEED_RHOPRIME_BYTES) : H(k, mu);
}
}

} // namespace Botan
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#define BOTAN_DILITHIUM_ASYM_PRIMITIVES_H_

#include <botan/dilithium.h>
#include <botan/rng.h>

#include <botan/internal/dilithium_types.h>
#include <botan/internal/fmt.h>
Expand Down Expand Up @@ -61,8 +62,10 @@ class Dilithium_Symmetric_Primitives {
enum class XofType { k128, k256 };

protected:
Dilithium_Symmetric_Primitives(size_t commitment_hash_length_bytes) :
m_commitment_hash_length_bytes(commitment_hash_length_bytes) {}
Dilithium_Symmetric_Primitives(const DilithiumConstants& mode) :
m_commitment_hash_length_bytes(mode.commitment_hash_full_bytes()),
m_public_key_hash_bytes(mode.public_key_hash_bytes()),
m_mode(mode.mode()) {}

public:
static std::unique_ptr<Dilithium_Symmetric_Primitives> create(const DilithiumConstants& mode);
Expand All @@ -77,13 +80,14 @@ class Dilithium_Symmetric_Primitives {
return DilithiumMessageHash(std::move(tr));
}

DilithiumHashedPublicKey H(StrongSpan<const DilithiumSerializedPublicKey> pk) const {
return H_256<DilithiumHashedPublicKey>(DilithiumConstants::PUBLIC_KEY_HASH_BYTES, pk);
}
/// Computes the private random seed rho prime used for signing
DilithiumSeedRhoPrime calc_rhoprime(RandomNumberGenerator& rng,
StrongSpan<const DilithiumSigningSeedK> k,
StrongSpan<const DilithiumMessageRepresentative> mu,
bool randomized) const;

DilithiumSeedRhoPrime H(StrongSpan<const DilithiumSigningSeedK> k,
StrongSpan<const DilithiumMessageRepresentative> mu) const {
return H_256<DilithiumSeedRhoPrime>(DilithiumConstants::SEED_RHOPRIME_BYTES, k, mu);
DilithiumHashedPublicKey H(StrongSpan<const DilithiumSerializedPublicKey> pk) const {
return H_256<DilithiumHashedPublicKey>(m_public_key_hash_bytes, pk);
}

std::tuple<DilithiumSeedRho, DilithiumSeedRhoPrime, DilithiumSigningSeedK> H(
Expand Down Expand Up @@ -142,8 +146,24 @@ class Dilithium_Symmetric_Primitives {
return m_xof.output<OutT>(outbytes);
}

/// Rho prime (deterministic) computation for Dilithium R3 instances
DilithiumSeedRhoPrime H(StrongSpan<const DilithiumSigningSeedK> k,
StrongSpan<const DilithiumMessageRepresentative> mu) const {
return H_256<DilithiumSeedRhoPrime>(DilithiumConstants::SEED_RHOPRIME_BYTES, k, mu);
}

/// Rho prime computation for ML-KEM IPD
DilithiumSeedRhoPrime H(StrongSpan<const DilithiumSigningSeedK> k,
StrongSpan<const DilithiumOptionalRandomness> rnd,
StrongSpan<const DilithiumMessageRepresentative> mu) const {
return H_256<DilithiumSeedRhoPrime>(DilithiumConstants::SEED_RHOPRIME_BYTES, k, rnd, mu);
}

private:
size_t m_commitment_hash_length_bytes;
size_t m_public_key_hash_bytes;
DilithiumMode m_mode;

mutable SHAKE_256_XOF m_xof;
mutable SHAKE_256_XOF m_xof_external;
};
Expand Down
3 changes: 3 additions & 0 deletions src/lib/pubkey/dilithium/dilithium_common/dilithium_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ using DilithiumSeedRho = Strong<std::vector<uint8_t>, struct DilithiumPublicSeed
/// Private seed to sample the polynomial vectors s1 and s2 from
using DilithiumSeedRhoPrime = Strong<secure_vector<uint8_t>, struct DilithiumSeedRhoPrime_>;

/// Optional randomness 'rnd' used for rho prime computation in ML-DSA
using DilithiumOptionalRandomness = Strong<secure_vector<uint8_t>, struct DilithiumOptionalRandomness_>;

/// Private seed K used during signing
using DilithiumSigningSeedK = Strong<secure_vector<uint8_t>, struct DilithiumSeedK_>;

Expand Down
Loading

0 comments on commit 7f6c0c7

Please sign in to comment.