Ekliptic - Primitives for cryptographic operations on the secp256k1 curve, with zero dependencies and excellent performance

Ekliptic

This package provides primitives for cryptographic operations on the secp256k1 curve, with zero dependencies and excellent performance. It provides both Affine and Jacobian interfaces for elliptic curve operations. Aims to facilitate performant low-level operations on secp256k1 without overengineering or kitchen-sink syndrome.

ALPHA STATE

This library is not finished, stable, or audited - depend on it at your own peril!

Examples

Deriving a public key from a private key:

privateKey, _ := new(big.Int).SetString("c370af8c091812ef7f6bfaffb494b1046fb25486c9873243b80826daef3ec583", 16)
x := new(big.Int)
y := new(big.Int)

ekliptic.MultiplyBasePoint(privateKey, x, y)

fmt.Println("Public key:")
fmt.Printf(" x: %x\n", x)
fmt.Printf(" y: %x\n", y)

// output:
// Public key:
//  x: 76cd66c6cca75278ff408ce67290537367719154ae2b96448327fe4033ddcfc7
//  y: 35663ecbb64397bb9bd79155a1e6b138c2fb8fa1f11355f8e9e97ddd88a78e49

Deriving a Diffie-Hellman shared-secret:

alice, _ := new(big.Int).SetString("94a22a406a6977c1a323f23b9d7678ad08e822834d1df8adece84e30f0c25b6b", 16)
bob, _ := new(big.Int).SetString("55ba19100104cbd2842999826e99e478efe6883ac3f3a0c7571034321e0595cf", 16)

var alicePub, bobPub struct{ x, y big.Int }

// derive public keys
MultiplyBasePoint(alice, &alicePub.x, &alicePub.y)
MultiplyBasePoint(bob, &bobPub.x, &bobPub.y)

var yValueIsUnused big.Int

// Alice gives Bob her public key, Bob derives the secret
bobSharedKey := new(big.Int)
MultiplyAffine(&alicePub.x, &alicePub.y, bob, bobSharedKey, &yValueIsUnused, nil)

// Bob gives Alice his public key, Alice derives the secret
aliceSharedKey := new(big.Int)
MultiplyAffine(&bobPub.x, &bobPub.y, alice, aliceSharedKey, &yValueIsUnused, nil)

fmt.Printf("Alice's derived secret: %x\n", aliceSharedKey)
fmt.Printf("Bob's derived secret:   %x\n", bobSharedKey)

// output:
// Alice's derived secret: 375a5d26649704863562930ded2193a0569f90f4eb4e63f0fee72c4c05268feb
// Bob's derived secret:   375a5d26649704863562930ded2193a0569f90f4eb4e63f0fee72c4c05268feb

Hacking on Ekliptic

Command Usage
go test Run unit tests.
go test -bench=. Run benchmarks.
go test -bench=. -benchmem Run benchmarks with memory profile.
go generate Regenerate precomputed base point doubles.

Performance Optimizations

Memory

All methods use and accept golang-native big.Int structs for math operations. In most cases we require the caller to pass pointers in which results will be stored.

x := new(big.Int).SetString("3e61e957cb7eb9252155722d37056b581cacd9949cd7daeba682d81ee829826d", 16)
y := new(big.Int).SetString("0c9b31d4b3f13c2dcec21b5d446a06cd655056d83495f63f05135ff4434e7ba5", 16)
z := new(big.Int).SetString("4c4619154810c1c0daa4ddd8c73971d159db91705f2113ce51b9885e4578874d", 16)

doubleX := new(big.Int)
doubleY := new(big.Int)
doubleZ := new(big.Int)

ekliptic.DoubleJacobi(
  x3, y3, z3,
  doubleX, doubleY, doubleZ,
)

While slightly more awkward to use, exposing this C-style API allows for better memory performance when doing large numbers of sequential operations. The garbage collector isn't doing as much work, because we don't have to keep re-allocating new big.Ints every time a call returns. We have some benchmarks for addition and doubling which demonstrate this method can save about 6 allocs/op, and a few hundred bytes of memory for every call involving Jacobian points.

You can even safely pass the input pointers as the output pointers, to modify them in place.

ekliptic.DoubleJacobi(
  x3, y3, z3,
  x3, y3, z3,
)

big.Int structs can be re-used when the values they hold are no longer required. This is why you'll see patterns like this if you read Ekliptic's code:

e := a.Mul(a, three)
a = nil
mod(e)

In the above example, a is no longer needed, so we reclaim its memory as a new variable to avoid allocating an entirely new big.Int struct for e.

