You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
10 KiB
340 lines
10 KiB
// 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() |
|
}
|
|
|