Documentation ¶
Overview ¶
Package ekliptic 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.
Index ¶
- Variables
- func AddAffine(x1, y1 *big.Int, x2, y2 *big.Int) (x3, y3 *big.Int)
- func AddJacobi(x1, y1, z1 *big.Int, x2, y2, z2 *big.Int) (x3, y3, z3 *big.Int)
- func DoubleAffine(x1, y1 *big.Int) (x3, y3 *big.Int)
- func DoubleJacobi(x1, y1, z1 *big.Int) (x3, y3, z3 *big.Int)
- func EqualAffine(x1, y1 *big.Int, x2, y2 *big.Int) bool
- func EqualJacobi(x1, y1, z1 *big.Int, x2, y2, z2 *big.Int) bool
- func InvertScalar(d *big.Int) *big.Int
- func IsOnCurveAffine(x, y *big.Int) bool
- func IsOnCurveJacobi(x, y, z *big.Int) bool
- func IsValidScalar(d *big.Int) bool
- func MultiplyAffine(x1, y1 *big.Int, k *big.Int, precomputedTable PrecomputedTable) (x2, y2 *big.Int)
- func MultiplyAffineNaive(x1, y1 *big.Int, k *big.Int) (x2, y2 *big.Int)
- func MultiplyBasePoint(k *big.Int) (x, y *big.Int)
- func MultiplyJacobi(x1, y1, z1 *big.Int, k *big.Int, precomputedTable PrecomputedTable) (x2, y2, z2 *big.Int)
- func Negate(y *big.Int) *big.Int
- func RandomScalar(random io.Reader) (*big.Int, error)
- func SignECDSA(d, k, z *big.Int) (r, s *big.Int)
- func SubAffine(x1, y1 *big.Int, x2, y2 *big.Int) (x3, y3 *big.Int)
- func SubJacobi(x1, y1, z1 *big.Int, x2, y2, z2 *big.Int) (x3, y3, z3 *big.Int)
- func ToAffine(x, y, z *big.Int)
- func VerifyECDSA(z *big.Int, r, s *big.Int, pubX, pubY *big.Int) bool
- func Weierstrass(x *big.Int) (evenY, oddY *big.Int)
- type Curve
- func (_ *Curve) Add(x1, y1, x2, y2 *big.Int) (x3, y3 *big.Int)
- func (_ *Curve) Double(x1, y1 *big.Int) (x3, y3 *big.Int)
- func (_ *Curve) IsOnCurve(x, y *big.Int) bool
- func (c *Curve) Params() *elliptic.CurveParams
- func (_ *Curve) ScalarBaseMult(k []byte) (x2, y2 *big.Int)
- func (_ *Curve) ScalarMult(x1, y1 *big.Int, k []byte) (x2, y2 *big.Int)
- type PrecomputedTable
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // The parameters of the secp256k1 elliptic curve. Secp256k1_B = seven Secp256k1_P = hexint("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F") Secp256k1_CurveOrder = hexint("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") // Secp256k1_GeneratorX and Secp256k1_GeneratorY describe the secp256k1 // generator point, used for deriving public keys from private keys. Secp256k1_GeneratorX = hexint("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798") Secp256k1_GeneratorY = hexint("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8") // Secp256k1_CurveOrderHalf is half of Secp256k1_CurveOrder (rounded down). Secp256k1_CurveOrderHalf = new(big.Int).Rsh(Secp256k1_CurveOrder, 1) )
Functions ¶
func AddAffine ¶
AddAffine adds two affine points on the secp256k1 curve:
P1 + P2 = P3 (x1, y1) + (x2, y2) = (x3, y3)
It returns the resulting affine point (x3, y3).
We incorporate the standard affine addition and doubling formulas:
if P1 == P2: m = (3 * x1² + a) / (2 * y1) else: m = (y2 - y1) / (x2 - x1) x3 = m² - x1 - x2 y3 = m * (x1 - x3) - y1
This function does not check point validity - it assumes you are passing valid points on the secp256k1 curve.
func AddJacobi ¶
AddJacobi adds two Jacobian coordinate points on the secp256k1 curve:
P1 + P2 = P3 (x1, y1, z1) + (x2, y2, z2) = (x3, y3, z3)
It returns the resulting Jacobian point (x3, y3, z3).
We use the "add-1998-cmo-2" addition formulas.
https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-1998-cmo-2
Z1Z1 = Z1² Z2Z2 = Z2² U1 = X1*Z2Z2 U2 = X2*Z1Z1 S1 = Y1*Z2*Z2Z2 S2 = Y2*Z1*Z1Z1 H = U2-U1 HH = H² HHH = H*HH r = S2-S1 V = U1*HH X3 = r²-HHH-2*V Y3 = r*(V-X3)-S1*HHH Z3 = Z1*Z2*H
This function does not check point validity - it assumes you are passing valid points on the secp256k1 curve.
func DoubleAffine ¶
m = (3*x1²+a) / (2*y1) x3 = m² - x1 - x2 y3 = m(x1-x3) - y1
func DoubleJacobi ¶
DoubleJacobi doubles a Jacobian coordinate point on the secp256k1 curve, using the "dbl-2009-l" doubling formulas.
http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
A = X1² B = Y1² C = B² D = 2*((X1+B)²-A-C) E = 3*A F = E² X3 = F-2*D Y3 = E*(D-X3)-8*C Z3 = 2*Y1*Z1
func EqualAffine ¶
EqualAffine tests the equality of two affine points.
func EqualJacobi ¶
EqualJacobi tests whether two Jacobian points are equivalent to the same affine point, without the performance penalty of actually converting both points to affine format. It returns true if:
x2 * z1² == x1 * z2² y2 * z1³ == y1 * z2³
Affine coordinates are calculated as:
Ax1 = x1 / z1² Ax2 = x2 / z2² Ay1 = y1 / z1³ Ay2 = y2 / z2³
Thus, we can re-arrange operations to avoid costly division, and determine if the x-coordinate matches:
Ax1 = Ax2 x2 / z2² = x1 / z1² (x2 * z1²) / z2² = x1 x2 * z1² = x1 * z2²
Same for the y-coordinate:
Ay1 = Ay2 (y2 * z1³) / z2³ = y1 y2 * z1³ = y1 * z2³
This approach provides a 5x speedup compared to affine conversion.
func InvertScalar ¶
InvertScalar returns d⁻¹: the multiplicative inverse of the given scalar value d, modulo the curve order N:
d * d⁻¹ = 1 mod N
Multiplying any point A by both d and d⁻¹ will return the same point A:
d * A * d⁻¹ = A
Example ¶
InvertScalar is useful for reversibly blinding a value you don't want to reveal. Alice can blind any point A with some random scalar s to produce a blinded point B:
B = s * A
B can then be blinded by another party Bob, with their own secret r:
C = r * B
Alice can then unblind C by inverting their secret s:
M = s⁻¹ * C M = s⁻¹ * (r * s * A) M = r * A
Alice now knows r * A without knowing r, having revealed neither A nor the final result M to Bob.
package main import ( "fmt" "math/big" "github.com/kklash/ekliptic" ) func main() { // Alice: input parameters s, _ := new(big.Int).SetString("2fc9374cad648e33f78dd294578dd960281e05744b27faa1ffe1e7175bd6901d", 16) aX, _ := new(big.Int).SetString("8bf20851cc16007dbf3df0c109dc016b360ca0f729f368ea38c385ceeffaf3cf", 16) aY, _ := new(big.Int).SetString("a0c7bd73154c02cc5e002c3a4f876158a4276c185ef859df589675a92c745e3a", 16) // Bob: input parameters r, _ := new(big.Int).SetString("825e7984ae7843f9c13371d9a54143a465b1e2d278e67de1ca713127e40a52f1", 16) // Alice: blind the input with secret s send the result B to Bob. // B = s * A bX, bY := ekliptic.MultiplyAffine(aX, aY, s, nil) // Bob: receive A, blind it again with secret r and return result C to Alice. // C = r * B = r * s * G cX, cY := ekliptic.MultiplyAffine(bX, bY, r, nil) // Alice: unblind C with the inverse of s: // s⁻¹ * C = s⁻¹ * r * s * G = r * G sInv := ekliptic.InvertScalar(s) mX, mY := ekliptic.MultiplyAffine(cX, cY, sInv, nil) expectedX, expectedY := ekliptic.MultiplyAffine(aX, aY, r, nil) if !ekliptic.EqualAffine(mX, mY, expectedX, expectedY) { fmt.Println("did not find expected unblinded point") return } fmt.Println("found correct unblinded point q * h * G") }
Output: found correct unblinded point q * h * G
func IsOnCurveAffine ¶
IsOnCurveAffine determines if the affine point at (x, y) is a valid point on the secp256k1 curve. It checks for equality using the Weierstrass curve equation:
y² = x³ + ax + b
func IsOnCurveJacobi ¶
IsOnCurveJacobi determines if the jacobian point at (x, y, z) is a valid point on the secp256k1 curve. If z is nil, it is assumed to be 1, meaning x and y are affine coordinates. It uses the Weierstrass curve equation to determine whether the given point is valid:
y² = x³ + ax + b
Since a = 0 in secp256k1, we can simplify this to
y² = x³ + b
When using Jacobian coordinates, x and y are expressed as ratios with respect to z:
x = x / z² y = y / z³
Substituting and simplifying, we arrive at a much cleaner equation we can easily solve:
(y/z³)² = (x/z²)³ + b y²/z⁶ = x³/z⁶ + b y² = x³ + z⁶b
This approach is 2.5x more performant than converting a Jacobian point to affine coordinates first, because it does not require modular inversion to arrive at a result.
func IsValidScalar ¶
IsValidScalar returns true if the given integer is a valid secp256k1 scalar (private key), i.e. a number in the range [1, N-1] where N is the secp256k1 curve order.
func MultiplyAffine ¶
func MultiplyAffine( x1, y1 *big.Int, k *big.Int, precomputedTable PrecomputedTable, ) (x2, y2 *big.Int)
MultiplyAffine multiplies the given affine point (x1, y1) by the scalar value k in constant time.
If a PrecomputedTable is passed, MultiplyAffine will use the windowed multiplication method for fast computation. Otherwise, it will use the Montgomery Ladder algorithm.
It returns the resulting affine point (x2, y2).
Callers can construct and pass a PrecomputedTable which massively boosts performance of a MultiplyAffine call, at the cost of a larger up-front computational investment to build the precomputations. If a you plan to multiply the same point several times, precomputing is definitely worthwhile.
MultiplyAffine uses MultiplyJacobi under the hood, as it is about 30% faster than performing affine addition.
Example ¶
Construct an ECDH shared secret.
package main import ( "fmt" "math/big" "github.com/kklash/ekliptic" ) func main() { alicePriv, _ := new(big.Int).SetString("94a22a406a6977c1a323f23b9d7678ad08e822834d1df8adece84e30f0c25b6b", 16) bobPriv, _ := new(big.Int).SetString("55ba19100104cbd2842999826e99e478efe6883ac3f3a0c7571034321e0595cf", 16) var alicePub, bobPub struct{ x, y *big.Int } // derive public keys alicePub.x, alicePub.y = ekliptic.MultiplyBasePoint(alicePriv) bobPub.x, bobPub.y = ekliptic.MultiplyBasePoint(bobPriv) // Alice gives Bob her public key, Bob derives the secret bobSharedKey, _ := ekliptic.MultiplyAffine(alicePub.x, alicePub.y, bobPriv, nil) // Bob gives Alice his public key, Alice derives the secret aliceSharedKey, _ := ekliptic.MultiplyAffine(bobPub.x, bobPub.y, alicePriv, 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
func MultiplyAffineNaive ¶
MultiplyAffineNaive multiplies the given affine point by the scalar value k, using the Montgomery Ladder algorithm in constant-time.
This naive implementation uses affine doubling and addition under the hood, which is not desirable. It is made available only as a demonstration of how much faster Jacobian math is for elliptic curve multiplication operations. You should be using MultiplyAffine instead.
func MultiplyBasePoint ¶
MultiplyBasePoint multiplies the secp256k1 generator base point by the given integer k, and returns the resulting affine point (x, y). This uses a precomputed table for the secp256k1 base point to speed up multiplications.
This function is used to derive the public key for a private key k, among other uses.
Example ¶
Generate a public key from a private key.
package main import ( "fmt" "math/big" "github.com/kklash/ekliptic" ) func main() { privateKey, _ := new(big.Int).SetString("c370af8c091812ef7f6bfaffb494b1046fb25486c9873243b80826daef3ec583", 16) x, y := ekliptic.MultiplyBasePoint(privateKey) fmt.Println("Public key:") fmt.Printf(" x: %x\n", x) fmt.Printf(" y: %x\n", y) }
Output: Public key: x: 76cd66c6cca75278ff408ce67290537367719154ae2b96448327fe4033ddcfc7 y: 35663ecbb64397bb9bd79155a1e6b138c2fb8fa1f11355f8e9e97ddd88a78e49
func MultiplyJacobi ¶
func MultiplyJacobi( x1, y1, z1 *big.Int, k *big.Int, precomputedTable PrecomputedTable, ) (x2, y2, z2 *big.Int)
MultiplyJacobi multiplies the given Jacobian point (x1, y1, z1) by the scalar value k in constant time.
It returns the resulting Jacobian point (x2, y2, z2).
Callers can construct and pass a PrecomputedTable which massively boosts performance of a MultiplyJacobi call, at the cost of a larger up-front computational investment to build the precomputations. If a you plan to multiply the same point several times, precomputing is definitely worthwhile.
If a PrecomputedTable is passed, MultiplyJacobi will use the windowed multiplication method for fast computation. Otherwise, it will use the Montgomery Ladder algorithm.
MultiplyJacobi checks and panics if the given point you are multiplying is not actually on the secp256k1 curve, as this could leak private data about the scalar value k.
func Negate ¶
Negate returns the additive inverse of the given Y-coordinate modulo the curve prime modulus P. This can be used to negate a point, because:
(x, y) + (x, -y) = 0
y is expected to be within range [0, P-1].
func RandomScalar ¶
RandomScalar securely generates a random scalar value from the given source of randomness. Ensures the distribution of possible scalars is uniformly distributed from [1..N-1].
Use this function to generate private keys.
func SignECDSA ¶
SignECDSA signs a message hash z using the private key d, and a random (or deterministically derived) nonce k. It returns the resulting signature parts r and s.
Both the nonce k and the private key d should be generated with equal probability distribution over the range [1, Secp256k1_CurveOrder). SignECDSA panics if k or d is not within this range.
Example ¶
Sign a message digest.
package main import ( cryptorand "crypto/rand" "crypto/sha256" "fmt" "math/big" mathrand "math/rand" "github.com/kklash/ekliptic" ) func main() { randReader := mathrand.New(mathrand.NewSource(1)) key, _ := ekliptic.RandomScalar(randReader) // This could also come from RFC6979 (github.com/kklash/rfc6979) nonce, _ := cryptorand.Int(randReader, ekliptic.Secp256k1_CurveOrder) hashedMessage := sha256.Sum256([]byte("i love you")) hashedMessageInt := new(big.Int).SetBytes(hashedMessage[:]) r, s := ekliptic.SignECDSA(key, nonce, hashedMessageInt) fmt.Printf("r: %x\n", r) fmt.Printf("s: %x\n", s) var pub struct{ x, y *big.Int } pub.x, pub.y = ekliptic.MultiplyBasePoint(key) valid := ekliptic.VerifyECDSA(hashedMessageInt, r, s, pub.x, pub.y) fmt.Printf("valid: %v\n", valid) }
Output: r: 4a821d5ec008712983929de448b8afb6c24e5a1b97367b9a65b6220d7f083fe3 s: 381d053be61243d950865d7b8eb6b5ba48fbabfe7fda81af3183a184a02f5d51 valid: true
func SubAffine ¶
SubAffine subtracts two affine points on the secp256k1 curve:
P1 - P2 = P3 (x1, y1) - (x2, y2) = (x3, y3)
It returns the resulting affine point (x3, y3).
This function does not check point validity - it assumes you are passing valid points on the secp256k1 curve.
func SubJacobi ¶
SubJacobi subtracts two Jacobian coordinate points on the secp256k1 curve:
P1 - P2 = P3 (x1, y1, z1) - (x2, y2, z2) = (x3, y3, z3)
It returns the resulting Jacobian point (x3, y3, z3).
This function does not check point validity - it assumes you are passing valid points on the secp256k1 curve.
func ToAffine ¶
Jacobian coordinates are a three-dimensional representation of a 2d (affine) point, (Ax, Ay), in terms of three variables: (x, y, z) such that:
Ax = x / z² Ay = y / z³
ToAffine converts the given jacobian coordinates to affine coordinates, normalizing x and y, and setting z = 1. This is an expensive operation, as it involves modular inversion of z to perform finite field division.
func VerifyECDSA ¶
VerifyECDSA returns true if the given signature (r, s) is a valid signature on message hash z from the given public key (pubX, pubY). Note that non-canonical ECDSA signatures (where s > N/2) are acceptable.
func Weierstrass ¶
Weierstrass solves the Weierstrass form elliptic curve equation for y, given an affine value of x:
y² = x³ + ax + b mod P
...where a = 0, b = 7 and P is Secp256k1_P - the secp256k1 curve constants.
It returns the two possible values of y: an even value and an odd value. You could also call this function "GetYValues(x)". This is useful for uncompressing public keys, which in secp256k1 often supply only the-x coordinate, and maybe a specifier indicating whether the y-coordinate is even or odd.
Returns two nil values if x is not a valid coordinate on the secp256k1 curve. Returns two zero values if x is zero.
Example ¶
Find possible Y-coordinates for an X. Used to uncompress a public key, where you may only have the full X-coordinate of the public key.
package main import ( "encoding/hex" "fmt" "math/big" "github.com/kklash/ekliptic" ) func main() { compressedKey, _ := hex.DecodeString("030000000000000000000000000000000000000000000000000000000000000001") publicKeyX := new(big.Int).SetBytes(compressedKey[1:]) evenY, oddY := ekliptic.Weierstrass(publicKeyX) var publicKeyY *big.Int if compressedKey[0]%2 == 0 { publicKeyY = evenY } else { publicKeyY = oddY } fmt.Println("uncompressed key:") fmt.Printf("x: %.64x\n", publicKeyX) fmt.Printf("y: %.64x\n", publicKeyY) }
Output: uncompressed key: x: 0000000000000000000000000000000000000000000000000000000000000001 y: bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441
Types ¶
type Curve ¶
type Curve struct {
// contains filtered or unexported fields
}
Curve satisfies crypto/elliptic.Curve using the secp256k1 curve paramters.
Example ¶
package main import ( "crypto/ecdsa" "crypto/rand" "crypto/sha256" "fmt" "math/big" "github.com/kklash/ekliptic" ) func main() { d, _ := new(big.Int).SetString("18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725", 16) pubX, pubY := ekliptic.MultiplyBasePoint(d) key := &ecdsa.PrivateKey{ D: d, PublicKey: ecdsa.PublicKey{ Curve: new(ekliptic.Curve), X: pubX, Y: pubY, }, } hashedMessage := sha256.Sum256([]byte("i love you")) r, s, err := ecdsa.Sign(rand.Reader, key, hashedMessage[:]) if err != nil { panic("failed to compute signature: " + err.Error()) } if ecdsa.Verify(&key.PublicKey, hashedMessage[:], r, s) { fmt.Println("verified ECDSA signature using crypto/ecdsa") } }
Output: verified ECDSA signature using crypto/ecdsa
func (*Curve) IsOnCurve ¶
IsOnCurve reports whether the given (x,y) lies on the curve. Satisfies elliptic.Curve. Note: The elliptic.Curve interface requires that infinity is NOT on the curve.
func (*Curve) Params ¶
func (c *Curve) Params() *elliptic.CurveParams
Params returns the parameters for the curve. Satisfies elliptic.Curve.
func (*Curve) ScalarBaseMult ¶
ScalarBaseMult returns k*G, where G is the base point of the group and k is an integer in big-endian form. Satisfies elliptic.Curve.
type PrecomputedTable ¶
PrecomputedTable is a table in the form of a 2d slice, holding precomputed multiplications of a point P. Each entry in the table is the product of P and some multiple of a power of 2.
The table is laid out like this:
0 1 2 3 4 ... 15 ----------------------- 2^0 |* * * * * ... * 2^4 |* * * * * ... * 2^8 |* * * * * ... * 2^16 |* * * * * ... * ... |* * * * * ... * 2^252|* * * * * ... *
Each row i of the table holds the multiplications of the point, doubled 4i times. i should be less than 64.
Each column j of the table holds the doublings of the point, multiplied j times. j should be less than 16.
Indexing the table like so:
table[i][j]
...Looks up the following precomputed point multiplication:
2^(4i) * j * P
func NewPrecomputedTable ¶
func NewPrecomputedTable(x, y *big.Int) PrecomputedTable
NewPrecomputedTable computes a PrecomputedTable used for speeding up multiplications of a fixed affine point (x, y).
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
genprecompute precomputes a base-point table for the secp256k1 generator point, and writes it as generated code to a given output file.
|
genprecompute precomputes a base-point table for the secp256k1 generator point, and writes it as generated code to a given output file. |