Jacobian Points

This library offers support for both affine and Jacobian point math. Affine coordinates are 'normal' two-dimensional coordinates, x and y, which unambiguously describes a point on the plane. Jacobian coordinates are a three-dimensional representation of an affine point, (Ax, Ay), in terms of three variables: (x, y, z) such that:

Ax = x / z²
Ay = y / z³

This relationship means there are an absurdly large number of possible Jacobian coordinate triplets which describe the same affine point. Each affine coordinate is basically converted into a ratio of x:z and y:z, thus proportional ratios simplify to the same affine point.

Why would we want to represent points this way? Elliptic curve multiplication - a critical primitive for almost any elliptic-curve cryptography - involves performing many addition operations in a row. That's what multiplication means, after all. When you add two affine (x, y) points together in an elliptic curve, you have to perform some finite field division, AKA modular inversion, to get a result back in affine form. Modular inversion is a very expensive operation compared to multiplication. Instead of dividing after every addition operation, you can defer the division until the end of the multiplication sequence, by accumulating in the divisor coordinate z. After the multiplication operation is done, the point can be converted back to affine, or used for new EC operations, as needed.

To demonstrate, notice how expensive a naive affine multiplication is compared to a Jacobian multiplication:

BenchmarkMultiplyJacobi-4                      600 ops     1926201 ns/op    639940 B/op     7261 allocs/op
BenchmarkMultiplyAffine-4                      606 ops     1926115 ns/op    641480 B/op     7284 allocs/op
BenchmarkMultiplyAffineNaive-4                 481 ops     2457812 ns/op    545873 B/op     9147 allocs/op

ekliptic.MultiplyJacobi and ekliptic.MultiplyAffine both use Jacobian math for multiplication operations under the hood. ekliptic.MultiplyAffineNaive is a naive implementation which uses affine addition and doubling instead of Jacobian math. It should be used for demonstrative purposes only.

Precomputation

You can improve multiplication performance even more by using precomputed doubles of the secp256k1 base-point. Precomputing G * 2^i for i in [0..256] significantly boosts performance for base-point double-and-add multiplication, especially if the precomputed doubles are saved in affine form. Values are computed using the ekliptic.ComputePointDoubles function, triggered by go generate.

Other Performance Notes

Thanks!

I wrote this library as a learning exercise. Thanks a ton to the following people and their amazing guides, with which I followed along and learned from.

