Files
next.orly.dev/pkg/crypto/ec/secp256k1/precomps/genprecomps.go
mleku 110223fc4e Migrate internal module imports to unified package path.
Replaced legacy `*.orly` module imports with `next.orly.dev/pkg` paths across the codebase for consistency. Removed legacy `go.mod` files from sub-packages, consolidating dependency management. Added Dockerfiles and configurations for benchmarking environments.
2025-09-12 16:12:31 +01:00

341 lines
10 KiB
Go

// Copyright 2015 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
// References:
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
import (
"fmt"
"math/big"
"os"
"lol.mleku.dev/chk"
"lol.mleku.dev/log"
"next.orly.dev/pkg/crypto/ec/secp256k1"
)
// curveParams houses the secp256k1 curve parameters for convenient access.
var curveParams = secp256k1.Params()
// bigAffineToJacobian takes an affine point (x, y) as big integers and converts
// it to Jacobian point with Z=1.
func bigAffineToJacobian(x, y *big.Int, result *secp256k1.JacobianPoint) {
result.X.SetByteSlice(x.Bytes())
result.Y.SetByteSlice(y.Bytes())
result.Z.SetInt(1)
}
// serializedBytePoints returns a serialized byte slice which contains all possible points per
// 8-bit window. This is used to when generating compressedbytepoints.go.
func serializedBytePoints() []byte {
// Calculate G^(2^i) for i in 0..255. These are used to avoid recomputing
// them for each digit of the 8-bit windows.
doublingPoints := make([]secp256k1.JacobianPoint, curveParams.BitSize)
var q secp256k1.JacobianPoint
bigAffineToJacobian(curveParams.Gx, curveParams.Gy, &q)
for i := 0; i < curveParams.BitSize; i++ {
// Q = 2*Q.
doublingPoints[i] = q
secp256k1.DoubleNonConst(&q, &q)
}
// Separate the bits into byte-sized windows.
curveByteSize := curveParams.BitSize / 8
serialized := make([]byte, curveByteSize*256*2*32)
offset := 0
for byteNum := 0; byteNum < curveByteSize; byteNum++ {
// Grab the 8 bits that make up this byte from doubling points.
startingBit := 8 * (curveByteSize - byteNum - 1)
windowPoints := doublingPoints[startingBit : startingBit+8]
// Compute all points in this window, convert them to affine, and
// serialize them.
for i := 0; i < 256; i++ {
var point secp256k1.JacobianPoint
for bit := 0; bit < 8; bit++ {
if i>>uint(bit)&1 == 1 {
secp256k1.AddNonConst(&point, &windowPoints[bit], &point)
}
}
point.ToAffine()
point.X.PutBytesUnchecked(serialized[offset:])
offset += 32
point.Y.PutBytesUnchecked(serialized[offset:])
offset += 32
}
}
return serialized
}
// sqrt returns the square root of the provided big integer using Newton's
// method. It's only compiled and used during generation of pre-computed
// values, so speed is not a huge concern.
func sqrt(n *big.Int) *big.Int {
// Initial guess = 2^(log_2(n)/2)
guess := big.NewInt(2)
guess.Exp(guess, big.NewInt(int64(n.BitLen()/2)), nil)
// Now refine using Newton's method.
big2 := big.NewInt(2)
prevGuess := big.NewInt(0)
for {
prevGuess.Set(guess)
guess.Add(guess, new(big.Int).Div(n, guess))
guess.Div(guess, big2)
if guess.Cmp(prevGuess) == 0 {
break
}
}
return guess
}
// endomorphismParams houses the parameters needed to make use of the secp256k1
// endomorphism.
type endomorphismParams struct {
lambda *big.Int
beta *big.Int
a1, b1 *big.Int
a2, b2 *big.Int
z1, z2 *big.Int
}
// endomorphismVectors runs the first 3 steps of algorithm 3.74 from [GECC] to
// generate the linearly independent vectors needed to generate a balanced
// length-two representation of a multiplier such that k = k1 + k2λ (mod N) and
// returns them. Since the values will always be the same given the fact that N
// and λ are fixed, the final results can be accelerated by storing the
// precomputed values.
func endomorphismVectors(lambda *big.Int) (a1, b1, a2, b2 *big.Int) {
// This section uses an extended Euclidean algorithm to generate a
// sequence of equations:
// s[i] * N + t[i] * λ = r[i]
nSqrt := sqrt(curveParams.N)
u, v := new(big.Int).Set(curveParams.N), new(big.Int).Set(lambda)
x1, y1 := big.NewInt(1), big.NewInt(0)
x2, y2 := big.NewInt(0), big.NewInt(1)
q, r := new(big.Int), new(big.Int)
qu, qx1, qy1 := new(big.Int), new(big.Int), new(big.Int)
s, t := new(big.Int), new(big.Int)
ri, ti := new(big.Int), new(big.Int)
a1, b1, a2, b2 = new(big.Int), new(big.Int), new(big.Int), new(big.Int)
found, oneMore := false, false
for u.Sign() != 0 {
// q = v/u
q.Div(v, u)
// r = v - q*u
qu.Mul(q, u)
r.Sub(v, qu)
// s = x2 - q*x1
qx1.Mul(q, x1)
s.Sub(x2, qx1)
// t = y2 - q*y1
qy1.Mul(q, y1)
t.Sub(y2, qy1)
// v = u, u = r, x2 = x1, x1 = s, y2 = y1, y1 = t
v.Set(u)
u.Set(r)
x2.Set(x1)
x1.Set(s)
y2.Set(y1)
y1.Set(t)
// As soon as the remainder is less than the sqrt of n, the
// values of a1 and b1 are known.
if !found && r.Cmp(nSqrt) < 0 {
// When this condition executes ri and ti represent the
// r[i] and t[i] values such that i is the greatest
// index for which r >= sqrt(n). Meanwhile, the current
// r and t values are r[i+1] and t[i+1], respectively.
//
// a1 = r[i+1], b1 = -t[i+1]
a1.Set(r)
b1.Neg(t)
found = true
oneMore = true
// Skip to the next iteration so ri and ti are not
// modified.
continue
} else if oneMore {
// When this condition executes ri and ti still
// represent the r[i] and t[i] values while the current
// r and t are r[i+2] and t[i+2], respectively.
//
// sum1 = r[i]^2 + t[i]^2
rSquared := new(big.Int).Mul(ri, ri)
tSquared := new(big.Int).Mul(ti, ti)
sum1 := new(big.Int).Add(rSquared, tSquared)
// sum2 = r[i+2]^2 + t[i+2]^2
r2Squared := new(big.Int).Mul(r, r)
t2Squared := new(big.Int).Mul(t, t)
sum2 := new(big.Int).Add(r2Squared, t2Squared)
// if (r[i]^2 + t[i]^2) <= (r[i+2]^2 + t[i+2]^2)
if sum1.Cmp(sum2) <= 0 {
// a2 = r[i], b2 = -t[i]
a2.Set(ri)
b2.Neg(ti)
} else {
// a2 = r[i+2], b2 = -t[i+2]
a2.Set(r)
b2.Neg(t)
}
// All done.
break
}
ri.Set(r)
ti.Set(t)
}
return a1, b1, a2, b2
}
// deriveEndomorphismParams calculates and returns parameters needed to make use
// of the secp256k1 endomorphism.
func deriveEndomorphismParams() [2]endomorphismParams {
// roots returns the solutions of the characteristic polynomial of the
// secp256k1 endomorphism.
//
// The characteristic polynomial for the endomorphism is:
//
// X^2 + X + 1 ≡ 0 (mod n).
//
// Solving for X:
//
// 4X^2 + 4X + 4 ≡ 0 (mod n) | (*4, possible because gcd(4, n) = 1)
// (2X + 1)^2 + 3 ≡ 0 (mod n) | (factor by completing the square)
// (2X + 1)^2 ≡ -3 (mod n) | (-3)
// (2X + 1) ≡ ±sqrt(-3) (mod n) | (sqrt)
// 2X ≡ ±sqrt(-3) - 1 (mod n) | (-1)
// X ≡ (±sqrt(-3)-1)*2^-1 (mod n) | (*2^-1)
//
// So, the roots are:
// X1 ≡ (-(sqrt(-3)+1)*2^-1 (mod n)
// X2 ≡ (sqrt(-3)-1)*2^-1 (mod n)
//
// It is also worth noting that X is a cube root of unity, meaning
// X^3 - 1 ≡ 0 (mod n), hence it can be factored as (X - 1)(X^2 + X + 1) ≡ 0
// and (X1)^2 ≡ X2 (mod n) and (X2)^2 ≡ X1 (mod n).
roots := func(prime *big.Int) [2]big.Int {
var result [2]big.Int
one := big.NewInt(1)
twoInverse := new(big.Int).ModInverse(big.NewInt(2), prime)
negThree := new(big.Int).Neg(big.NewInt(3))
sqrtNegThree := new(big.Int).ModSqrt(negThree, prime)
sqrtNegThreePlusOne := new(big.Int).Add(sqrtNegThree, one)
negSqrtNegThreePlusOne := new(big.Int).Neg(sqrtNegThreePlusOne)
result[0].Mul(negSqrtNegThreePlusOne, twoInverse)
result[0].Mod(&result[0], prime)
sqrtNegThreeMinusOne := new(big.Int).Sub(sqrtNegThree, one)
result[1].Mul(sqrtNegThreeMinusOne, twoInverse)
result[1].Mod(&result[1], prime)
return result
}
// Find the λ's and β's which are the solutions for the characteristic
// polynomial of the secp256k1 endomorphism modulo the curve order and
// modulo the field order, respectively.
lambdas := roots(curveParams.N)
betas := roots(curveParams.P)
// Ensure the calculated roots are actually the roots of the characteristic
// polynomial.
checkRoots := func(foundRoots [2]big.Int, prime *big.Int) {
// X^2 + X + 1 ≡ 0 (mod p)
one := big.NewInt(1)
for i := 0; i < len(foundRoots); i++ {
root := &foundRoots[i]
result := new(big.Int).Mul(root, root)
result.Add(result, root)
result.Add(result, one)
result.Mod(result, prime)
if result.Sign() != 0 {
panic(
fmt.Sprintf(
"%[1]x^2 + %[1]x + 1 != 0 (mod %x)", root,
prime,
),
)
}
}
}
checkRoots(lambdas, curveParams.N)
checkRoots(betas, curveParams.P)
// checkVectors ensures the passed vectors satisfy the equation:
// a + b*λ ≡ 0 (mod n)
checkVectors := func(a, b *big.Int, lambda *big.Int) {
result := new(big.Int).Mul(b, lambda)
result.Add(result, a)
result.Mod(result, curveParams.N)
if result.Sign() != 0 {
panic(
fmt.Sprintf(
"%x + %x*lambda != 0 (mod %x)", a, b,
curveParams.N,
),
)
}
}
var endoParams [2]endomorphismParams
for i := 0; i < 2; i++ {
// Calculate the linearly independent vectors needed to generate a
// balanced length-two representation of a scalar such that
// k = k1 + k2*λ (mod n) for each of the solutions.
lambda := &lambdas[i]
a1, b1, a2, b2 := endomorphismVectors(lambda)
// Ensure the derived vectors satisfy the required equation.
checkVectors(a1, b1, lambda)
checkVectors(a2, b2, lambda)
// Calculate the precomputed estimates also used when generating the
// aforementioned decomposition.
//
// z1 = floor(b2<<320 / n)
// z2 = floor(((-b1)%n)<<320) / n)
const shift = 320
z1 := new(big.Int).Lsh(b2, shift)
z1.Div(z1, curveParams.N)
z2 := new(big.Int).Neg(b1)
z2.Lsh(z2, shift)
z2.Div(z2, curveParams.N)
params := &endoParams[i]
params.lambda = lambda
params.beta = &betas[i]
params.a1 = a1
params.b1 = b1
params.a2 = a2
params.b2 = b2
params.z1 = z1
params.z2 = z2
}
return endoParams
}
func main() {
if _, err := os.Stat(".git"); chk.T(err) {
fmt.Printf("File exists\n")
_, _ = fmt.Fprintln(
os.Stderr,
"This generator must be run with working directory at the root of"+
" the repository",
)
os.Exit(1)
}
serialized := serializedBytePoints()
embedded, err := os.Create("secp256k1/rawbytepoints.bin")
if err != nil {
log.F.Ln(err)
os.Exit(1)
}
n, err := embedded.Write(serialized)
if err != nil {
panic(err)
}
if n != len(serialized) {
fmt.Printf(
"failed to write all of byte points, wrote %d expected %d",
n, len(serialized),
)
panic("fail")
}
_ = embedded.Close()
}