|
| 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