• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 21944630505

12 Feb 2026 11:24AM UTC coverage: 90.076% (+0.01%) from 90.063%
21944630505

Pull #5307

github

web-flow
Merge c4cab7420 into f97d7db3f
Pull Request #5307: Draft: Fix ML-DSA private key encoding

102323 of 113596 relevant lines covered (90.08%)

11536245.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/src/lib/pubkey/dilithium/ml_dsa/ml_dsa_impl.cpp
1
/*
2
* Asymmetric primitives for ML-DSA
3
* (C) 2024 Jack Lloyd
4
* (C) 2024 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include "botan/asn1_obj.h"
10
#include "botan/exceptn.h"
11
#include "botan/internal/dilithium_types.h"
12
#include <botan/internal/ml_dsa_impl.h>
13

14
#include <botan/internal/dilithium_algos.h>
15

16
#include <botan/ber_dec.h>
17
#include <botan/der_enc.h>
18
#include <string>
19
#include <utility>
20

21
namespace Botan {
22

23
namespace {
24
typedef Botan::DilithiumInternalKeypair (*decoding_func)(std::span<const uint8_t> key_bits,
25
                                                         Botan::DilithiumConstants mode);
26

27
Botan::DilithiumInternalKeypair decode_seed_only_or_throw(std::span<const uint8_t> key_bits,
51✔
28
                                                     Botan::DilithiumConstants mode) {
29
   Botan::secure_vector<uint8_t> seed;
51✔
30
   Botan::BER_Decoder(key_bits)
102✔
31
      .decode(seed, Botan::ASN1_Type::OctetString, Botan::ASN1_Type(0), Botan::ASN1_Class::ContextSpecific)
51✔
32
      .verify_end();
40✔
33
   return Botan::Dilithium_Algos::expand_keypair(Botan::DilithiumSeedRandomness(seed), std::move(mode));
91✔
34
}
40✔
35

36
Botan::DilithiumInternalKeypair decode_expanded_only_or_throw(std::span<const uint8_t> key_bits,
11✔
37
                                                         Botan::DilithiumConstants mode) {
38
   Botan::secure_vector<uint8_t> expanded;
11✔
39
   Botan::BER_Decoder(key_bits).decode(expanded, Botan::ASN1_Type::OctetString).verify_end();
11✔
40
   Botan::DilithiumInternalKeypair key_pair =
7✔
41
      Botan::Dilithium_Algos::decode_keypair(Botan::DilithiumSerializedPrivateKey(expanded), std::move(mode));
18✔
42
   return key_pair;
7✔
43
}
7✔
44

45
Botan::DilithiumInternalKeypair decode_seed_plus_expanded_or_throw(std::span<const uint8_t> key_bits, Botan::DilithiumConstants mode) {
54✔
46
   Botan::secure_vector<uint8_t> expanded;
54✔
47
   Botan::secure_vector<uint8_t> seed;
54✔
48
   Botan::BER_Decoder(key_bits)
61✔
49
      .start_sequence()
11✔
50
      .decode(seed, Botan::ASN1_Type::OctetString)
4✔
51
      .decode(expanded, Botan::ASN1_Type::OctetString)
4✔
52
      .end_cons()
4✔
53
      .verify_end();
4✔
54
   Botan::DilithiumInternalKeypair key_pair =
4✔
55
      Botan::Dilithium_Algos::decode_keypair(Botan::DilithiumSerializedPrivateKey(expanded), mode);
59✔
56
   Botan::DilithiumInternalKeypair key_pair_from_seed =
4✔
57
      Botan::Dilithium_Algos::expand_keypair(Botan::DilithiumSeedRandomness(seed), std::move(mode));
8✔
58

59
   DilithiumSerializedPrivateKey expanded_from_seed = Dilithium_Algos::encode_keypair(key_pair_from_seed);
4✔
60

61
   if(expanded_from_seed.get() != expanded) {
4✔
62
      throw Botan::Decoding_Error("seed and expanded key in ML-DSA serialized key do not match");
1✔
63
   }
64
   return key_pair;
3✔
65
}
62✔
66

67
}  // namespace
68

69
secure_vector<uint8_t> ML_DSA_Expanding_Keypair_Codec::encode_keypair(DilithiumInternalKeypair keypair) const {
67✔
70
   BOTAN_ASSERT_NONNULL(keypair.second);
67✔
71
   const auto& seed = keypair.second->seed();
67✔
72
   if(!keypair.second->mode().is_ml_dsa()) {
67✔
73
      // return the raw seed for dilithium
74
      BOTAN_ARG_CHECK(seed.has_value(), "Cannot encode keypair without the private seed");
×
75
      return seed.value().get();
×
76
   }
77
   secure_vector<uint8_t> result;
67✔
78
   DER_Encoder der_enc(result);
67✔
79
   if(!seed.has_value()) {
67✔
80
      // encode expanded-only format
81
      DilithiumSerializedPrivateKey expanded = Dilithium_Algos::encode_keypair(keypair);
5✔
82
      der_enc.encode(expanded.get(), ASN1_Type::OctetString);
5✔
83
   } else {
5✔
84
      // encode seed-only format
85
      der_enc.encode(seed.value().get(),
62✔
86
                     ASN1_Type(Botan::ASN1_Type::OctetString) /*real_type*/,
87
                     ASN1_Type(Botan::ASN1_Type(0)),
88
                     Botan::ASN1_Class::ContextSpecific);
89
      /* 
90
       * This yields the following ASN.1/DER structure:
91
       <80 20>
92
       0  32: [0]
93
            :   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
94
            :   10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
95
       *
96
       *  Note: The previous format, which only contains the seed as an OCTET STRING without the [0]-tag, can still be decoded by OpenSSL. Apparently, it has this feature as a non-standard compatibility fallback for legacy formats.
97
       */
98
   }
99

100
   return result;
67✔
101
}
67✔
102

103
DilithiumInternalKeypair ML_DSA_Expanding_Keypair_Codec::decode_keypair(std::span<const uint8_t> private_key_bits,
54✔
104
                                                                        DilithiumConstants mode) const {
105
   if(private_key_bits.size() == 32) {
54✔
106
      // backwards compatibility (not RFC 9881 conforming) to raw seed format.
107
      return Botan::Dilithium_Algos::expand_keypair(Botan::DilithiumSeedRandomness(private_key_bits), std::move(mode));
×
108
   }
109
   /* we have to check 3 different format: "seed-only", "expanded-only", and "both"
110
     */
111
   decoding_func fn_arr[3] = {decode_seed_plus_expanded_or_throw, decode_seed_only_or_throw, decode_expanded_only_or_throw};
54✔
112
   for(auto fn : fn_arr) {
120✔
113
      try {
116✔
114
         return fn(private_key_bits, mode);
116✔
115
      } catch(const Botan::Decoding_Error& e) {
66✔
116
         // pass
117
      }
66✔
118
   }
119
   throw Decoding_Error("unsupported ML-DSA private key format, key size in bytes: " +
8✔
120
                        std::to_string(private_key_bits.size()));
12✔
121
}
122

123
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc