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

randombit / botan / 22172928579

19 Feb 2026 07:42AM UTC coverage: 90.338% (-1.3%) from 91.672%
22172928579

Pull #5307

github

web-flow
Merge 6ff0be01a into 59d4d64fb
Pull Request #5307: Fix ML-DSA private key encoding

102778 of 113770 relevant lines covered (90.34%)

11436124.12 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/internal/ml_dsa_impl.h>
10

11
#include <botan/asn1_obj.h>
12
#include <botan/ber_dec.h>
13
#include <botan/der_enc.h>
14
#include <botan/exceptn.h>
15
#include <botan/internal/dilithium_algos.h>
16
#include <botan/internal/dilithium_types.h>
17
#include <utility>
18

19
namespace Botan {
20

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

25
Botan::DilithiumInternalKeypair decode_seed_only(std::span<const uint8_t> key_bits,
43✔
26
                                                     Botan::DilithiumConstants mode) {
27
   Botan::secure_vector<uint8_t> seed;
43✔
28
   Botan::BER_Decoder(key_bits)
86✔
29
      .decode(seed, Botan::ASN1_Type::OctetString, Botan::ASN1_Type(0), Botan::ASN1_Class::ContextSpecific)
43✔
30
      .verify_end();
43✔
31
   return Botan::Dilithium_Algos::expand_keypair(Botan::DilithiumSeedRandomness(seed), std::move(mode));
86✔
32
}
43✔
33

34
Botan::DilithiumInternalKeypair decode_expanded_only(std::span<const uint8_t> key_bits,
4✔
35
                                                         Botan::DilithiumConstants mode) {
36
   Botan::secure_vector<uint8_t> expanded;
4✔
37
   Botan::BER_Decoder(key_bits).decode(expanded, Botan::ASN1_Type::OctetString).verify_end();
4✔
38
   Botan::DilithiumInternalKeypair key_pair =
4✔
39
      Botan::Dilithium_Algos::decode_keypair(Botan::DilithiumSerializedPrivateKey(expanded), std::move(mode));
8✔
40
   return key_pair;
4✔
41
}
4✔
42

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

57
   DilithiumSerializedPrivateKey expanded_from_seed = Dilithium_Algos::encode_keypair(key_pair_from_seed);
4✔
58

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

65
}  // namespace
66

67
secure_vector<uint8_t> ML_DSA_Expanding_Keypair_Codec::encode_keypair(DilithiumInternalKeypair keypair) const {
67✔
68
   BOTAN_ASSERT_NONNULL(keypair.second);
67✔
69
   const auto& seed = keypair.second->seed();
67✔
70
   if(!keypair.second->mode().is_ml_dsa()) {
67✔
71
      // return the raw seed for dilithium
72
      BOTAN_ARG_CHECK(seed.has_value(), "Cannot encode keypair without the private seed");
×
73
      return seed.value().get();
×
74
   }
75
   secure_vector<uint8_t> result;
67✔
76
   DER_Encoder der_enc(result);
67✔
77
   if(!seed.has_value()) {
67✔
78
      // encode expanded-only format
79
      DilithiumSerializedPrivateKey expanded = Dilithium_Algos::encode_keypair(keypair);
2✔
80
      der_enc.encode(expanded.get(), ASN1_Type::OctetString);
2✔
81
   } else {
2✔
82
      // encode seed-only format
83
      der_enc.encode(seed.value().get(),
65✔
84
                     ASN1_Type(Botan::ASN1_Type::OctetString) /*real_type*/,
85
                     ASN1_Type(Botan::ASN1_Type(0)),
86
                     Botan::ASN1_Class::ContextSpecific);
87
      /* 
88
       * This yields the following ASN.1/DER structure:
89
       <80 20>
90
       0  32: [0]
91
            :   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
92
            :   10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
93
       *
94
       *  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.
95
       */
96
   }
97

98
   return result;
67✔
99
}
67✔
100

101
DilithiumInternalKeypair ML_DSA_Expanding_Keypair_Codec::decode_keypair(std::span<const uint8_t> private_key_bits,
54✔
102
                                                                        DilithiumConstants mode) const {
103
   if(private_key_bits.size() == 32) {
54✔
104
      // backwards compatibility (not RFC 9881 conforming) to raw seed format.
105
      return Botan::Dilithium_Algos::expand_keypair(Botan::DilithiumSeedRandomness(private_key_bits), std::move(mode));
×
106
   }
107
   // "seed-only" format from RFC 9881
108
   BER_Decoder ber_dec(private_key_bits);
54✔
109
   auto obj = ber_dec.peek_next_object();
54✔
110
   if(obj.type() == ASN1_Type(0)) {
54✔
111
        return decode_seed_only(private_key_bits, mode); 
43✔
112
   }
113
   // now it could still be "expanded-only" or "both"
114
   if(obj.type() == ASN1_Type::OctetString) {
11✔
115
        return decode_expanded_only(private_key_bits, mode); 
4✔
116
   }
117
   return decode_seed_plus_expanded(private_key_bits, mode);
7✔
118
}
58✔
119

120
}  // 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