Skip to content
Sonr Crypto
Esc
navigateopen⌘Jpreview
On this page

Foundations

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.

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:

The Curve value

curves.Curve is a plain struct, not an interface. It is a bundle of prototypes:

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.

PropType
curve.NewScalar()?Scalar

A fresh scalar set to zero. Equivalent to curve.Scalar.Zero().

TypeScalar
curve.NewGeneratorPoint()?Point

The group generator G. Equivalent to curve.Point.Generator().

TypePoint
curve.NewIdentityPoint()?Point

The point at infinity. Equivalent to curve.Point.Identity().

TypePoint
curve.ScalarBaseMult(sc)?Point

Fixed-base multiplication sc·G. Use this instead of NewGeneratorPoint().Mul(sc).

TypePoint
curve.ToEllipticCurve()?(elliptic.Curve, error)

Bridge to crypto/elliptic. Only K256 and P-256 succeed; every other curve returns an error.

Type(elliptic.Curve, error)

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.

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.

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.

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.

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

Where the pieces are used

Layer Packages What it needs from foundations
Signatures signatures/bls/bls_sig, signatures/bbs, signatures/schnorr/* *curves.Curve, or *curves.PairingCurve for BBS+
Threshold sharing, dkg/*, tecdsa/dklsv1, ted25519/*, ot/* *curves.Curve, plus core/protocol for tecdsa/dklsv1
Zero-knowledge zkp/schnorr, accumulator, bulletproof *curves.Curve; the accumulator needs a *PairingCurve
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 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 for that world.

Last updated on September 2, 2026

Was this page helpful?