Many test vectors in this package were either duplicated from, generated by, or derived from other Elliptic Curve Crypto implementations:

  • paulmillr/noble-secp256k1
  • btcsuite/btcec (note: they use a different algorithm to add jacobians, but it works out to the same affine coordinates at the end. I modified a few of their test fixtures' jacobian ratios.)

Donations

If you're interested in supporting development of this package, show your love by dropping me some Bitcoins!

bc1qhct3hwt5pjmu75d2fldwd477vhwmthuqvmh03s

Owner
Comments
  • perf: strip unnecessary modulo calls

    perf: strip unnecessary modulo calls

    I did some CPU profiling on the benchmarks, and found that the biggest single operation slowing us down was coming from mod() calls. This isn't too surprising - Jacobian points are used precisely to avoid excessive use of modulo operations, as they require expensive division. However, there were a lot of places where I was doing mod unnecessarily.

    Numbers only need to be taken modulo P when they are either being compared with one-another directly, being returned to the caller, or being subtracted in such a way that might result in negative values being fed into forthcoming multiplicative operations.

    Other than those cases, modulo operations can be pruned.


    I also found a improvement in the dbl-2009-l jacobian point doubling formula, where the expression for d can be simplified.

    I discovered that when I removed the mod(d) call, the formula still worked. After expanding the terms to investigate, I realized the expression for d is always positive, and happened to notice an opportunity to simplify, removing several subtraction and multiplication operations.

    The official dbl-2009-l formula specifies:

    d = 2 * ((x1+b)² - a - c)
    

    But it can be simplified, because:

    a = x1², c = b²
    d = 2 * ((x1+b)² - a - c)
      = 2 * ((x1+b)² - x1² - b²)
      = 2 * ((x1+b)(x1+b) - x1² - b²)
      = 2 * (x1² + 2(x1*b) + b² - x1² - b²)
      = 2 * 2(x1*b)
    

    So actually:

    d = 4 * x1 * b
    

    Much easier on the eyes.

  • docs: improve examples

    docs: improve examples

    • ExampleWeierstrass is now an example of uncompressing a public key.
    • All examples moved into one file.
    • Examples now all import ekliptic instead of being inside the package scope.
    • Specify alice and bob are priv keys
    • Use NewPrivateKey in ECDSA example
  • feat: use windowed method and montgomery ladder for fast constant-time multiplication

    feat: use windowed method and montgomery ladder for fast constant-time multiplication

    Current Behavior

    The various point-multiplication methods use non-constant-time algorithms which will leak a lot of timing information.

    New Behavior

    • Multiplying with precomputation now uses a constant-time version of the windowed method using a table of precomputed values. See the README changes for a full explanation.
    • Multiplying without precomputation now uses the Montgomery Ladder algorithm to ensure constant-time multiplication.
  • feat: add Curve struct type to satisfy elliptic.Curve

    feat: add Curve struct type to satisfy elliptic.Curve

    Fixes #4

    Not going to remove ekliptic.NewPrivateKey as suggested in the issue. Callers may want an obvious way of generating keys. Also, elliptic.GenerateKey returns the private key as a byte slice, which is contrary to the idiom in Ekliptic (using big.Ints for everything).

  • use %.64x formatting on all test error messages which hexify bigints

    use %.64x formatting on all test error messages which hexify bigints

    0000000000000000000000000000000000000000000000000000000000000001 vs 6d1b68d0dd35b49978b210349b47202a998d0eaaa4eec00b4d8f056173a2dd4e

    is easier to compare visually than

    1 vs 6d1b68d0dd35b49978b210349b47202a998d0eaaa4eec00b4d8f056173a2dd4e

  • feat: improve quality and reproducability of test vectors

    feat: improve quality and reproducability of test vectors

    Test vectors are rather opaque right now as to where they came from and how I got them. We could write scripts to re-derive (thus validating) our test vectors using other stable secp256k1 implementations.

    Potential side-quest related to this issue: in EC math unit tests, we should check affine equality against vectors rather than jacobian equality. It doesn't matter in the end what the exact (x,y,z) pair is, as long as the jacobian ratio is equivalent to the desired affine point. Testing affine equality will make it easier to consume the test vectors of other implementations, because they may use different jacobian math than us.

  • feat: satisfy elliptic.Curve interface

    feat: satisfy elliptic.Curve interface

    Golang's standard library provides an elliptic curve crypto utility:

    $ go doc elliptic.curve
    
    package elliptic // import "crypto/elliptic"
    
    type Curve interface {
    	// Params returns the parameters for the curve.
    	Params() *CurveParams
    	// IsOnCurve reports whether the given (x,y) lies on the curve.
    	IsOnCurve(x, y *big.Int) bool
    	// Add returns the sum of (x1,y1) and (x2,y2)
    	Add(x1, y1, x2, y2 *big.Int) (x, y *big.Int)
    	// Double returns 2*(x,y)
    	Double(x1, y1 *big.Int) (x, y *big.Int)
    	// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
    	ScalarMult(x1, y1 *big.Int, k []byte) (x, y *big.Int)
    	// ScalarBaseMult returns k*G, where G is the base point of the group
    	// and k is an integer in big-endian form.
    	ScalarBaseMult(k []byte) (x, y *big.Int)
    }
        A Curve represents a short-form Weierstrass curve with a=-3.
    
        The output of Add, Double, and ScalarMult when the input is not a point on
        the curve is undefined.
    
        Note that the conventional point at infinity (0, 0) is not considered on the
        curve, although it can be returned by Add, Double, ScalarMult, or
        ScalarBaseMult (but not Unmarshal or UnmarshalCompressed).
    

    We should provide a struct which fulfills this interface, so callers can use it in golang standard library APIs.

    Possibly related: if we have an elliptic.Curve, we can get rid of ekliptic.NewPrivateKey, because callers could just use crypto/elliptic.GenerateKey instead:

    package elliptic // import "crypto/elliptic"
    
    func GenerateKey(curve Curve, rand io.Reader) (priv []byte, x, y *big.Int, err error)
        GenerateKey returns a public/private key pair. The private key is generated
        using the given reader, which must return random data.
    
  • Documentation improvements

    Documentation improvements

    • [x] More examples of how to use this code, in human readable terms (https://github.com/kklash/ekliptic/pull/11)
    • [ ] Add custom artwork for the library
    • [x] Publish a link to pkg.go.dev in readme (https://github.com/kklash/ekliptic/commit/9e6b550c2c4add6881394356dd572a19628c3b6f)
Simple, fast and safe cross-platform linear binary stream communication protocol. AES key exchange based on ecc secp256k1

FFAX Protocol 2 dev 简体中文 Welcome to FFAX Protocol v2 Quick start go get github.com/RealFax/FFAX func example() { listener, err := net.Listen("tcp",

Mar 21, 2022
Signing, Keystore and RLP encoding utilities for EVM / Ethereum / secp256k1 based blockchains

Signing, Keystore and RLP encoding utilities for EVM / Ethereum / secp256k1 based blockchains. Written in Go with an enterprise friendly Apache 2.0 license, and a runtime JSON/RPC proxy server. Part of the Hyperledger FireFly project

Aug 9, 2022
Attempts to make attribute based encryption work, particularly trying out bn256 pairing curve
Attempts to make attribute based encryption work, particularly trying out bn256 pairing curve

EC Pairings over bn256 This is an attempt to solve the core problem of attribute based encryption, where the goal is to be able to use CA-issued attri

Jan 5, 2022
Bitcoin futures curve from Deribit as a JSON webservice

Curve Bitcoin futures curve from Deribit as a JSON webservice Building go build . Running ./curve Expiration date and annualised yield of each contr

Dec 13, 2021
goKryptor is a small and portable cryptographic tool for encrypting and decrypting files.

goKryptor goKryptor is a small and portable cryptographic tool for encrypting and decrypting files. This tool supports XOR and AES-CTR (Advanced Encry

Dec 6, 2021
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Jan 9, 2023
Go implementation of BLAKE2 (b) cryptographic hash function (optimized for 64-bit platforms).

Go implementation of BLAKE2b collision-resistant cryptographic hash function created by Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, an

Jul 11, 2022
whirlpool cryptographic hashing library

whirlpool.go A whirlpool hashing library for go Build status Setup $ go get github.com/jzelinskie/whirlpool Example package main import ( "fmt" "

Oct 12, 2022
Cryptographic Addition Chain Generation in Go

Cryptographic Addition Chain Generation in Go addchain generates short addition chains for exponents of cryptographic interest with results rivaling t

Dec 5, 2022
Pure Go GOST cryptographic functions library.

Pure Go GOST cryptographic functions library. GOST is GOvernment STandard of Russian Federation (and Soviet Union). GOST 28147-89 (RFC 5830) block cip

Aug 10, 2022
An out-of-the-box cryptographic message communication.

An out-of-the-box cryptographic message communication.

Feb 8, 2022
List your dependencies capabilities and monitor if updates require more capabilities.

A take on supply chain security in Go List your dependencies capabilities and monitor if dependency updates require more capabilities. The Problem Rec

Nov 16, 2022
Go implementation of a vanity attempt to generate Bitcoin private keys and subsequently checking whether the corresponding Bitcoin address has a non-zero balance.

vanity-BTC-miner Go implementation of a vanity attempt to generate Bitcoin private keys and subsequently checking whether the corresponding Bitcoin ad

Jun 3, 2022
Gozerodemo - go-zero demo

README RPC https://go-zero.dev/cn/goctl-rpc.html 更新API后生成代码 goctl api go -api gozerodemo.api -dir . -style gozero 启动服务 go run gozerodemo.go -f etc/goz

Jan 9, 2022
Accelerate aggregated MD5 hashing performance up to 8x for AVX512 and 4x for AVX2.
Accelerate aggregated MD5 hashing performance up to 8x for AVX512 and 4x for AVX2.

Accelerate aggregated MD5 hashing performance up to 8x for AVX512 and 4x for AVX2. Useful for server applications that need to compute many MD5 sums in parallel.

Nov 23, 2022
sops is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP
sops is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP

sops is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP. (demo)

Jan 9, 2023
A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.
A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.

A simple, modern and secure encryption tool (and Go library) with small explicit keys, no config options, and UNIX-style composability.

Jan 7, 2023
Webserver I built to serve Infura endpoints. Deployable via k8s and AWS EKS. Load testable via k6 tooling, and montiorable via prometheus and grafana

Infura Web Server Welcome to my verion of the take home project. I've created a webserver written in go to serve Infura api data over 3 possible data

Nov 15, 2022
Get any cryptocurrencies ticker and trade data in real time from multiple exchanges and then save it in multiple storage systems.
Get any cryptocurrencies ticker and trade data in real time from multiple exchanges and then save it in multiple storage systems.

Cryptogalaxy is an app which will get any cryptocurrencies ticker and trade data in real time from multiple exchanges and then saves it in multiple storage systems.

Jan 4, 2023