1104 lines
29 KiB
C++
1104 lines
29 KiB
C++
|
|
/// @file dpf/p256.hpp
|
|||
|
|
/// @brief NIST P-256 points as a DPF output group.
|
|||
|
|
/// @details A point is 33 bytes in SEC1 compressed form. The identity is 33
|
|||
|
|
/// zero bytes. `p256{k}` is `k` times the generator, and
|
|||
|
|
/// `p256::from_compressed` reads a 33-byte encoding. Leaf addition is
|
|||
|
|
/// point addition on valid encodings. A PRG block is stretched to at
|
|||
|
|
/// least 256 bits and hashed onto the curve via `from_seed`. There is
|
|||
|
|
/// no point×point product. This is a point-function output; it also
|
|||
|
|
/// supplies `from_seed` for comparison payloads.
|
|||
|
|
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
|
|||
|
|
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
|
|||
|
|
/// see [LICENSE.md](@ref license) for details.
|
|||
|
|
|
|||
|
|
#ifndef LIBDPF_INCLUDE_DPF_P256_HPP__
|
|||
|
|
#define LIBDPF_INCLUDE_DPF_P256_HPP__
|
|||
|
|
|
|||
|
|
#include <array>
|
|||
|
|
#include <cstddef>
|
|||
|
|
#include <cstdint>
|
|||
|
|
#include <cstring>
|
|||
|
|
#include <ostream>
|
|||
|
|
#include <stdexcept>
|
|||
|
|
#include <type_traits>
|
|||
|
|
|
|||
|
|
#include "hedley/hedley.h"
|
|||
|
|
#include "simde/simde/x86/avx2.h"
|
|||
|
|
|
|||
|
|
#include "dpf/leaf_arithmetic.hpp"
|
|||
|
|
#include "dpf/random.hpp"
|
|||
|
|
#include "dpf/utils.hpp"
|
|||
|
|
|
|||
|
|
namespace dpf
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
namespace p256_detail
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
struct fe
|
|||
|
|
{
|
|||
|
|
std::uint64_t d[4]{};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t P[4] = {
|
|||
|
|
0xffffffffffffffffull,
|
|||
|
|
0x00000000ffffffffull,
|
|||
|
|
0x0000000000000000ull,
|
|||
|
|
0xffffffff00000001ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t B[4] = {
|
|||
|
|
0x3bce3c3e27d2604bull,
|
|||
|
|
0x651d06b0cc53b0f6ull,
|
|||
|
|
0xb3ebbd55769886bcull,
|
|||
|
|
0x5ac635d8aa3a93e7ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t GX[4] = {
|
|||
|
|
0xf4a13945d898c296ull,
|
|||
|
|
0x77037d812deb33a0ull,
|
|||
|
|
0xf8bce6e563a440f2ull,
|
|||
|
|
0x6b17d1f2e12c4247ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t GY[4] = {
|
|||
|
|
0xcbb6406837bf51f5ull,
|
|||
|
|
0x2bce33576b315eceull,
|
|||
|
|
0x8ee7eb4a7c0f9e16ull,
|
|||
|
|
0x4fe342e2fe1a7f9bull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t N[4] = {
|
|||
|
|
0xf3b9cac2fc632551ull,
|
|||
|
|
0xbce6faada7179e84ull,
|
|||
|
|
0xffffffffffffffffull,
|
|||
|
|
0xffffffff00000000ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t EXP_P_PLUS_1_OVER_4[4] = {
|
|||
|
|
0x0000000000000000ull,
|
|||
|
|
0x0000000040000000ull,
|
|||
|
|
0x4000000000000000ull,
|
|||
|
|
0x3fffffffc0000000ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t EXP_P_MINUS_1_OVER_2[4] = {
|
|||
|
|
0xffffffffffffffffull,
|
|||
|
|
0x000000007fffffffull,
|
|||
|
|
0x8000000000000000ull,
|
|||
|
|
0x7fffffff80000000ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline constexpr std::uint64_t EXP_P_MINUS_2[4] = {
|
|||
|
|
0xfffffffffffffffdull,
|
|||
|
|
0x00000000ffffffffull,
|
|||
|
|
0x0000000000000000ull,
|
|||
|
|
0xffffffff00000001ull,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
struct affine
|
|||
|
|
{
|
|||
|
|
fe x{};
|
|||
|
|
fe y{};
|
|||
|
|
bool inf = true;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
int fe_cmp(const fe &a, const fe &b) noexcept
|
|||
|
|
{
|
|||
|
|
for (int i = 3; i >= 0; --i)
|
|||
|
|
{
|
|||
|
|
if (a.d[i] < b.d[i])
|
|||
|
|
return -1;
|
|||
|
|
if (a.d[i] > b.d[i])
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
int limbs_cmp(const std::uint64_t *a, const std::uint64_t *b, int n) noexcept
|
|||
|
|
{
|
|||
|
|
for (int i = n - 1; i >= 0; --i)
|
|||
|
|
{
|
|||
|
|
if (a[i] < b[i])
|
|||
|
|
return -1;
|
|||
|
|
if (a[i] > b[i])
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool fe_is_zero(const fe &a) noexcept
|
|||
|
|
{
|
|||
|
|
return (a.d[0] | a.d[1] | a.d[2] | a.d[3]) == 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Conditionally subtract `P` when `a` is in `[P, 2P)`.
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_sub_p_if_ge(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
if (fe_cmp(a, fe{{P[0], P[1], P[2], P[3]}}) >= 0)
|
|||
|
|
{
|
|||
|
|
unsigned borrow = 0;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const unsigned __int128 need =
|
|||
|
|
static_cast<unsigned __int128>(P[i]) + borrow;
|
|||
|
|
const unsigned __int128 cur = a.d[i];
|
|||
|
|
if (cur >= need)
|
|||
|
|
{
|
|||
|
|
a.d[i] = static_cast<std::uint64_t>(cur - need);
|
|||
|
|
borrow = 0;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
a.d[i] = static_cast<std::uint64_t>(
|
|||
|
|
(cur + (static_cast<unsigned __int128>(1) << 64)) - need);
|
|||
|
|
borrow = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void u512_add_u64(std::uint64_t x[8], int limb, std::uint64_t v) noexcept
|
|||
|
|
{
|
|||
|
|
unsigned __int128 sum =
|
|||
|
|
static_cast<unsigned __int128>(x[limb]) + v;
|
|||
|
|
x[limb] = static_cast<std::uint64_t>(sum);
|
|||
|
|
unsigned __int128 carry = sum >> 64;
|
|||
|
|
for (int i = limb + 1; carry && i < 8; ++i)
|
|||
|
|
{
|
|||
|
|
sum = static_cast<unsigned __int128>(x[i]) + carry;
|
|||
|
|
x[i] = static_cast<std::uint64_t>(sum);
|
|||
|
|
carry = sum >> 64;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void u512_add_shifted(std::uint64_t x[8], const std::uint64_t h[4],
|
|||
|
|
int bit_shift) noexcept
|
|||
|
|
{
|
|||
|
|
const int limb = bit_shift >> 6;
|
|||
|
|
const int bit = bit_shift & 63;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
if (bit == 0)
|
|||
|
|
u512_add_u64(x, limb + i, h[i]);
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
u512_add_u64(x, limb + i,
|
|||
|
|
static_cast<std::uint64_t>(h[i] << bit));
|
|||
|
|
if (limb + i + 1 < 8)
|
|||
|
|
u512_add_u64(x, limb + i + 1, h[i] >> (64 - bit));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool u512_sub(std::uint64_t x[8], const std::uint64_t y[8]) noexcept
|
|||
|
|
{
|
|||
|
|
unsigned borrow = 0;
|
|||
|
|
for (int i = 0; i < 8; ++i)
|
|||
|
|
{
|
|||
|
|
const unsigned __int128 need =
|
|||
|
|
static_cast<unsigned __int128>(y[i]) + borrow;
|
|||
|
|
const unsigned __int128 cur = x[i];
|
|||
|
|
if (cur >= need)
|
|||
|
|
{
|
|||
|
|
x[i] = static_cast<std::uint64_t>(cur - need);
|
|||
|
|
borrow = 0;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
x[i] = static_cast<std::uint64_t>(
|
|||
|
|
(cur + (static_cast<unsigned __int128>(1) << 64)) - need);
|
|||
|
|
borrow = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return borrow != 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void u512_add_p_at(std::uint64_t x[8], int limb) noexcept
|
|||
|
|
{
|
|||
|
|
unsigned __int128 carry = 0;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
carry += static_cast<unsigned __int128>(x[limb + i]) + P[i];
|
|||
|
|
x[limb + i] = static_cast<std::uint64_t>(carry);
|
|||
|
|
carry >>= 64;
|
|||
|
|
}
|
|||
|
|
for (int i = limb + 4; carry && i < 8; ++i)
|
|||
|
|
{
|
|||
|
|
carry += x[i];
|
|||
|
|
x[i] = static_cast<std::uint64_t>(carry);
|
|||
|
|
carry >>= 64;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Fast NIST P-256 reduction of a 512-bit little-endian product.
|
|||
|
|
/// @details `2^256 ≡ 2^224 - 2^192 - 2^96 + 1 (mod p)`. Shifts are applied
|
|||
|
|
/// as full `__int128` limb writes so carry propagation, not a second
|
|||
|
|
/// half-limb store, moves the high bits.
|
|||
|
|
inline fe fe_reduce_wide(const std::uint64_t z[8]) noexcept
|
|||
|
|
{
|
|||
|
|
__int128 t[10]{};
|
|||
|
|
auto add = [&](int i, __int128 v) { t[i] += v; };
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
add(i, static_cast<__int128>(z[i]));
|
|||
|
|
add(i, static_cast<__int128>(z[4 + i]));
|
|||
|
|
}
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 h = static_cast<__int128>(z[4 + i]);
|
|||
|
|
// +H << 224 and -H << 192 and -H << 96 (carry moves overflow).
|
|||
|
|
add(i + 3, h << 32);
|
|||
|
|
add(i + 3, -h);
|
|||
|
|
add(i + 1, -(h << 32));
|
|||
|
|
}
|
|||
|
|
for (int pass = 0; pass < 6; ++pass)
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < 9; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 c = t[i] >> 64;
|
|||
|
|
t[i] = static_cast<std::uint64_t>(t[i]);
|
|||
|
|
t[i + 1] += c;
|
|||
|
|
}
|
|||
|
|
if (t[4] == 0 && t[5] == 0 && t[6] == 0 && t[7] == 0
|
|||
|
|
&& t[8] == 0 && t[9] == 0)
|
|||
|
|
break;
|
|||
|
|
__int128 high[6] = {t[4], t[5], t[6], t[7], t[8], t[9]};
|
|||
|
|
t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = 0;
|
|||
|
|
for (int i = 0; i < 6; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 h = high[i];
|
|||
|
|
if (h == 0)
|
|||
|
|
continue;
|
|||
|
|
// h * 2^{256+64*i} ≡ h*2^{64*i} * (1+2^224-2^192-2^96)
|
|||
|
|
add(i, h);
|
|||
|
|
add(i + 3, h << 32);
|
|||
|
|
add(i + 3, -h);
|
|||
|
|
add(i + 1, -(h << 32));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 c = t[i] >> 64;
|
|||
|
|
t[i] = static_cast<std::uint64_t>(t[i]);
|
|||
|
|
t[i + 1] += c;
|
|||
|
|
}
|
|||
|
|
for (int guard = 0; guard < 6; ++guard)
|
|||
|
|
{
|
|||
|
|
if (t[4] == 0 && t[0] >= 0 && t[1] >= 0 && t[2] >= 0 && t[3] >= 0)
|
|||
|
|
break;
|
|||
|
|
if (t[4] != 0 || t[5] != 0)
|
|||
|
|
{
|
|||
|
|
__int128 high[2] = {t[4], t[5]};
|
|||
|
|
t[4] = t[5] = 0;
|
|||
|
|
for (int i = 0; i < 2; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 h = high[i];
|
|||
|
|
if (h == 0)
|
|||
|
|
continue;
|
|||
|
|
add(i, h);
|
|||
|
|
add(i + 3, h << 32);
|
|||
|
|
add(i + 3, -h);
|
|||
|
|
add(i + 1, -(h << 32));
|
|||
|
|
}
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 c = t[i] >> 64;
|
|||
|
|
t[i] = static_cast<std::uint64_t>(t[i]);
|
|||
|
|
t[i + 1] += c;
|
|||
|
|
}
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
add(0, static_cast<__int128>(P[0]));
|
|||
|
|
add(1, static_cast<__int128>(P[1]));
|
|||
|
|
add(2, static_cast<__int128>(P[2]));
|
|||
|
|
add(3, static_cast<__int128>(P[3]));
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const __int128 c = t[i] >> 64;
|
|||
|
|
t[i] = static_cast<std::uint64_t>(t[i]);
|
|||
|
|
t[i + 1] += c;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
fe out{{static_cast<std::uint64_t>(t[0]),
|
|||
|
|
static_cast<std::uint64_t>(t[1]),
|
|||
|
|
static_cast<std::uint64_t>(t[2]),
|
|||
|
|
static_cast<std::uint64_t>(t[3])}};
|
|||
|
|
out = fe_sub_p_if_ge(out);
|
|||
|
|
out = fe_sub_p_if_ge(out);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_add(fe a, fe b) noexcept
|
|||
|
|
{
|
|||
|
|
unsigned __int128 carry = 0;
|
|||
|
|
fe r{};
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
carry += static_cast<unsigned __int128>(a.d[i]) + b.d[i];
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(carry);
|
|||
|
|
carry >>= 64;
|
|||
|
|
}
|
|||
|
|
if (carry)
|
|||
|
|
{
|
|||
|
|
std::uint64_t z[8] = {r.d[0], r.d[1], r.d[2], r.d[3], 1, 0, 0, 0};
|
|||
|
|
return fe_reduce_wide(z);
|
|||
|
|
}
|
|||
|
|
return fe_sub_p_if_ge(r);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_neg(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
if (fe_is_zero(a))
|
|||
|
|
return a;
|
|||
|
|
fe r{};
|
|||
|
|
unsigned borrow = 0;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const unsigned __int128 need =
|
|||
|
|
static_cast<unsigned __int128>(a.d[i]) + borrow;
|
|||
|
|
const unsigned __int128 cur = P[i];
|
|||
|
|
if (cur >= need)
|
|||
|
|
{
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(cur - need);
|
|||
|
|
borrow = 0;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(
|
|||
|
|
(cur + (static_cast<unsigned __int128>(1) << 64)) - need);
|
|||
|
|
borrow = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return r;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_sub(fe a, fe b) noexcept
|
|||
|
|
{
|
|||
|
|
fe r{};
|
|||
|
|
unsigned borrow = 0;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const unsigned __int128 need =
|
|||
|
|
static_cast<unsigned __int128>(b.d[i]) + borrow;
|
|||
|
|
const unsigned __int128 cur = a.d[i];
|
|||
|
|
if (cur >= need)
|
|||
|
|
{
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(cur - need);
|
|||
|
|
borrow = 0;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(
|
|||
|
|
(cur + (static_cast<unsigned __int128>(1) << 64)) - need);
|
|||
|
|
borrow = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (borrow)
|
|||
|
|
{
|
|||
|
|
unsigned __int128 carry = 0;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
carry += static_cast<unsigned __int128>(r.d[i]) + P[i];
|
|||
|
|
r.d[i] = static_cast<std::uint64_t>(carry);
|
|||
|
|
carry >>= 64;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return r;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_mul(fe a, fe b) noexcept
|
|||
|
|
{
|
|||
|
|
std::uint64_t z[8]{};
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
unsigned __int128 carry = 0;
|
|||
|
|
for (int j = 0; j < 4; ++j)
|
|||
|
|
{
|
|||
|
|
const unsigned __int128 cur =
|
|||
|
|
static_cast<unsigned __int128>(z[i + j])
|
|||
|
|
+ static_cast<unsigned __int128>(a.d[i]) * b.d[j]
|
|||
|
|
+ carry;
|
|||
|
|
z[i + j] = static_cast<std::uint64_t>(cur);
|
|||
|
|
carry = cur >> 64;
|
|||
|
|
}
|
|||
|
|
z[i + 4] = static_cast<std::uint64_t>(carry);
|
|||
|
|
}
|
|||
|
|
return fe_reduce_wide(z);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_pow(fe a, const std::uint64_t exp[4]) noexcept
|
|||
|
|
{
|
|||
|
|
fe result{{1, 0, 0, 0}};
|
|||
|
|
for (int bit = 255; bit >= 0; --bit)
|
|||
|
|
{
|
|||
|
|
result = fe_mul(result, result);
|
|||
|
|
const unsigned limb = static_cast<unsigned>(bit >> 6);
|
|||
|
|
const unsigned off = static_cast<unsigned>(bit & 63);
|
|||
|
|
if ((exp[limb] >> off) & 1u)
|
|||
|
|
result = fe_mul(result, a);
|
|||
|
|
}
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool fe_is_square(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
if (fe_is_zero(a))
|
|||
|
|
return true;
|
|||
|
|
const fe leg = fe_pow(a, EXP_P_MINUS_1_OVER_2);
|
|||
|
|
return leg.d[0] == 1 && leg.d[1] == 0 && leg.d[2] == 0 && leg.d[3] == 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_sqrt(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
return fe_pow(a, EXP_P_PLUS_1_OVER_4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_inv(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
return fe_pow(a, EXP_P_MINUS_2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe curve_rhs(fe x) noexcept
|
|||
|
|
{
|
|||
|
|
const fe x2 = fe_mul(x, x);
|
|||
|
|
const fe x3 = fe_mul(x2, x);
|
|||
|
|
const fe three = fe_add(x, fe_add(x, x));
|
|||
|
|
fe rhs = fe_sub(x3, three);
|
|||
|
|
fe b{{B[0], B[1], B[2], B[3]}};
|
|||
|
|
return fe_add(rhs, b);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void fe_from_be(fe &out, const std::uint8_t be[32]) noexcept
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
std::uint64_t v = 0;
|
|||
|
|
for (int j = 0; j < 8; ++j)
|
|||
|
|
v = (v << 8) | be[i * 8 + j];
|
|||
|
|
out.d[3 - i] = v;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void fe_to_be(std::uint8_t be[32], const fe &a) noexcept
|
|||
|
|
{
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
const std::uint64_t v = a.d[3 - i];
|
|||
|
|
for (int j = 0; j < 8; ++j)
|
|||
|
|
be[i * 8 + j] = static_cast<std::uint8_t>(v >> (56 - 8 * j));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine generator_point() noexcept
|
|||
|
|
{
|
|||
|
|
affine g;
|
|||
|
|
g.inf = false;
|
|||
|
|
for (int i = 0; i < 4; ++i)
|
|||
|
|
{
|
|||
|
|
g.x.d[i] = GX[i];
|
|||
|
|
g.y.d[i] = GY[i];
|
|||
|
|
}
|
|||
|
|
return g;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine point_neg(affine a) noexcept
|
|||
|
|
{
|
|||
|
|
if (!a.inf)
|
|||
|
|
a.y = fe_neg(a.y);
|
|||
|
|
return a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
fe fe_sqr(fe a) noexcept
|
|||
|
|
{
|
|||
|
|
return fe_mul(a, a);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Jacobian coordinates `(X : Y : Z)` for `x=X/Z^2`, `y=Y/Z^3`.
|
|||
|
|
struct jacobian
|
|||
|
|
{
|
|||
|
|
fe X{};
|
|||
|
|
fe Y{};
|
|||
|
|
fe Z{};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool jac_is_inf(const jacobian &p) noexcept
|
|||
|
|
{
|
|||
|
|
return fe_is_zero(p.Z);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
jacobian jac_from_affine(affine a) noexcept
|
|||
|
|
{
|
|||
|
|
if (a.inf)
|
|||
|
|
return {};
|
|||
|
|
return jacobian{a.x, a.y, fe{{1, 0, 0, 0}}};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine jac_to_affine(jacobian p) noexcept
|
|||
|
|
{
|
|||
|
|
if (jac_is_inf(p))
|
|||
|
|
return {};
|
|||
|
|
const fe zi = fe_inv(p.Z);
|
|||
|
|
const fe zi2 = fe_sqr(zi);
|
|||
|
|
const fe zi3 = fe_mul(zi2, zi);
|
|||
|
|
affine r;
|
|||
|
|
r.inf = false;
|
|||
|
|
r.x = fe_mul(p.X, zi2);
|
|||
|
|
r.y = fe_mul(p.Y, zi3);
|
|||
|
|
return r;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Jacobian doubling for short-Weierstrass with `a = -3` (P-256).
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
jacobian jac_double(jacobian p) noexcept
|
|||
|
|
{
|
|||
|
|
if (jac_is_inf(p) || fe_is_zero(p.Y))
|
|||
|
|
return {};
|
|||
|
|
const fe yy = fe_sqr(p.Y);
|
|||
|
|
const fe yyyy = fe_sqr(yy);
|
|||
|
|
const fe zz = fe_sqr(p.Z);
|
|||
|
|
// S = 4*X*Y^2
|
|||
|
|
const fe s = fe_mul(fe_add(p.X, p.X), fe_add(yy, yy));
|
|||
|
|
// M = 3*(X+Z^2)*(X-Z^2)
|
|||
|
|
const fe xz = fe_add(p.X, zz);
|
|||
|
|
const fe xd = fe_sub(p.X, zz);
|
|||
|
|
const fe m = fe_mul(fe_add(xd, fe_add(xd, xd)), xz);
|
|||
|
|
const fe m2 = fe_sqr(m);
|
|||
|
|
jacobian r;
|
|||
|
|
r.X = fe_sub(m2, fe_add(s, s));
|
|||
|
|
// 8*Y^4
|
|||
|
|
fe eight_yyyy = fe_add(yyyy, yyyy);
|
|||
|
|
eight_yyyy = fe_add(eight_yyyy, eight_yyyy);
|
|||
|
|
eight_yyyy = fe_add(eight_yyyy, eight_yyyy);
|
|||
|
|
r.Y = fe_sub(fe_mul(m, fe_sub(s, r.X)), eight_yyyy);
|
|||
|
|
// Z3 = 2*Y*Z = (Y+Z)^2 - Y^2 - Z^2
|
|||
|
|
r.Z = fe_sub(fe_sub(fe_sqr(fe_add(p.Y, p.Z)), yy), zz);
|
|||
|
|
return r;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Mixed Jacobian–affine addition.
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
jacobian jac_add_mixed(jacobian p, affine q) noexcept
|
|||
|
|
{
|
|||
|
|
if (q.inf)
|
|||
|
|
return p;
|
|||
|
|
if (jac_is_inf(p))
|
|||
|
|
return jac_from_affine(q);
|
|||
|
|
const fe z1z1 = fe_sqr(p.Z);
|
|||
|
|
const fe u2 = fe_mul(q.x, z1z1);
|
|||
|
|
const fe s2 = fe_mul(q.y, fe_mul(p.Z, z1z1));
|
|||
|
|
const fe h = fe_sub(u2, p.X);
|
|||
|
|
const fe r = fe_sub(s2, p.Y);
|
|||
|
|
if (fe_is_zero(h))
|
|||
|
|
{
|
|||
|
|
if (fe_is_zero(r))
|
|||
|
|
return jac_double(p);
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
const fe h2 = fe_sqr(h);
|
|||
|
|
const fe h3 = fe_mul(h2, h);
|
|||
|
|
const fe v = fe_mul(p.X, h2);
|
|||
|
|
const fe r2 = fe_sqr(r);
|
|||
|
|
jacobian out;
|
|||
|
|
out.X = fe_sub(fe_sub(r2, h3), fe_add(v, v));
|
|||
|
|
out.Y = fe_sub(fe_mul(r, fe_sub(v, out.X)), fe_mul(p.Y, h3));
|
|||
|
|
out.Z = fe_mul(h, p.Z);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine point_add(affine a, affine b) noexcept
|
|||
|
|
{
|
|||
|
|
if (a.inf)
|
|||
|
|
return b;
|
|||
|
|
if (b.inf)
|
|||
|
|
return a;
|
|||
|
|
// One mixed add + one inversion beats two affine inversions when both
|
|||
|
|
// inputs are finite; keep the classical path for the equal-x branch.
|
|||
|
|
if (fe_cmp(a.x, b.x) == 0)
|
|||
|
|
{
|
|||
|
|
if (fe_cmp(a.y, b.y) != 0 || fe_is_zero(a.y))
|
|||
|
|
return {};
|
|||
|
|
return jac_to_affine(jac_double(jac_from_affine(a)));
|
|||
|
|
}
|
|||
|
|
return jac_to_affine(jac_add_mixed(jac_from_affine(a), b));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine point_sub(affine a, affine b) noexcept
|
|||
|
|
{
|
|||
|
|
return point_add(a, point_neg(b));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine point_scalarmul_limbs(affine g, const std::uint64_t k[4]) noexcept
|
|||
|
|
{
|
|||
|
|
if (g.inf)
|
|||
|
|
return {};
|
|||
|
|
// 4-bit window with an affine odd/even table. Doublings stay in Jacobian
|
|||
|
|
// so the ladder uses one field inversion (final map) instead of one per add.
|
|||
|
|
affine table[16]{};
|
|||
|
|
table[1] = g;
|
|||
|
|
const affine two = jac_to_affine(jac_double(jac_from_affine(g)));
|
|||
|
|
table[2] = two;
|
|||
|
|
for (int i = 3; i < 16; ++i)
|
|||
|
|
table[i] = jac_to_affine(jac_add_mixed(jac_from_affine(table[i - 1]), g));
|
|||
|
|
jacobian r{};
|
|||
|
|
bool started = false;
|
|||
|
|
for (int bit = 255; bit >= 0; bit -= 4)
|
|||
|
|
{
|
|||
|
|
if (started)
|
|||
|
|
{
|
|||
|
|
r = jac_double(r);
|
|||
|
|
r = jac_double(r);
|
|||
|
|
r = jac_double(r);
|
|||
|
|
r = jac_double(r);
|
|||
|
|
}
|
|||
|
|
unsigned nibble = 0;
|
|||
|
|
for (int j = 0; j < 4; ++j)
|
|||
|
|
{
|
|||
|
|
const int b = bit - j;
|
|||
|
|
if (b < 0)
|
|||
|
|
break;
|
|||
|
|
const unsigned limb = static_cast<unsigned>(b >> 6);
|
|||
|
|
const unsigned off = static_cast<unsigned>(b & 63);
|
|||
|
|
nibble <<= 1;
|
|||
|
|
if ((k[limb] >> off) & 1u)
|
|||
|
|
nibble |= 1u;
|
|||
|
|
}
|
|||
|
|
if (nibble != 0)
|
|||
|
|
{
|
|||
|
|
r = jac_add_mixed(r, table[nibble]);
|
|||
|
|
started = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return jac_to_affine(r);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine point_scalarmul(affine g, unsigned __int128 k) noexcept
|
|||
|
|
{
|
|||
|
|
std::uint64_t limbs[4] = {
|
|||
|
|
static_cast<std::uint64_t>(k),
|
|||
|
|
static_cast<std::uint64_t>(k >> 64),
|
|||
|
|
0, 0};
|
|||
|
|
return point_scalarmul_limbs(g, limbs);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Expand `src` to at least 256 bits via SHA-256(src ‖ counter).
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void stretch_seed(std::uint8_t *dst, std::size_t dst_n,
|
|||
|
|
const void *src, std::size_t src_n) noexcept
|
|||
|
|
{
|
|||
|
|
std::size_t off = 0;
|
|||
|
|
std::uint32_t counter = 0;
|
|||
|
|
while (off < dst_n)
|
|||
|
|
{
|
|||
|
|
class SHA256 h;
|
|||
|
|
h.add(src, src_n);
|
|||
|
|
const unsigned char ctr[4] = {
|
|||
|
|
static_cast<unsigned char>(counter),
|
|||
|
|
static_cast<unsigned char>(counter >> 8),
|
|||
|
|
static_cast<unsigned char>(counter >> 16),
|
|||
|
|
static_cast<unsigned char>(counter >> 24)};
|
|||
|
|
h.add(ctr, sizeof(ctr));
|
|||
|
|
unsigned char block[SHA256::HashBytes];
|
|||
|
|
h.getHash(block);
|
|||
|
|
const std::size_t take =
|
|||
|
|
dst_n - off < sizeof(block) ? dst_n - off : sizeof(block);
|
|||
|
|
std::memcpy(dst + off, block, take);
|
|||
|
|
off += take;
|
|||
|
|
++counter;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void hash_to_curve(const std::uint8_t *data, std::size_t n, affine &out) noexcept
|
|||
|
|
{
|
|||
|
|
std::uint8_t be[32]{};
|
|||
|
|
const std::size_t take = n < 32 ? n : 32;
|
|||
|
|
std::memcpy(be, data, take);
|
|||
|
|
for (std::size_t i = 32; i < n; ++i)
|
|||
|
|
be[i & 31] ^= data[i];
|
|||
|
|
fe x;
|
|||
|
|
fe_from_be(x, be);
|
|||
|
|
fe p{{P[0], P[1], P[2], P[3]}};
|
|||
|
|
if (fe_cmp(x, p) >= 0)
|
|||
|
|
x = fe_sub(x, p);
|
|||
|
|
const fe one{{1, 0, 0, 0}};
|
|||
|
|
for (;;)
|
|||
|
|
{
|
|||
|
|
const fe rhs = curve_rhs(x);
|
|||
|
|
if (fe_is_square(rhs))
|
|||
|
|
{
|
|||
|
|
fe y = fe_sqrt(rhs);
|
|||
|
|
if ((y.d[0] & 1u) != 0)
|
|||
|
|
y = fe_neg(y);
|
|||
|
|
out.inf = false;
|
|||
|
|
out.x = x;
|
|||
|
|
out.y = y;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
x = fe_add(x, one);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool decode_compressed(const std::uint8_t enc[33], affine &out) noexcept
|
|||
|
|
{
|
|||
|
|
bool zeros = true;
|
|||
|
|
for (int i = 0; i < 33; ++i)
|
|||
|
|
zeros = zeros && enc[i] == 0;
|
|||
|
|
if (zeros)
|
|||
|
|
{
|
|||
|
|
out = affine{};
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
if (enc[0] != 0x02 && enc[0] != 0x03)
|
|||
|
|
return false;
|
|||
|
|
fe x;
|
|||
|
|
fe_from_be(x, enc + 1);
|
|||
|
|
fe p{{P[0], P[1], P[2], P[3]}};
|
|||
|
|
if (fe_cmp(x, p) >= 0)
|
|||
|
|
return false;
|
|||
|
|
const fe rhs = curve_rhs(x);
|
|||
|
|
if (!fe_is_square(rhs))
|
|||
|
|
return false;
|
|||
|
|
fe y = fe_sqrt(rhs);
|
|||
|
|
const bool odd = (y.d[0] & 1u) != 0;
|
|||
|
|
if (odd != (enc[0] == 0x03))
|
|||
|
|
y = fe_neg(y);
|
|||
|
|
if (fe_cmp(fe_mul(y, y), rhs) != 0)
|
|||
|
|
return false;
|
|||
|
|
out.inf = false;
|
|||
|
|
out.x = x;
|
|||
|
|
out.y = y;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Decode a stored leaf/share encoding. Invalid claimings throw.
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
affine decode_strict(const std::uint8_t *bytes, std::size_t n)
|
|||
|
|
{
|
|||
|
|
std::uint8_t enc[33]{};
|
|||
|
|
const std::size_t take = n < 33 ? n : 33;
|
|||
|
|
std::memcpy(enc, bytes, take);
|
|||
|
|
affine out;
|
|||
|
|
if (!decode_compressed(enc, out))
|
|||
|
|
throw std::invalid_argument("p256: not a P-256 point");
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
void encode_point(std::uint8_t enc[33], affine p) noexcept
|
|||
|
|
{
|
|||
|
|
std::memset(enc, 0, 33);
|
|||
|
|
if (p.inf)
|
|||
|
|
return;
|
|||
|
|
enc[0] = (p.y.d[0] & 1u) ? 0x03 : 0x02;
|
|||
|
|
fe_to_be(enc + 1, p.x);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace p256_detail
|
|||
|
|
|
|||
|
|
/// @brief NIST P-256 point, SEC1-compressed in 33 bytes.
|
|||
|
|
class p256
|
|||
|
|
{
|
|||
|
|
public:
|
|||
|
|
static constexpr std::size_t encoded_size = 33;
|
|||
|
|
static constexpr bool dpf_point_group = true;
|
|||
|
|
/// @brief Leaf PRG bytes are mapped through `from_seed` before storage.
|
|||
|
|
static constexpr bool dpf_curve_point = true;
|
|||
|
|
|
|||
|
|
/// @brief The identity (point at infinity).
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
constexpr p256() noexcept = default;
|
|||
|
|
|
|||
|
|
/// @brief `scalar` times the generator. A negative scalar negates the point.
|
|||
|
|
/// @tparam T integral scalar, at most 128 bits
|
|||
|
|
/// @param scalar the multiplier of the generator
|
|||
|
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
|||
|
|
explicit p256(T scalar)
|
|||
|
|
{
|
|||
|
|
bool neg = false;
|
|||
|
|
unsigned __int128 mag = 0;
|
|||
|
|
if constexpr (std::is_signed_v<T>)
|
|||
|
|
{
|
|||
|
|
if (scalar < 0)
|
|||
|
|
{
|
|||
|
|
neg = true;
|
|||
|
|
using U = std::make_unsigned_t<T>;
|
|||
|
|
mag = static_cast<U>(0) - static_cast<U>(scalar);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
mag = static_cast<std::make_unsigned_t<T>>(scalar);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
mag = static_cast<unsigned __int128>(scalar);
|
|||
|
|
}
|
|||
|
|
auto point = p256_detail::point_scalarmul(
|
|||
|
|
p256_detail::generator_point(), mag);
|
|||
|
|
if (neg)
|
|||
|
|
point = p256_detail::point_neg(point);
|
|||
|
|
p256_detail::encode_point(enc_, point);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief The conventional generator.
|
|||
|
|
/// @return the generator
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
static p256 generator()
|
|||
|
|
{
|
|||
|
|
p256 g;
|
|||
|
|
p256_detail::encode_point(g.enc_, p256_detail::generator_point());
|
|||
|
|
return g;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Decode a 33-byte SEC1 compressed point. 33 zero bytes are the identity.
|
|||
|
|
/// @param in the compressed encoding
|
|||
|
|
/// @return the point
|
|||
|
|
static p256 from_compressed(const std::uint8_t (&in)[encoded_size])
|
|||
|
|
{
|
|||
|
|
p256_detail::affine point;
|
|||
|
|
if (!p256_detail::decode_compressed(in, point))
|
|||
|
|
throw std::invalid_argument("p256::from_compressed: not a P-256 point");
|
|||
|
|
p256 out;
|
|||
|
|
p256_detail::encode_point(out.enc_, point);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Decode a 33-byte SEC1 compressed point.
|
|||
|
|
/// @param in the compressed encoding
|
|||
|
|
/// @return the point
|
|||
|
|
static p256 from_compressed(const std::array<std::uint8_t, encoded_size> &in)
|
|||
|
|
{
|
|||
|
|
std::uint8_t raw[encoded_size];
|
|||
|
|
std::memcpy(raw, in.data(), encoded_size);
|
|||
|
|
return from_compressed(raw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Map a PRG block to a curve point.
|
|||
|
|
/// @details Stretches the seed to at least 256 bits, then hashes onto the
|
|||
|
|
/// curve. Leaf and arithmetic paths require a valid encoding;
|
|||
|
|
/// use this for masks, not for decoding peer points.
|
|||
|
|
/// @param bytes the PRG output
|
|||
|
|
/// @param n the number of bytes available
|
|||
|
|
/// @return the curve point
|
|||
|
|
static p256 from_seed(const void * bytes, std::size_t n) noexcept
|
|||
|
|
{
|
|||
|
|
std::uint8_t wide[64]{};
|
|||
|
|
p256_detail::stretch_seed(wide, sizeof(wide), bytes, n);
|
|||
|
|
p256_detail::affine point;
|
|||
|
|
p256_detail::hash_to_curve(wide, sizeof(wide), point);
|
|||
|
|
p256 out;
|
|||
|
|
p256_detail::encode_point(out.enc_, point);
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief The 33-byte encoding.
|
|||
|
|
/// @return the encoding
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
const std::uint8_t *bytes() const noexcept { return enc_; }
|
|||
|
|
|
|||
|
|
/// @brief True for the identity.
|
|||
|
|
/// @return whether this is the identity
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
bool is_identity() const noexcept
|
|||
|
|
{
|
|||
|
|
std::uint8_t z = 0;
|
|||
|
|
for (std::size_t i = 0; i < encoded_size; ++i)
|
|||
|
|
z |= enc_[i];
|
|||
|
|
return z == 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Point addition.
|
|||
|
|
/// @param a left addend
|
|||
|
|
/// @param b right addend
|
|||
|
|
/// @return `a + b` on the curve
|
|||
|
|
friend p256 operator+(p256 a, p256 b)
|
|||
|
|
{
|
|||
|
|
const auto pa = p256_detail::decode_strict(a.enc_, encoded_size);
|
|||
|
|
const auto pb = p256_detail::decode_strict(b.enc_, encoded_size);
|
|||
|
|
p256 out;
|
|||
|
|
p256_detail::encode_point(out.enc_, p256_detail::point_add(pa, pb));
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Point negation.
|
|||
|
|
/// @param a the point to negate
|
|||
|
|
/// @return `-a`
|
|||
|
|
friend p256 operator-(p256 a)
|
|||
|
|
{
|
|||
|
|
const auto pa = p256_detail::decode_strict(a.enc_, encoded_size);
|
|||
|
|
p256 out;
|
|||
|
|
p256_detail::encode_point(out.enc_, p256_detail::point_neg(pa));
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Point subtraction.
|
|||
|
|
/// @param a minuend
|
|||
|
|
/// @param b subtrahend
|
|||
|
|
/// @return `a - b` on the curve
|
|||
|
|
friend p256 operator-(p256 a, p256 b)
|
|||
|
|
{
|
|||
|
|
return a + (-b);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Encoding equality.
|
|||
|
|
/// @param a left point
|
|||
|
|
/// @param b right point
|
|||
|
|
/// @return `true` when the encodings match
|
|||
|
|
friend bool operator==(p256 a, p256 b) noexcept
|
|||
|
|
{
|
|||
|
|
return std::memcmp(a.enc_, b.enc_, encoded_size) == 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Encoding inequality.
|
|||
|
|
/// @param a left point
|
|||
|
|
/// @param b right point
|
|||
|
|
/// @return `true` when the encodings differ
|
|||
|
|
friend bool operator!=(p256 a, p256 b) noexcept
|
|||
|
|
{
|
|||
|
|
return !(a == b);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// @brief Write the 33-byte encoding in hexadecimal.
|
|||
|
|
/// @param os the output stream
|
|||
|
|
/// @param a the point to write
|
|||
|
|
/// @return `os`
|
|||
|
|
friend std::ostream & operator<<(std::ostream &os, p256 a)
|
|||
|
|
{
|
|||
|
|
const auto flags = os.flags();
|
|||
|
|
os << std::hex;
|
|||
|
|
for (std::size_t i = 0; i < encoded_size; ++i)
|
|||
|
|
{
|
|||
|
|
const unsigned byte = a.enc_[i];
|
|||
|
|
os << "0123456789abcdef"[byte >> 4] << "0123456789abcdef"[byte & 0xf];
|
|||
|
|
}
|
|||
|
|
os.flags(flags);
|
|||
|
|
return os;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
std::uint8_t enc_[encoded_size]{};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
namespace leaf_arithmetic
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
namespace detail
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
template <typename Leaf>
|
|||
|
|
HEDLEY_ALWAYS_INLINE
|
|||
|
|
Leaf p256_leaf_combine(const Leaf &a, const Leaf &b, bool subtract)
|
|||
|
|
{
|
|||
|
|
unsigned char ab[sizeof(Leaf)];
|
|||
|
|
unsigned char bb[sizeof(Leaf)];
|
|||
|
|
unsigned char cb[sizeof(Leaf)];
|
|||
|
|
std::memcpy(ab, &a, sizeof(Leaf));
|
|||
|
|
std::memcpy(bb, &b, sizeof(Leaf));
|
|||
|
|
const auto pa = p256_detail::decode_strict(ab, sizeof(Leaf));
|
|||
|
|
const auto pb = p256_detail::decode_strict(bb, sizeof(Leaf));
|
|||
|
|
const auto pc = subtract ? p256_detail::point_sub(pa, pb)
|
|||
|
|
: p256_detail::point_add(pa, pb);
|
|||
|
|
std::memset(cb, 0, sizeof(cb));
|
|||
|
|
p256_detail::encode_point(cb, pc);
|
|||
|
|
Leaf out{};
|
|||
|
|
std::memcpy(&out, cb, sizeof(Leaf));
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace detail
|
|||
|
|
|
|||
|
|
template <typename NodeT, std::size_t N>
|
|||
|
|
struct add_t<p256, std::array<NodeT, N>>
|
|||
|
|
{
|
|||
|
|
auto operator()(const std::array<NodeT, N> &a,
|
|||
|
|
const std::array<NodeT, N> &b) const
|
|||
|
|
{
|
|||
|
|
return detail::p256_leaf_combine(a, b, false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
template <typename NodeT, std::size_t N>
|
|||
|
|
struct subtract_t<p256, std::array<NodeT, N>>
|
|||
|
|
{
|
|||
|
|
auto operator()(const std::array<NodeT, N> &a,
|
|||
|
|
const std::array<NodeT, N> &b) const
|
|||
|
|
{
|
|||
|
|
return detail::p256_leaf_combine(a, b, true);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
template <typename NodeT, std::size_t N>
|
|||
|
|
struct multiply_t<p256, std::array<NodeT, N>>
|
|||
|
|
{
|
|||
|
|
template <typename Leaf>
|
|||
|
|
Leaf operator()(const Leaf &, p256) const
|
|||
|
|
{
|
|||
|
|
static_assert(sizeof(NodeT) == 0,
|
|||
|
|
"p256 has no point product; p256{k} is k times the generator");
|
|||
|
|
return Leaf{};
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
} // namespace leaf_arithmetic
|
|||
|
|
|
|||
|
|
/// @brief Sample a uniform curve point by rejection-sampling a scalar in `[0, n)`.
|
|||
|
|
/// @return a curve point
|
|||
|
|
template <>
|
|||
|
|
HEDLEY_NO_THROW
|
|||
|
|
inline auto uniform_sample<p256>() noexcept
|
|||
|
|
{
|
|||
|
|
std::uint64_t k[4]{};
|
|||
|
|
for (;;)
|
|||
|
|
{
|
|||
|
|
k[0] = uniform_sample<std::uint64_t>();
|
|||
|
|
k[1] = uniform_sample<std::uint64_t>();
|
|||
|
|
k[2] = uniform_sample<std::uint64_t>();
|
|||
|
|
k[3] = uniform_sample<std::uint64_t>();
|
|||
|
|
if (p256_detail::limbs_cmp(k, p256_detail::N, 4) < 0)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
const auto point = p256_detail::point_scalarmul_limbs(
|
|||
|
|
p256_detail::generator_point(), k);
|
|||
|
|
if (point.inf)
|
|||
|
|
return p256{};
|
|||
|
|
unsigned char enc[p256::encoded_size];
|
|||
|
|
p256_detail::encode_point(enc, point);
|
|||
|
|
return p256::from_compressed(enc);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace dpf
|
|||
|
|
|
|||
|
|
#endif // LIBDPF_INCLUDE_DPF_P256_HPP__
|