Merge bitcoin/bitcoin#36327: test: speed up secp256k1 fixed-base multiplication and Schnorr signing
What changed, and why it matters
This commit is a pure performance optimization for Bitcoin Core's internal test framework. It changes how the test code performs certain mathematical operations on the secp256k1 elliptic curve, making tests run faster. There is no change to live network code, wallet behavior, consensus rules, or cryptographic security. It does not fix a vulnerability or introduce a known security risk.
No security action required. Treat as a normal test-framework performance improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit modifies test/functional/test_framework/crypto/secp256k1.py, a Python implementation of secp256k1 used only by functional tests. It replaces a bit-by-bit fixed-base scalar multiplication with a 4-bit windowed approach and adds an LRU cache for generator multiplications. The goal is to reduce runtime of taproot-related functional tests. The change is confined to test infrastructure and does not affect production signing, verification, or consensus code.
Changed components
test/functional/test_framework/crypto/secp256k1.pyInspect captured patch +26 / −13
### test/functional/test_framework/crypto/secp256k1.py
@@ -15,6 +15,7 @@
* G: the secp256k1 generator point
"""
+import functools
import unittest
from hashlib import sha256
from test_framework.util import assert_equal, assert_not_equal
@@ -230,7 +231,7 @@ def mul(*aps):
def __rmul__(self, a):
"""Multiply an integer with a group element."""
if self == G:
- return FAST_G.mul(a)
+ return mul_G(a % GE.ORDER)
return GE.mul((a, self))
def __neg__(self):
@@ -322,32 +323,44 @@ def __repr__(self):
class FastGEMul:
"""Table for fast multiplication with a constant group element.
- Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with
- its powers of 2:
+ Speed up scalar multiplication with a fixed point P by using a precomputed lookup table.
+ The scalar is split into WINDOW-bit chunks, and for every chunk position i the table holds
+ a row [(0 * 2^(WINDOW*i)) * P, (1 * 2^(WINDOW*i)) * P, ..., ((2^WINDOW-1) * 2^(WINDOW*i)) * P]:
- table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P]
+ table[i][j] = j * (2^(WINDOW*i)) * P
- During multiplication, the points corresponding to each bit set in the scalar are added up,
- i.e. on average ~128 point additions take place.
+ During multiplication, one table entry per chunk is added up, i.e. 256/WINDOW point
+ additions take place (instead of ~128 on average for a bit-by-bit approach).
"""
+ WINDOW = 4
+
def __init__(self, p):
- self.table = [p] # table[i] = (2^i) * p
- for _ in range(255):
- p = p + p
- self.table.append(p)
+ self.table = [] # table[i][j] = j * (2^(WINDOW*i)) * p
+ for _ in range((256 + self.WINDOW - 1) // self.WINDOW):
+ row = [GE()]
+ for _ in range((1 << self.WINDOW) - 1):
+ row.append(row[-1] + p)
+ self.table.append(row)
+ for _ in range(self.WINDOW):
+ p = p + p
def mul(self, a):
result = GE()
a = a % GE.ORDER
- for bit in range(a.bit_length()):
- if a & (1 << bit):
- result += self.table[bit]
+ mask = (1 << self.WINDOW) - 1
+ for row in self.table:
+ result += row[a & mask]
+ a >>= self.WINDOW
return result
# Precomputed table with multiples of G for fast multiplication
FAST_G = FastGEMul(G)
+@functools.lru_cache(maxsize=1024)
+def mul_G(a):
+ return FAST_G.mul(a)
+
class TestFrameworkSecp256k1(unittest.TestCase):
def test_H(self):
H = sha256(G.to_bytes_uncompressed()).digest()Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.