Files
MicrOBU/microbu-esp32c5/external/vanetza-idf/ports/esp_idf/src/ecc.cpp
T
Ashin Walpola 0e9525162d Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
2026-09-23 17:46:40 +02:00

83 lines
3.4 KiB
C++

#include <vanetza_idf/ecc.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <algorithm>
#include <iterator>
namespace vanetza_idf::ecc {
using namespace vanetza::security;
namespace {
// Fixed width, no heap: a 384-bit product needs 768 bits before reduction.
using Int = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
1024, 1024, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
// FIPS 186-4 D.1.2.3 (P-256), RFC 5639 3.4 (brainpoolP256r1) and 3.6 (brainpoolP384r1).
// The host regression compares every constant with OpenSSL's built-in groups.
constexpr CurveParameters curves[] = {
{KeyType::NistP256, 32,
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"},
{KeyType::BrainpoolP256r1, 32,
"A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377",
"7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9",
"26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6"},
{KeyType::BrainpoolP384r1, 48,
"8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53",
"7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826",
"04A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11"},
};
Int from_hex(const char* hex) { return Int(std::string("0x") + hex); }
Int from_bytes(const vanetza::ByteBuffer& bytes) {
Int value;
boost::multiprecision::import_bits(value, bytes.begin(), bytes.end(), 8, true);
return value;
}
vanetza::ByteBuffer to_bytes(const Int& value, std::size_t octets) {
vanetza::ByteBuffer out;
boost::multiprecision::export_bits(value, std::back_inserter(out), 8, true);
if (out.size() > octets) return {};
out.insert(out.begin(), octets - out.size(), 0);
return out;
}
} // namespace
const CurveParameters* curve(KeyType type) {
for (const auto& c : curves) if (c.type == type) return &c;
return nullptr;
}
boost::optional<Uncompressed> decompress(KeyType type, const vanetza::ByteBuffer& x_bytes, bool y_odd) {
const auto* c = curve(type);
if (!c || x_bytes.size() != c->octets) return boost::none;
const Int p = from_hex(c->p), a = from_hex(c->a), b = from_hex(c->b);
const Int x = from_bytes(x_bytes);
if (x >= p) return boost::none;
// rhs = x^3 + a*x + b (mod p)
Int rhs = (x * x) % p;
rhs = (rhs * x) % p;
rhs = (rhs + (a * x) % p) % p;
rhs = (rhs + b) % p;
// p = 3 (mod 4): sqrt(n) = n^((p+1)/4) (mod p) when n is a quadratic residue.
Int y = boost::multiprecision::powm(rhs, (p + 1) / 4, p);
if ((y * y) % p != rhs) return boost::none; // x is not on the curve
if (static_cast<bool>(y & 1) != y_odd) y = p - y;
Uncompressed point;
point.x = x_bytes;
point.y = to_bytes(y, c->octets);
if (point.y.empty()) return boost::none;
return point;
}
vanetza::ByteBuffer encode_uncompressed(const Uncompressed& point) {
vanetza::ByteBuffer out;
out.reserve(1 + point.x.size() + point.y.size());
out.push_back(0x04);
out.insert(out.end(), point.x.begin(), point.x.end());
out.insert(out.end(), point.y.begin(), point.y.end());
return out;
}
} // namespace vanetza_idf::ecc