UltrafastSecp256k1 3.50.0
Ultra high-performance secp256k1 elliptic curve cryptography library
Loading...
Searching...
No Matches
field_52.hpp
Go to the documentation of this file.
1#ifndef SECP256K1_FIELD_52_HPP
2#define SECP256K1_FIELD_52_HPP
3#pragma once
4
5// ============================================================================
6// 5x52-bit Field Element for secp256k1 (Hybrid Lazy-Reduction Scheme)
7// ============================================================================
8//
9// Alternative representation: 5 limbs x 52 bits each in uint64_t[5].
10// The upper 12 bits per limb provide "headroom" for lazy reduction:
11//
12// Addition = 5 plain adds (NO carry propagation!)
13// Sub = 5 adds (pre-add 2p to avoid underflow, NO borrow!)
14// Mul/Sqr = native 5x52 with inline secp256k1 reduction
15//
16// Headroom budget: 12 bits -> up to 4096 additions without normalization.
17// In practice, ECC point operations need <=50 chained additions.
18//
19// Hybrid approach: convert between FieldElement (4x64) <-> FieldElement52 (5x52)
20// to use whichever representation is optimal for each code path.
21//
22// FieldElement (4x64) -> optimal for: scalar multiply, serialization, I/O
23// FieldElement52 (5x52) -> optimal for: ECC point ops (add-heavy chains)
24//
25// Based on bitcoin-core/secp256k1 field_5x52 representation.
26// Multiplication/squaring adapted from bitcoin-core's field_5x52_int128_impl.h
27// (MIT license, Copyright (c) 2013-2024 Pieter Wuille and contributors)
28// ============================================================================
29
30#include <cstdint>
31#include <array>
32#include "secp256k1/field.hpp"
33
34namespace secp256k1::fast {
35
36// -- Constants ----------------------------------------------------------------
37namespace fe52_constants {
38 // Mask for 52 bits
39 constexpr std::uint64_t M52 = 0xFFFFFFFFFFFFFULL; // (1 << 52) - 1
40 // Mask for 48 bits (top limb)
41 constexpr std::uint64_t M48 = 0xFFFFFFFFFFFFULL; // (1 << 48) - 1
42 // 2^260 mod p = 2^4 * (2^256 mod p) = 16 * 0x1000003D1 = 0x1000003D10
43 constexpr std::uint64_t R52 = 0x1000003D10ULL;
44
45 // p in 5x52 representation
46 // p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
47 constexpr std::uint64_t P0 = 0xFFFFEFFFFFC2FULL;
48 constexpr std::uint64_t P1 = 0xFFFFFFFFFFFFFULL;
49 constexpr std::uint64_t P2 = 0xFFFFFFFFFFFFFULL;
50 constexpr std::uint64_t P3 = 0xFFFFFFFFFFFFFULL;
51 constexpr std::uint64_t P4 = 0xFFFFFFFFFFFFULL; // 48 bits
52}
53
54// -- 5x52 Field Element -------------------------------------------------------
55struct alignas(8) FieldElement52 {
56 std::uint64_t n[5]; // Each limb holds <=52 bits when normalized (magnitude=1)
57
58 // -- Construction -------------------------------------------------
59 static FieldElement52 zero() noexcept;
60 static FieldElement52 one() noexcept;
61
62 // -- Conversion (4x64 <-> 5x52) ------------------------------------
63 //
64 // 4x64 bit layout: [bits 0..63] [64..127] [128..191] [192..255]
65 // 5x52 bit layout: [bits 0..51] [52..103] [104..155] [156..207] [208..255]
66 //
67 static FieldElement52 from_fe(const FieldElement& fe) noexcept;
68 FieldElement to_fe() const noexcept; // Normalizes first!
69
70 // Convenience: serialize FE52 -> bytes (BE) in one call.
71 // Replaces common pattern: to_fe().to_bytes_into(out)
72 void to_bytes_into(std::uint8_t* out) const noexcept;
73
74 // Fast serialize for pre-normalized limbs (z_one_ affine points).
75 // PRECONDITION: limbs must already be fully normalized (each <= 52 bits,
76 // value < p). Guaranteed by make_affine_inplace / from_affine / from_bytes.
77 // Skips fe52_normalize_inline for ~2x speedup on public key serialization.
78 void store_b32_prenorm(std::uint8_t* out) const noexcept;
79
80 // Direct 4x64 limbs -> 5x52 conversion (zero-copy, no FieldElement construction).
81 // Input: 4 little-endian uint64_t limbs representing a value < p.
82 // Use for Scalar->FE52 where we know value < n < p.
83 static FieldElement52 from_4x64_limbs(const std::uint64_t* limbs) noexcept;
84
85 // Direct bytes (big-endian) -> 5x52 conversion (no FieldElement construction).
86 // Reads 32 BE bytes, reduces mod p if needed, converts to 5x52 limbs.
87 // Replaces the common pattern: FE52::from_fe(FieldElement::from_bytes(data))
88 static FieldElement52 from_bytes(const std::array<std::uint8_t, 32>& bytes) noexcept;
89 static FieldElement52 from_bytes(const std::uint8_t* bytes) noexcept;
90
91 // Inverse via safegcd (4x64): FE52 -> FE(4x64) -> safegcd -> FE52
92 // Single call replaces the common pattern: from_fe(to_fe().inverse())
94
95 // -- Normalization ------------------------------------------------
96 // Weak: carry-propagate so each limb <= 52 bits, but result may be >= p
97 void normalize_weak() noexcept;
98 // Full: canonical result in [0, p)
99 void normalize() noexcept;
100
101 // -- Lazy Arithmetic (NO carry propagation!) ----------------------
102 // These just do 5 plain adds per operation. Caller is responsible
103 // for normalizing before limbs would exceed 64 bits (after ~4096 adds).
104
105 // add: result limb i equals sum of operand limbs (magnitude increases by 1)
106 FieldElement52 operator+(const FieldElement52& rhs) const noexcept;
107 void add_assign(const FieldElement52& rhs) noexcept;
108
109 // -- Negate -------------------------------------------------------
110 // Computes (magnitude+1)*p - a to ensure positive result.
111 // 'magnitude' must be >= the current maximum magnitude of this element.
112 FieldElement52 negate(unsigned magnitude) const noexcept;
113 void negate_assign(unsigned magnitude) noexcept;
114
115 // Branchless conditional negate (magnitude 1).
116 // sign_mask: 0 = keep, -1 = negate. No branch; compiles to XOR+AND.
117 void conditional_negate_assign(std::int32_t sign_mask) noexcept;
118
119 // -- Fully-Reduced Arithmetic -------------------------------------
120 // Multiplication and squaring produce normalized output (magnitude=1).
121 FieldElement52 operator*(const FieldElement52& rhs) const noexcept;
122 FieldElement52 square() const noexcept;
123
124 // In-place variants
125 void mul_assign(const FieldElement52& rhs) noexcept;
126 FieldElement52& operator*=(const FieldElement52& rhs) noexcept {
127 mul_assign(rhs);
128 return *this;
129 }
130 void square_inplace() noexcept;
131
132 // -- Comparison (requires normalized inputs!) ---------------------
133 bool is_zero() const noexcept;
134 bool operator==(const FieldElement52& rhs) const noexcept;
135
136 // -- Fast Variable-time Zero Check --------------------------------
137 // Checks if value reduces to zero mod p WITHOUT full normalization.
138 // Much cheaper than normalize()+is_zero() -- no copy, no canonical form.
139 // Variable-time: safe for non-secret values (point coordinates in ECC).
140 bool normalizes_to_zero() const noexcept;
141
142 // Variable-time zero check with early exit (verify hot path).
143 // Combined normalize_weak + zero check in a single pass.
144 // Safe for magnitudes up to ~4000. 99.99..% of calls exit after
145 // a single OR-reduction (non-zero fast path). Only values that
146 // happen to be 0 or p fall through to the full limb comparison.
147 bool normalizes_to_zero_var() const noexcept;
148
149 // -- Multiply by small integer (no carry propagation) ---------------
150 // Multiplies each limb by a (must be <= 32).
151 // Output magnitude = input_magnitude * a.
152 void mul_int_assign(std::uint32_t a) noexcept;
153
154 // -- Half ---------------------------------------------------------
155 // Computes a/2 mod p. Branchless.
156 FieldElement52 half() const noexcept;
157 void half_assign() noexcept;
158
159 // -- Inverse (Fermat) ---------------------------------------------
160 // a^(p-2) mod p. 255 squarings + 14 multiplications in native FE52.
161 // ~1.6us -- faster than binary GCD (~3us) or SafeGCD (~3-5us in 4x64).
162 FieldElement52 inverse() const noexcept;
163
164 // -- Square Root --------------------------------------------------
165 // a^((p+1)/4) mod p. 253 squarings + 13 multiplications in native FE52.
166 // Returns a candidate y such that y^2 == a (mod p). Caller must verify.
167 FieldElement52 sqrt() const noexcept;
168};
169
170// -- Free Functions ------------------------------------------------------------
171//
172// Hot-path functions (fe52_mul_inner, fe52_sqr_inner, fe52_normalize_weak,
173// fe52_normalize, from_fe, to_fe) are defined INLINE in field_52_impl.hpp
174// for zero call overhead.
175
176// Out-of-line fe52_normalize kept for backward compatibility with code
177// that calls it outside hot paths. Actual implementation is inline.
178void fe52_normalize(std::uint64_t* r) noexcept;
179
180// Compile-time layout verification
181static_assert(sizeof(FieldElement52) == 40, "FieldElement52 must be 40 bytes (5x8)");
182
183} // namespace secp256k1::fast
184
185// Inline implementations of all hot-path 5x52 operations.
186// Must come AFTER the FieldElement52 class definition.
187#include "secp256k1/field_52_impl.hpp"
188
189#endif // SECP256K1_FIELD_52_HPP
constexpr std::uint64_t R52
Definition field_52.hpp:43
constexpr std::uint64_t P1
Definition field_52.hpp:48
constexpr std::uint64_t P4
Definition field_52.hpp:51
constexpr std::uint64_t M48
Definition field_52.hpp:41
constexpr std::uint64_t M52
Definition field_52.hpp:39
constexpr std::uint64_t P3
Definition field_52.hpp:50
constexpr std::uint64_t P2
Definition field_52.hpp:49
constexpr std::uint64_t P0
Definition field_52.hpp:47
void fe52_normalize(std::uint64_t *r) noexcept
void add_assign(const FieldElement52 &rhs) noexcept
FieldElement52 inverse() const noexcept
static FieldElement52 from_fe(const FieldElement &fe) noexcept
void store_b32_prenorm(std::uint8_t *out) const noexcept
void mul_assign(const FieldElement52 &rhs) noexcept
bool is_zero() const noexcept
FieldElement52 inverse_safegcd() const noexcept
static FieldElement52 from_bytes(const std::array< std::uint8_t, 32 > &bytes) noexcept
FieldElement to_fe() const noexcept
void mul_int_assign(std::uint32_t a) noexcept
void negate_assign(unsigned magnitude) noexcept
bool normalizes_to_zero() const noexcept
static FieldElement52 from_4x64_limbs(const std::uint64_t *limbs) noexcept
static FieldElement52 zero() noexcept
void conditional_negate_assign(std::int32_t sign_mask) noexcept
FieldElement52 half() const noexcept
void to_bytes_into(std::uint8_t *out) const noexcept
FieldElement52 sqrt() const noexcept
bool normalizes_to_zero_var() const noexcept
FieldElement52 square() const noexcept
FieldElement52 negate(unsigned magnitude) const noexcept
static FieldElement52 one() noexcept