Skip to content

Instantly share code, notes, and snippets.

@gz-c
Created March 1, 2019 04:08
Show Gist options
  • Save gz-c/bf70ce96b2488e5ccca65900086c75f5 to your computer and use it in GitHub Desktop.
Save gz-c/bf70ce96b2488e5ccca65900086c75f5 to your computer and use it in GitHub Desktop.
Hal Finney efficiently computable endomorphism parameters secp256k1
Source: https://bitcointalk.org/index.php?topic=3238.msg45565#msg45565
Hal Finney's explanation of secp256k1 "efficiently computable endomorphism" parameters used secp256k1 libraries, archived:
I implemented an optimized ECDSA verify for the secp256k1 curve used by Bitcoin, based on pages 125-129 of the Guide to Elliptic Curve Cryptography, by Hankerson, Menezes and Vanstone. I own the book but I also found a PDF on a Russian site which is more convenient.
secp256k1 uses the following prime for its x and y coordinates:
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
and the curve order is:
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
The first step is to compute values beta, lambda such that for any curve point Q = (x,y):
lambda * Q = (beta*x mod p, y)
This is the so-called efficiently computable endomorphism, and what it means is, you can multiply any curve point by this special value lambda very quickly, by doing a single mod-p multiply.
The book tells (well, hints) how to compute lambda and beta, and here are the values I found:
lambda = 0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72
beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee
Given that we can multiply by lambda quickly, here is the trick to compute k*Q. First use the shortcut to compute Q' = lambda*Q. Next, k must be decomposed into two parts k1 and k2, each about half the width of n, such that:
k = k1 + k2*lambda mod n
Then
k*Q = (k1 + k2*lambda)*Q = k1*Q + k2*lambda*Q = k1*Q + k2*Q'
That last expression can be evaluated efficiently via a double multiply algorithm, and since k1 and k2 are half length, we get the speedup.
The missing piece is splitting k into k1 and k2. This uses the following 4 values:
a1 = 0x3086d221a7d46bcde86c90e49284eb15
b1 = -0xe4437ed6010e88286f547fa90abfe4c3
a2 = 0x114ca50f7a8e2f3f657c1108d9d44cfd8
b2 = 0x3086d221a7d46bcde86c90e49284eb15
(it's ok that a1 = b2)
Use these as follows to split k:
c1 = RoundToNearestInteger(b2*k/n)
c2 = RoundToNearestInteger(-b1*k/n)
k1 = k - c1*a1 - c2*a2
k2 = -c1*b1 - c2*b2
With all this, I measure about a 25% speedup on raw signature verifications. For Bitcoin, initial block load from a wifi-connected local node is reduced from:
real 36m21.537s
user 24m43.277s
sys 0m27.950s
to:
real 32m59.777s
user 18m21.145s
sys 0m28.262s
Not a big difference, and it would probably be even less significant when fetching over the net.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment