Skip to content

Commit 48fb332

Browse files
committed
RFC: Add enum ReprEndianness constant to PrimeField trait
1 parent ff3a785 commit 48fb332

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
- Feature Name: `ff_prime_field_repr_endianness`
2+
- Start Date: 2026-06-04
3+
- RFC PR: TBD
4+
- Tracking Issue: [zkcrypto/ff#155](https://github.com/zkcrypto/ff/issues/155)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Add an enum with variants for distinguishing little versus big endian to the `ff` crate, as well
10+
as an associated constant value of this type to the `PrimeField` trait, in order to handle cases
11+
where curves use a big endian rather than little endian scalar serialization.
12+
13+
# Motivation
14+
[motivation]: #motivation
15+
16+
Almost all the elliptic curve implementations maintained by the @RustCrypto organization use the
17+
SEC1 serialization for field elements, which is big endian.
18+
19+
This primarily becomes a problem when trying to combine scalars using a big endian serialization
20+
for `PrimeField::Repr` with the `group` crate's w-NAF implementation, which assumes a little endian
21+
ordering despite the documentation for `PrimeField::to_repr` stipulating that "The endianness of the
22+
byte representation is implementation-specific. Generic encodings of field elements should be
23+
treated as opaque."
24+
25+
Downstream consumers of `PrimeField::Repr`, including the `group` crate, need to instead handle it
26+
in an endian-aware manner.
27+
28+
# Guide-level explanation
29+
[guide-level-explanation]: #guide-level-explanation
30+
31+
Adds a new `enum` to the public API of `ff` with the following shape (e.g. inspired by the
32+
`ff_derive` crate):
33+
34+
```rust
35+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36+
pub enum ReprEndianness {
37+
Big,
38+
#[default]
39+
Little,
40+
}
41+
```
42+
43+
An associated constant added to `PrimeField`, with a default of `ReprEndianness::Little` in order
44+
to ensure the addition is non-breaking, can now be used to determine how to iterate over the bits
45+
of `PrimeField::Repr`:
46+
47+
```rust
48+
pub trait PrimeField {
49+
type Repr: ...;
50+
51+
const REPR_ENDIANNESS: ReprEndianness = ReprEndianness::Little;
52+
}
53+
```
54+
55+
Any downstream code consuming `PrimeField::Repr` in a non-opaque manner will need to consult this
56+
constant when attempting to iterate over its bytes.
57+
58+
# Reference-level explanation
59+
[reference-level-explanation]: #reference-level-explanation
60+
61+
The w-NAF implementation in `group` can now consult this constant when e.g. constructing
62+
`LimbBuffer` in order to determine the order in which to iterate over the bytes of the serialized
63+
scalar.
64+
65+
Nothing else in the API needs to change besides this: downstream users of the w-NAF implementation
66+
will now have it automatically work when they use it in conjunction with an elliptic curve whose
67+
scalars use a big endian serialization, once the curve implementation sets the constant to
68+
`ReprEndianness::Big` when appropriate.
69+
70+
# Drawbacks
71+
[drawbacks]: #drawbacks
72+
73+
The main drawback of this proposed approach is it leaves the door open to potential misuse where
74+
the associated `const` for `ReprEndianness` isn't checked/honored and code continues to assume that
75+
`PrimeField::Repr` is always little endian.
76+
77+
It doesn't present a single API that makes it easy to iterate over the bits of a field element
78+
irrespective of the endianness in which `Repr` is serialized, though such an API could potentially
79+
be added in a followup. This proposal deliberately sidesteps defining such an API in order to keep
80+
the scope and code surface as simple as possible.
81+
82+
# Rationale and alternatives
83+
[rationale-and-alternatives]: #rationale-and-alternatives
84+
85+
Without some way for curve implementations to signal the endianness of `PrimeField::Repr` it's not
86+
possible to use curves with a big endian serialization thereof with generic code that needs to
87+
iterate over the bits of a field element.
88+
89+
The main alternative would be to define an API that allows for iterating over the bits of a
90+
serialized field element in least-to-most significant order, such as `PrimeField::to_le_bits`,
91+
returning a type bounded by something like `(Into)Iterator<Item = bool>`, e.g. a new public iterator
92+
type adapted from e.g. `LimbBuffer` that can be used generically over curves, a new associated
93+
type, or potentially using RPIT such that the type can be completely opaque.
94+
95+
Such an API can still be added in a followup, and defined generically by having it automatically
96+
consult the associated `const` for the endianness.
97+
98+
# Prior art
99+
[prior-art]: #prior-art
100+
101+
Two places we see prior art for at least the `enum` portion of this proposal:
102+
103+
- `ff_derive`: has an `enum ReprEndianness`, unfortunately due to the nature of proc macro crates
104+
it will always need to define its own `enum` separate from everything else.
105+
- `primefield`: @RustCrypto's generic implementation of prime fields which implement the traits
106+
from `ff` defines an `enum ByteOrder` with `LittleEndian` and `BigEndian` variants, but can switch
107+
to the `enum` from `ff` when added to the public API.
108+
109+
# Unresolved questions
110+
[unresolved-questions]: #unresolved-questions
111+
112+
The main unresolved question is how to present an API which can consult the associated `const` which
113+
defines the `ReprEndianness` and from it provide consistent behavior to downstream users, such that
114+
downstream code doesn't need to check it and can always e.g. iterate over the bits of a field
115+
element in least-to-most significant order regardless of the field element serialization,.
116+
117+
# Future possibilities
118+
[future-possibilities]: #future-possibilities
119+
120+
The main future possibility, as sketched out in the "Rationale and alternatives" section, is to
121+
provide a higher-level API which abstracts over the endianness so downstream code doesn't need to
122+
be aware of it at all.
123+
124+
This would be beneficial for preventing misuses where the endianness is incorectly assumed to always
125+
be little endian.

0 commit comments

Comments
 (0)