Repository navigation
Downstream scalar fields
The open Field trait lets a downstream crate supply scalar arithmetic and receive Elem<F>, operators, and the sealed FieldElem implementation. Metadata validation checks declarations; the implementor remains responsible for the algebra.
This guide targets fgf 3.0.0. The field reference owns provider obligations. The type model introduces the capability split.
A supported custom binary quadratic tower uses TowerSpec, not a new scalar wrapper. It receives the supported binary presentation, embeddings, and applicable byte-buffer surface. Quadratic towers explains that route.
A field outside that vocabulary implements Field. This does not implement the sealed BinaryField, BinaryRepr, or FieldKernels contracts. In particular, PrimeCharacteristic<P> checks a prime identity; it is not an arithmetic engine for every P.
A field declares its raw word, characteristic identity, degree, order, canonical zero and one, and validity check. Its raw arithmetic methods operate on canonical inputs and must produce canonical outputs. The wrapper's constructors invoke canonical_raw; arithmetic does not repair an invalid provider's result afterward.
The example defines GF(7). Small modular formulas keep the provider visible rather than hiding it behind another dependency. The bounded inverse search handles zero by returning zero, matching the library convention.
use fgf::{Elem, Field, FieldBuffer, FieldElem, HasGenerator, PrimeCharacteristic, ops};
#[derive(Clone, Copy, Debug)]
struct Seven;
impl Field for Seven {
type Raw = u8;
type Characteristic = PrimeCharacteristic<7>;
const NAME: &'static str = "GF(7)";
const DEGREE: u32 = 1;
const ORDER: u128 = 7;
const ZERO_RAW: u8 = 0;
const ONE_RAW: u8 = 1;
const VALID: () = ();
fn canonical_raw(raw: u8) -> u8 { raw % 7 }
fn add_raw(a: u8, b: u8) -> u8 { (a + b) % 7 }
fn sub_raw(a: u8, b: u8) -> u8 { (a + 7 - b) % 7 }
fn neg_raw(a: u8) -> u8 { if a == 0 { 0 } else { 7 - a } }
fn mul_raw(a: u8, b: u8) -> u8 { (a * b) % 7 }
fn inv_raw(a: u8) -> u8 {
(1u8..7).find(|&candidate| (a * candidate) % 7 == 1).unwrap_or(0)
}
}
impl HasGenerator for Seven {
const GENERATOR_RAW: u8 = 3;
}
impl FieldBuffer for Seven {
const BYTES: usize = 1;
const STORAGE_BITS: u32 = 8;
fn decode(bytes: &[u8]) -> Elem<Self> {
assert_eq!(bytes.len(), 1);
Elem::<Self>::from_raw(bytes[0])
}
fn encode(bytes: &mut [u8], value: Elem<Self>) {
assert_eq!(bytes.len(), 1);
bytes[0] = value.to_raw();
}
}
let a = Elem::<Seven>::from_raw(12);
let b = Elem::<Seven>::from_raw(3);
assert_eq!(a.to_raw(), 5);
assert_eq!((a * b).to_raw(), 1);
assert_eq!((a - b).to_raw(), 2);
assert_eq!((-a).to_raw(), 2);
assert_eq!(a * a.inv(), Elem::<Seven>::ONE);
assert_eq!(a / Elem::<Seven>::ZERO, Elem::<Seven>::ZERO);
let generator = Elem::<Seven>::GENERATOR;
assert_eq!(generator.pow(6), Elem::<Seven>::ONE);
assert_ne!(generator.pow(3), Elem::<Seven>::ONE);
assert_ne!(generator.pow(2), Elem::<Seven>::ONE);
let mut bytes = [0u8; 2];
ops::pack::<Seven>(&mut bytes, &[a, b]);
assert_eq!(bytes, [5, 3]);
assert_eq!(Seven::decode(&[12]), a);The largest canonical product is 6*6, so these raw u8 formulas fit. That reasoning does not generalize to a larger modulus: a provider must choose intermediates that cannot overflow and a reduction correct over its entire declared raw domain.
VALID = () adds no provider-specific check here. It does not certify the methods. The metadata is still checked centrally; the example's arithmetic follows reduction modulo the known prime seven.
Generic construction checks a prime characteristic identity, positive degree, and ORDER == CHARACTERISTIC^DEGREE with checked exponentiation. Characteristic-two fields additionally obey the binary degree cap of 64. The wrapper evaluates the field's VALID constant after those checks.
These checks reject inconsistent metadata. They do not prove distributivity, inverse correctness, canonical output, or a selected generator's order. VALID is where a provider can add checks on its defining data; it cannot replace verification of the supplied arithmetic.
FieldBuffer supplies a fixed-width little-endian encoding. The example's one-byte format is its own contract: decode reduces accepted aliases, while encode emits canonical storage. This enables ops::pack and unpack, which need encoding but not arithmetic kernels. ops::mul_add::<Seven> is not available, because Seven does not implement the sealed kernel trait.
HasGenerator supplies canonical raw coordinates with full multiplicative order. For a downstream provider, canonicality and full order are implementor obligations. The example checks order six using its prime-factor cofactors. Supplying GENERATOR_RAW alone does not make the wrapper prove that order.
Custom arithmetic receives ordinary trait methods and operators. The wrapper does not synthesize inherent const arithmetic for a downstream provider. ZERO, ONE, and to_raw remain generic const items; custom construction and multiplication use the runtime trait methods.
Provider checks need observable algebra and representation results: known modular answers, boundary raw values, zero inversion, nonzero inverse products, field laws, and encode/decode canonicalization. Generator checks verify full order, not merely g^(q-1) == 1. An independent modular reference can distinguish arithmetic mistakes from matching wrapper calls.
Metadata checks and this small teaching example are not an audit of a new production field. Field is safe to implement, but incorrect arithmetic still produces incorrect application results. Constant-time behavior is not guaranteed by the trait.
Next: Features and deployment, or The type model and generic algorithms.
Using fgf
- Type model and generic algorithms
- Choosing a field and basis
- Coordinates and packed bytes
- Linear operations and row geometry
- Prepared coefficients
- Prime fields
- Bit-packed GF(2)
- Features and deployment
Presentations and extensions
Upgrading