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.
341 lines
10 KiB
Go
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()
|
|
}
|