Getting started
Install the module, choose a curve, and learn the conventions — constructors, round-based protocols, serialization, and error handling — that every package in this library shares.
Install
go get github.com/sonr-io/crypto
Requires Go 1.24.7 or newer. Every package is imported under the module path
github.com/sonr-io/crypto/<package>:
import (
"github.com/sonr-io/crypto/core/curves"
"github.com/sonr-io/crypto/sharing"
"github.com/sonr-io/crypto/mpc"
)
There is no top-level façade package — the module root holds only a cross-package security test suite. Import the specific primitive you need.
Pick a curve first
Most constructors take a *curves.Curve. That single argument determines the group, the scalar
field, and the serialization width of everything downstream, so it is the first decision you make:
curve := curves.K256() // secp256k1 — Bitcoin, Ethereum, Cosmos
curve := curves.P256() // NIST P-256 — WebAuthn, FIDO2, TLS
curve := curves.ED25519() // Ed25519 — Sonr identity keys
Some primitives need a pairing-friendly curve instead, because they rely on a bilinear map.
BBS+ signatures and the accumulator both fall in this group and take a *curves.PairingCurve:
pairingCurve := curves.BLS12381(curves.BLS12381G1().NewGeneratorPoint())
Passing a plain *curves.Curve where a *curves.PairingCurve is required will not compile, which is
the intended guardrail. See Curves for the full catalog and interface reference.
Conventions worth knowing
Constructors validate, so check the error
Constructors do real work — parameter validation, generator derivation, table precomputation — and
return an error rather than panicking on bad input. A NewShamir with a threshold above its limit
fails at construction, not at Split time:
scheme, err := sharing.NewShamir(3, 5, curves.K256())
if err != nil {
return fmt.Errorf("invalid sharing parameters: %w", err)
}
Two generations of API coexist
The library carries an older, curve-specific API alongside the modern generic one. You will meet both
in go doc output, and mixing them does not type-check:
| Modern | Legacy | Used by |
|---|---|---|
curves.Point, curves.Scalar |
curves.EcPoint, curves.EcScalar |
sharing/v1, dkg/gennaro |
sharing |
sharing/v1 |
dkg/gennaro, dkg/gennaro2p |
operates on curves.Scalar |
operates on []byte and curves.Element |
— |
Prefer the modern API for new code. Reach for the legacy layer only when a package you depend on forces it. Foundations explains the split in detail.
Multi-party protocols are explicit round objects
Nothing in this library hides the network. A multi-party protocol is a stateful object whose methods are the rounds, and you move the messages between parties yourself. Two shapes appear:
Each round is a distinct method. You call them in order and route the outputs — some broadcast to
everyone, some point-to-point to one peer. Used by dkg/frost,
dkg/gennaro, and ted25519/frost.
bcast, p2p, err := participant.Round1(secret)
// broadcast `bcast` to all; send p2p[peerID] privately to each peerThe protocol is a protocol.Iterator: you feed it the counterparty’s message and it returns the
next one, until it signals completion and you read Result. Used by
tecdsa/dklsv1 and, wrapped up entirely, by
mpc.
msg, err := alice.Next(bobMsg)Calling rounds out of order is an error, not undefined behavior — the objects track their own state. See Protocol messages for the iterator contract.
Serialization is per-type, not reflective
Keys, shares, proofs, and signatures implement their own codecs — usually
MarshalBinary/UnmarshalBinary, sometimes MarshalJSON/UnmarshalJSON, and in the DKG packages
Encode/Decode. Use them rather than reflecting over struct fields with encoding/gob or a
generic JSON marshal, because unexported field state and curve identity would be lost.
Unmarshalling frequently needs to know the curve up front, since the wire bytes alone do not identify it. The idiom is to initialize an empty value on the right curve, then unmarshal into it:
sig := new(bbs.Signature).Init(pairingCurve)
if err := sig.UnmarshalBinary(data); err != nil {
return err
}
Randomness is injected
Anything that consumes entropy takes an io.Reader, so tests can be deterministic and production
code is explicit about its source. Pass crypto/rand.Reader unless you have a specific reason not to:
shares, err := scheme.Split(secret, rand.Reader)
Where to next
Foundations
The curve, point, and scalar model that the rest of the library is written against.
Package index
Every importable package and the page that documents it.
Security notes
Stubs, known defects, and non-constant-time paths found while documenting the code.
MPC enclave
The highest-level entry point: threshold ECDSA as a single value.