--- title: Foundations description: The curve abstraction, the arithmetic helpers, and the protocol iterator — the three things almost every other package in this library is built on top of. sidebar: order: 1 icon: layers --- Nearly every package in this repository is generic over one type: `*curves.Curve`. BLS signatures, BBS+, Shamir sharing, Feldman/Pedersen VSS, Schnorr proofs, the accumulator, threshold ECDSA, and the DID key layer all take a curve value and do their work through two interfaces — `curves.Point` and `curves.Scalar`. If you understand those three things, the rest of the library reads as variations on a theme. This section covers the shared substrate: Every named curve constructor, the full `Point` / `Scalar` method sets, pairing curves, and the low-level `native` field arithmetic. The `core` package: modular arithmetic over `big.Int`, hash-to-field, Fiat–Shamir, safe primes, and the HMAC commitment scheme. The `Iterator` / `Message` crank pattern that drives every DKLs18-family interactive protocol. ## The `Curve` value `curves.Curve` is a plain struct, not an interface. It is a *bundle of prototypes*: ```go type Curve struct { Scalar Scalar Point Point Name string } ``` `Scalar` and `Point` are not "the" scalar or "the" point — they are zero-valued exemplars you call constructor-shaped methods on. This is how the library gets generic behaviour without Go generics: `curve.Scalar.Random(rand.Reader)` dispatches to the K256 or Ed25519 or BLS12-381 implementation depending on which curve you were handed. Curve constructors are memoized behind `sync.Once`, so `curves.K256()` returns the same pointer on every call and is safe to call in a hot loop. ## Arithmetic on K256 `Point` and `Scalar` methods are chainable and return new values — they never mutate the receiver, so you can hold onto intermediates freely. ```go title="arith.go" package main import ( "crypto/rand" "fmt" "github.com/sonr-io/crypto/core/curves" ) func main() { curve := curves.K256() // Two random field elements. x := curve.Scalar.Random(rand.Reader) y := curve.Scalar.Random(rand.Reader) // Scalar field arithmetic: mod q, where q is the group order. sum := x.Add(y) xInv, err := x.Invert() if err != nil { panic(err) // only fails for zero } fmt.Println(x.Mul(xInv).IsOne()) // true // Group arithmetic. Note the homomorphism: // (x + y)·G == x·G + y·G P := curve.ScalarBaseMult(x) Q := curve.NewGeneratorPoint().Mul(y) fmt.Println(P.Add(Q).Equal(curve.ScalarBaseMult(sum))) // true // Identity behaves as expected. fmt.Println(P.Sub(P).Equal(curve.NewIdentityPoint())) // true // Serialization round-trip: 33 bytes compressed for K256. enc := P.ToAffineCompressed() P2, err := curve.Point.FromAffineCompressed(enc) if err != nil { panic(err) } fmt.Println(len(enc), P2.Equal(P), P.CurveName()) // 33 true secp256k1 } ``` Two habits worth forming immediately: - **Deserialize through the curve's prototype**, i.e. `curve.Point.FromAffineCompressed(b)` and `curve.Scalar.SetBytes(b)`. These are the only entry points that know which concrete type to produce. - **Check the error on `Invert`, `Sqrt`, `SetBytes`, and `SetBigInt`.** The arithmetic methods (`Add`, `Mul`, `Neg`, `Double`) return no error and will happily produce garbage if you fed them a value from a different curve. :::warning[Cross-curve values do not panic] `Scalar.Cmp` returns `-2` when the two scalars belong to different fields — that is the only place the library tells you about a curve mismatch. `Add`, `Mul`, and friends have no error channel. Mixing a `ScalarK256` into a P-256 computation produces a silently wrong result, so keep a single `*curves.Curve` threaded through a computation rather than calling constructors ad hoc. ::: ## Two generations of API coexist This is the single most important orientation fact about the repository. There are **two** unrelated curve APIs in `core/curves`, and which one you get depends entirely on which package you called. Interface-based, generic over the curve, supports every curve in the catalog including pairing-friendly ones. ```go curve := curves.K256() s := curve.Scalar.Random(rand.Reader) // curves.Scalar P := curve.ScalarBaseMult(s) // curves.Point ``` Used by: `signatures/bbs`, `signatures/bls/bls_sig`, `signatures/schnorr/mina`, `signatures/schnorr/nem`, `sharing`, `dkg/frost`, `zkp/schnorr`, `accumulator`, `bulletproof`, `tecdsa/dklsv1`, `ted25519/frost`, `ot/*`. Concrete structs over `crypto/elliptic` and `math/big`. No pairing support, no hash-to-curve, and scalars are raw `*big.Int` wrapped by an `EcScalar` strategy object. ```go // EcPoint wraps an elliptic.Curve plus affine X, Y as *big.Int. P, err := curves.NewScalarBaseMult(btcec.S256(), k) // Field/Element is generic modular arithmetic over an explicit modulus. f := curves.NewField(order) e := f.NewElement(big.NewInt(3)) e = e.Mul(f.NewElement(big.NewInt(4))) // 12 mod order ``` Used by: `sharing/v1`, `dkg/gennaro`, `dkg/gennaro2p`, `ted25519/ted25519` keygen, `paillier` (`psf.go`), and the ECDSA public-key conversion helpers in `keys` and `mpc`. The two worlds share nothing. There is no conversion helper between `curves.Point` and `*curves.EcPoint`, and no helper between `curves.Scalar` and `*curves.Element`. If you need to move a value across, you go through bytes or `big.Int` yourself and take responsibility for the encoding. :::danger[The legacy `Field` is not constant time] The package documentation for `core/curves` says so outright: *"Field implementation IS NOT constant time as it leverages math/big for big number operations."* This applies to `Field`, `Element`, `EcPoint`, and everything built on them — which includes `sharing/v1` and `dkg/gennaro`. Do not use those packages on secret-dependent inputs where timing is observable by an attacker. Prefer the modern `Point`/`Scalar` path for new code. ::: ## Where the pieces are used | Layer | Packages | What it needs from foundations | | --- | --- | --- | | [Signatures](/signatures) | `signatures/bls/bls_sig`, `signatures/bbs`, `signatures/schnorr/*` | `*curves.Curve`, or `*curves.PairingCurve` for BBS+ | | [Threshold](/threshold) | `sharing`, `dkg/*`, `tecdsa/dklsv1`, `ted25519/*`, `ot/*` | `*curves.Curve`, plus `core/protocol` for `tecdsa/dklsv1` | | [Zero-knowledge](/zero-knowledge) | `zkp/schnorr`, `accumulator`, `bulletproof` | `*curves.Curve`; the accumulator needs a `*PairingCurve` | | [Identity](/identity) | `keys`, `mpc`, `ucan`, `ecies`, `wasm` | `keys` and `mpc` use `curves`; `mpc` also uses `core/protocol`. `ucan`, `wasm`, and most of `ecies` do not touch the curve abstraction at all. | | [Symmetric](/symmetric) | `aead`, `daed`, `argon2`, `subtle`, `secure`, `salt`, `password` | nothing — these are pure `[]byte` APIs | Two more packages sit outside the curve abstraction entirely: `ecdsa` and `vrf` do not import `core/curves` at all, and `paillier` — like `core` itself — works directly over `math/big` integers with an explicit modulus. See [arithmetic](/foundations/arithmetic) for that world.