ecc.curves package¶
Submodules¶
ecc.curves.curve module¶
The prime-order Curve and the multiplications built on it.
mult, double_mult_var and multi_mult_var dispatch secp256k1 to the libsecp256k1 bindings and answer every other curve – and the cases the bindings cannot express – with curve_group’s arithmetic.
What this module exports is the class, the three multiplications, PreparedPoint – the one way a caller has of saying that a point of its own will come back, so that the tables built for it are kept – the catalogue, the standards it is the union of, and the *_params2 each of those is built from, which is what test_catalogued_curves rebuilds every curve out of with every check on. Beside the multiplications are add, sum_var, tweak_add_var and is_x_coordinate_var, each checking its arguments for a private twin the library composes internally, and TweakChain, which checks its own. The standards are SEC2v1, SEC2v2, NIST and Brainpool, and this module is where ecc.curves keeps them: a standard is a question about a curve, not a way of finding one.
datadir stays out: it is where this package keeps the json files loaded below, so it answers a question about the installation rather than about a curve. The loader’s own names are underscored, a with target and a for target being module globals like any other.
- class ecc.curves.curve.Curve(p: bytes | str | bytearray | memoryview | int, a: bytes | str | bytearray | memoryview | int, b: bytes | str | bytearray | memoryview | int, G: tuple[int, int], n: bytes | str | bytearray | memoryview | int, cofactor: int, weakness_check: bool = True, order_check: bool = True, name: str | None = None)[source]¶
Bases:
CurveGroupCyclic subgroup of prime order n, generated by G, of the curve points.
The subgroup is ⟨G⟩ = {INF, G, 2G, …, (n-1)G}, of prime order n, inside the group of all the points of the curve — the CurveGroup this is built on, whose order is n times the cofactor h. The curve is that group and this is a subgroup of it, so the name is the wrong way round on purpose: this is the only group anything else in this package multiplies in, and Curve is what a caller asks for.
Two things follow from n being prime, and ecc.ecc relies on both: the integers modulo n are a field, so every scalar but zero has an inverse — the nonce and s that ecdsa inverts, the challenge that ssa’s key recovery does — and the subgroup has no subgroup other than itself and {INF}, so every point of ⟨G⟩ but INF is a generator of it.
That second guarantee is about the points already known to be in ⟨G⟩; it says nothing about a point a caller hands in. A curve of cofactor 1 has no other point to confuse ⟨G⟩ with, so there confinement to a small subgroup is not possible either way. A curve of cofactor above 1 does: a point outside ⟨G⟩ can have any order dividing the cofactor, small and attacker-chosen, and mult does not refuse one on its own, reducing whatever scalar it is given mod n regardless of the point’s own order. sec_point.point_from_pub_key and point_from_octets_var are where a point read from the outside is confined to ⟨G⟩, refusing one whose unreduced n·Q is not INF (issue btclib-org/ecc#15).
n is a parameter and not a computed quantity: the order of G is the order of the whole group divided by the index of ⟨G⟩ in it, and counting the points of a curve this size is Schoof-Elkies-Atkin rather than arithmetic on p, a and b. What the constructor does compute is that the parameter is the order, and that is where the primality of n earns its second keep. nG = INF proves only that n annihilates G, i.e. that the order of G divides n; pinning it to n in general asks for (n/q)G ≠ INF for every prime q dividing n, which is the factorization of n — a number no curve publishes alongside its parameters. With n prime and G ≠ INF the single nG = INF is the whole of it.
Which is also why the two classes are the whole hierarchy, with no third one between them for the cyclic subgroup of unstated order: ⟨G⟩ carrying an n it cannot verify, or no n at all, is a cyclic group that cannot say how many elements it has, cannot reduce a scalar, and cannot bound a private key. Nothing here could take one as an argument.
- is_on_curve(Q: tuple[int, int]) bool[source]¶
Return True if the point is on the curve.
As CurveGroup.is_on_curve, with one further refusal: a curve of cofactor above 1 can hold a real point at y == 0, the curve’s own element of order 2, and that tuple is indistinguishable from INF, which is spelled the same way. CurveGroup has no G and no n and reads every such tuple as INF unconditionally, which is correct there – find_all_points walks a bare group, off ⟨G⟩ entirely, and a real two-torsion point is exactly as legitimate a member of it as any other. Here, where ⟨G⟩ is what everything else in this package multiplies in, the ambiguity is refused instead (issue btclib-org/ecc#16). A curve of cofactor 1 never takes this branch: a real root at y == 0 makes the curve’s full order even, and n, being prime, is odd.
- class ecc.curves.curve.PreparedPoint(point: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectA point whose multiplication tables are kept, because it will come back.
The tables of curve_group are memoized on the point, the curve and the table’s parameters already, so a repeated point would find its own: what is missing is anyone to say that a point is repeated. Only the generator is assumed to be, and everything else is treated as arriving once – which is right for most callers and wrong for a few, and no measurement can tell which a caller is. This is where a caller says so.
Two tables answer to it, one per operation:
mult takes the fixed-base comb of the generator instead of the GLV endomorphism, which builds nothing: a warm call is cheaper, and the table a build to pay for. Issue btclib-org/ecc#194 holds the break-even and its measurement. A point that comes back is where it pays: dh.diffie_hellman against a counterparty, a taproot internal key tweaked repeatedly, pedersen against a fixed second generator.
a verification under it – dsa and ssa both take one where they take a public key – memoizes the wNAF tables of the key’s two endomorphism halves at _FIXED_POINT_W instead of rebuilding them at _DOUBLE_MULT_W per signature: 2 tables built per verification become 0, and the verification a fifth cheaper for ECDSA and for BIP340 alike. Break-even is 22 signatures under the one key, the first verification costing several times what a bare key’s does.
Both are the Python arithmetic, and what puts a call on them is that call’s own guard: _libsecp256k1_serves – the process-wide switch, which an installation without the bindings leaves off and which set_libsecp256k1_serving and ECC_NO_LIBSECP256K1 turn off in a process that has them, and the curve – with whatever that call ands onto it. Neither call passes a hash function: a verification’s is spent on its own module’s guard, and _jac_double_mult_var under that guard asks the predicate again with none, so a hash function the bindings do not serve leaves the multiplication delegated and the tables handed to it unused. Where a call’s whole guard holds there is nothing here to reach – libsecp256k1 verifies in a fraction of either arm and holds tables of its own – so handing a prepared point in on that path is not an error and costs nothing; it simply buys nothing.
Preparing is a caller’s word and never inferred, and the memory is why: the tables are per distinct point, so a library that memoized whatever public key arrived would hold a few MB for keys nobody will see again – issue btclib-org/btclib#287, the bound _cached_base58_decode and pedersen.second_generator hold too. What bounds it here is that nothing is prepared unless asked, and beyond that the lru_cache maxsize the tables live under.
Nothing is built by the constructor. It parses and validates the point, which is the other half of what a verifier repeats – a decompression per signature, on the Python path where a compressed key is a field square root – and leaves the tables to the first multiplication that wants them, because which of the two families above is wanted is a question only that call answers.
On a curve with a cofactor the constructor also asks, by one multiplication by n, whether the point is in ⟨G⟩. A point outside it can have a multiple of order 2, whose affine form (x, 0) the comb’s tables cannot tell from infinity. So mult sends such a point to the arm curve.mult takes, which keeps no table. A point of ⟨G⟩ has no multiple of order 2, n being an odd prime.
The verification figures are measured on an Apple M5, macOS 26.6, arm64, CPython 3.14, with curve._libsecp256k1_available set to False; best of five alternating rounds of 300 to 800 calls, and the median of seven for the cold rows, each on a freshly derived point so that the tables are built rather than found. A working desktop rather than a quiesced machine: a ratio, not a figure to quote.
- ec¶
the curve it was validated against.
- Type:
- fixed¶
the Jacobian set a verification hands straight down, derived by the constructor from the two above; the comment beside the field is what it holds and why it is derived.
- Parameters:
point – the point to prepare, on the curve and not infinity.
ec – the curve it belongs to.
- Raises:
EccValueError – if the point is not on the curve, or is infinity, which has no tables and multiplies to itself.
- mult(m_int: bytes | str | bytearray | memoryview | int) tuple[int, int][source]¶
Return m*point, through the tables this point keeps.
curve.mult with the fixed-base arm taken for this point, where it is in ⟨G⟩, instead of only for the generator; everything else about the call, the dispatch to the bindings included, is the same.
- class ecc.curves.curve.TweakChain(base: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectOne point, many tweaks of it, and one parse on the far side.
_tweak_add_var above crosses the boundary once per tweak: the point is serialized here, libsecp256k1 parses it, tweaks it and serializes the answer, and this side reads that back. A caller tweaking one point over and over pays the parse at every tweak, for a point that has not changed since the last one – BIP352’s scan_outputs is the caller, walking k upward from one spend key.
The tweaks asked of this are absolute, each measured from the point the chain was built on, because that is what the caller means by t_k. What crosses is their differences:
P_k = B + t_k*G P_(k+1) = P_k + (t_(k+1) - t_k)*G
which makes many tweaks of one point a chain of steps, each step’s output the next one’s input, and keys.PubkeyTweakChain is the object that holds the parsed point across such a chain. So this needs no fan-out entry point of its own, and the bindings have none: the fan-out is a chain read the other way round. Sixteen tweaks of one point cost less than sixteen separate ones, the difference being the fifteen parses that do not happen; what that is worth at the one caller, per number of outputs found, is in btclib-org/btclib’s CHANGELOG, in the entry that took it.
Nothing is required of the order the tweaks arrive in. The difference is taken modulo n, so the same tweak may be asked for twice – a zero step, which libsecp256k1 adds like any other and answers with the point it already holds, its header saying so of every tweak it takes – and a later tweak may be smaller than an earlier one. An instance is reusable for that reason: the base never moves, and what is held beside it is only the tweak the next difference is measured from.
- Parameters:
base – the point every tweak is added to.
ec – the curve it belongs to.
- Raises:
EccTypeError – if the curve is not a Curve.
EccValueError – if the point is not on the curve.
- ecc.curves.curve.add(P: tuple[int, int], Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return P + Q, for two points that may be secret.
_add behind a check of its arguments: the curve, and both points on the curve, which on the bindings arm the parse inside _add completes. sum_var is the sum of points that are public.
The contract differs by arm. Where the libsecp256k1 bindings serve, which is for secp256k1 while is_libsecp256k1_serving() is True, the sum is secp256k1_ec_pubkey_combine: the call is constant time on the C side of the boundary, and the Python steps around it are not. Everywhere else it is the Python arithmetic on two blinded points, which is not constant time. SECURITY.md’s Limitations, not vulnerabilities says which Python steps those are, and what each arm leaves.
Where the bindings serve, the Python checks only the form of each point, and the parse of secp256k1_ec_pubkey_combine proves a finite one is on the curve: require_on_curve runs only on the path that refuses, to raise what it raises on the other arm. _mult_sec makes the same trade for a key handed over as octets.
- ecc.curves.curve.double_mult_var(u: Public, H: tuple[int, int], v: Public, Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Double scalar multiplication (u*H + v*Q).
- ecc.curves.curve.is_libsecp256k1_serving() bool[source]¶
Return True if the bindings are what this process delegates to.
One question and not two: installed, and not refused. A caller has no use for the difference – what it can act on is whether the answer it is about to get comes from libsecp256k1 or from the Python arithmetic – and two observable states where there is one is how a caller comes to handle only the state it happened to meet.
The public reading of the seam every dispatch consults. It is what a project built on this package asks when it must not check libsecp256k1 with libsecp256k1: Bitcoin Core’s own test framework keeps that rule – crypto/secp256k1.py is “designed for ease of understanding, not performance” – and issue btclib-org/btclib#198 is btclib’s side of it.
- ecc.curves.curve.is_x_coordinate_var(x: Public, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) bool[source]¶
Return True if x is the x-coordinate of a point of the curve.
_is_x_coordinate_var behind a check of its arguments: a caller that reads a compressed key and has no use for its y – an xpub’s key is one, parsed at every level of a derivation – asks this rather than lifting the point. An integer that is no field element is False.
- ecc.curves.curve.mult(m_int: bytes | str | bytearray | memoryview | int, Q: tuple[int, int] | None = None, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Elliptic curve scalar multiplication.
- ecc.curves.curve.multi_mult_var(scalars: Sequence[Public], points: Sequence[tuple[int, int]], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return the multi scalar multiplication u1*Q1 + … + un*Qn.
Interleaved wNAF on few scalars, Bos-Coster on many: curve_group’s _multi_mult_var dispatches on the count, at the size the two measure the same. On secp256k1 the bindings serve the whole sum instead.
The callers handing it many scalars at once are musig2’s key aggregation, frost’s interpolation over its public shares, and the Python arm of _multi_mult_x_only_var, which serves ssa’s batch verification and ssa_halfagg’s aggregate verification. On the delegated arm those verifications reach _libsecp256k1_multi_mult_var_ with their terms as octets, this signature taking points and a point being what their terms would have to be lifted into for the multiplication to write them straight back out.
- ecc.curves.curve.set_libsecp256k1_serving(*, serving: bool, required: bool = False) None[source]¶
Ask for the bindings, or for the Python arithmetic, from here on.
Process-wide and immediate: every dispatch in the package asks _libsecp256k1_serves, and that predicate reads the global this assigns, so nothing has to be re-imported and no module keeps an answer of its own.
serving=True with the bindings not installed is a request that cannot be served, and is refused rather than silently ignored: a caller that asked for C and got Python would be timing Python and calling it C. is_libsecp256k1_serving is how the answer is read back.
required=True goes further: a call on secp256k1 that the bindings decline for something the caller chose raises EccRuntimeError instead of running the Python arithmetic; SECURITY.md says which choices. With serving=False it raises EccValueError: a caller asking for both would get neither.
The environment variable is the other way in, and the earlier one: ECC_NO_LIBSECP256K1 set to a non-empty value makes the initial state False, which is what a test runner wants – it settles before the first import, where this function cannot. It is not a lock: any code in the process can reverse it with this function, so a test framework that must not run on the bindings checks is_libsecp256k1_serving itself, where it needs the answer.
The first serving=True after that variable refused the bindings checks them against known answers. If one is wrong it raises EccRuntimeError and the state is as it was.
- ecc.curves.curve.sum_var(points: Sequence[tuple[int, int]], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return the sum of points, delegated as one call where it can be.
_sum_var behind a check of its arguments: the curve, the sequence, and every point on the curve, the last of which the twin asks itself.
- ecc.curves.curve.tweak_add_var(P: tuple[int, int], t: Public, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return P + t*G, a point plus a multiple of the generator.
_tweak_add_var behind a check of its arguments: the curve, the tweak as an integer, reduced mod n, and the point on the curve, which the twin asks itself.
ecc.curves.curve_group module¶
Elliptic CurveGroup class and functions.
CurveGroup is every point of the curve, a group that needs be neither cyclic nor of prime order. Curve, in the ecc.curves.curve module, is the cyclic subgroup of prime order generated by G, which is the group everything built on curves multiplies in; the two are the whole of the hierarchy, and the class docstring of Curve says what separates them.
- class ecc.curves.curve_group.CurveGroup(p: bytes | str | bytearray | memoryview | int, a: bytes | str | bytearray | memoryview | int, b: bytes | str | bytearray | memoryview | int)[source]¶
Bases:
objectFinite group of the points of an elliptic curve over Fp.
The elliptic curve is the set of points (x, y) that are solutions to a Weierstrass equation y^2 = x^3 + a*x + b, with x, y, a, and b in Fp (p being a prime), together with a point at infinity INF. The constants a, b must satisfy the relationship 4 a^3 + 27 b^2 ≠ 0.
The group is defined by the point addition group law, INF being its neutral element, and it is finite and abelian — hence a product of at most two cyclic groups, Z_n1 x Z_n2 with n2 dividing n1, so neither cyclic nor of prime order in general.
How many points it holds is not among its data, and cannot be computed from them: the order is p+1 minus the trace of Frobenius, which Hasse’s theorem bounds by twice the square root of p and nothing here counts. Nor is any point of the group distinguished from the others. The generator G, the prime order n of the cyclic subgroup it generates, and the cofactor relating that n to the order of this group are the parameters of Curve, in ecc.curves.curve, which is what a caller of the library multiplies in; what this class is for is the arithmetic underneath, shared by the two and defined by p, a and b alone.
- add_aff_var(Q: tuple[int, int], R: tuple[int, int]) tuple[int, int][source]¶
Return the sum of two affine points, assumed on the curve.
One modular inversion; the special cases are branched on, the comment below saying why affine coordinates leave no choice.
- add_jac(Q: tuple[int, int, int], R: tuple[int, int, int]) tuple[int, int, int][source]¶
Return the sum of two Jacobian points, branch-free.
The input points are assumed to be on the curve. One sequence of operations whatever the operands – infinity and doubling included, the comment of _add_jac saying why that is load-bearing.
- add_jac_aff(Q: tuple[int, int, int], R: tuple[int, int]) tuple[int, int, int][source]¶
Return the sum of a Jacobian point and an affine one, branch-free.
add_jac with the second operand’s Z known to be one, which is what an affine point is: five of the sixteen products become multiplications by one – R’s two powers of Z, the two that put Q in R’s frame, and one factor of the answer’s Z. libsecp256k1 keeps its tables in affine coordinates for exactly this and states secp256k1_gej_add_ge_var as 8 mul and 3 sqr against the 12 and 4 of secp256k1_gej_add_var.
The affine operand is a Point and not a JacPoint whose Z happens to be one, so the precondition is in the signature rather than in a sentence nothing checks.
The input points are assumed to be on the curve. Every case add_jac answers is answered here the same way and for the same reasons, which its comments carry: infinity through a stand-in and a selection at the end, spelled R[1] == 0 because that is what infinity is in affine coordinates, and the doubling and the sum that is infinity through the one branch on V.
- add_var(Q1: tuple[int, int], Q2: tuple[int, int]) tuple[int, int][source]¶
Return the sum of two points.
The input points must be on the curve.
- aff_from_jac_batch_var(Qs: Sequence[tuple[int, int, int]]) list[tuple[int, int]][source]¶
Return the affine points: one modular inversion for all of them.
aff_from_jac_var over a sequence, with mod_inv_batch_var in place of the one inverse each: the conversion is two products a point once the inverse is in hand, so a caller holding several Jacobian points pays one extended Euclid instead of one per point.
The input points are assumed to be on the curve. Infinity is not in the batch, having no Z to invert, and comes back as INF where it stood.
- aff_from_jac_var(Q: tuple[int, int, int]) tuple[int, int][source]¶
Return the affine point: one modular inversion.
The input point is assumed to be on the curve; infinity comes back as INF, its affine spelling.
- double_aff_var(Q: tuple[int, int]) tuple[int, int][source]¶
Return twice the affine point, assumed to be on the curve.
- double_jac(Q: tuple[int, int, int]) tuple[int, int, int][source]¶
Return twice the Jacobian point, assumed to be on the curve.
- is_jac_equal(QJ: tuple[int, int, int], PJ: tuple[int, int, int]) bool[source]¶
Return True if Jacobian points are equal in affine coordinates.
The input points are assumed to be on curve.
- negate(Q: tuple[int, int]) tuple[int, int][source]¶
Return the opposite point.
The input point is not checked to be on the curve.
- negate_jac(Q: tuple[int, int, int]) tuple[int, int, int][source]¶
Return the opposite Jacobian point.
The input point is not checked to be on the curve.
- require_on_curve(Q: tuple[int, int]) None[source]¶
Require the input curve Point to be on the curve.
An Error is raised if not.
- x_aff_from_jac_var(Q: tuple[int, int, int]) int[source]¶
Return the affine x alone, without the products y costs.
One inversion, as aff_from_jac_var, of Z^2 rather than of Z: the power x wants is the one inverted, so nothing is rebuilt from it. The input point is assumed to be on the curve; infinity has no x and is refused.
- y_aff_from_jac_var(Q: tuple[int, int, int]) int[source]¶
Return the affine y alone, without the product x costs.
One inversion, as aff_from_jac_var, of Z^3 rather than of Z, for the same reason x_aff_from_jac_var inverts Z^2. The input point is assumed to be on the curve; infinity has no y and is refused.
- ecc.curves.curve_group.signed_odd_digits(m: int, w: int, size: int) list[int][source]¶
Return the size signed odd base-2^w digits of an odd m.
Regular recoding, Joye-Tunstall: m = sum(digits[i] * 2^(w*i)) with every digit odd and in {±1, ±3, …, ±(2^w - 1)}, least significant first, as _wNAF_of_m_var returns its own. Two properties are what it is for, and neither is the wNAF’s:
no digit is zero, so the multiplication that indexes them makes one addition per digit whatever m is, where a wNAF adds on a nonzero digit and so once per unit of the recoded weight
the count is size and not the length of m, so a scalar’s size stops being visible too
The digits are signed, and that is what buys the first property: 2^w odd values in -(2^w - 1)..2^w - 1 name 2^(w-1) points and their opposites, and negating a Jacobian point is one modular subtraction.
m must be odd – a sum of odd digits weighted by powers of 2 has the parity of digits[0], so no even number has such a form at all – and must fit the size asked for, which is m < 2^(w*size): the last digit is what is left of m and is not reduced further.
ecc.curves.curve_group_2 module¶
Elliptic curve point multiplication functions.
The implemented algorithms are:
Montgomery Ladder
Scalar multiplication on basis 3
Fixed window
Regular window, signed odd digits (Joye-Tunstall recoding)
Sliding window
w-ary non-adjacent form (wNAF)
Interleaved-wNAF double multiplication (HMV algorithm 3.51)
Regular-window double multiplication
GLV endomorphism multiplication for secp256k1 (HMV algorithm 3.77)
GLV endomorphism double multiplication for secp256k1
References
https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication
Hankerson, ‘Guide to Elliptic Curve Cryptography’ chapter 3
M. Joye and M. Tunstall, ‘Exponent Recoding and Regular Exponentiation Algorithms’, AFRICACRYPT 2009, for the signed odd digits of a regular window
https://bitcointalk.org/index.php?topic=3238.msg45565#msg45565
Further improvements, and the material for them. These are not bibliography: each is an improvement this module could take, with what would be needed to take it, kept next to the code it is about. The point-addition special cases are settled where they live, in curve_group’s own comments, and issue btclib-org/btclib#183 tracks the smaller follow-ups; what is here is the rest:
Computational cost of the different multiplications, measured rather than assumed: which of the six is fastest, and at what scalar size
New algorithms at the state of the art:
https://iacr.org/workshops/ches/ches2006/presentations/Douglas%20Stebila.pdf
https://www.esat.kuleuven.be/cosic/publications/article-2293.pdf
https://crypto.stackexchange.com/questions/58506/what-is-the-curve-type-of-secp256k1
1-s_2.0-S1071579704000395-main (the file name it was saved as; the paper it refers to has not been identified)
Joint sparse form (JSF, HMV algorithm 3.50) for double mult: the alternative to the interleaving _double_mult_w_NAF_var implements – one joint recoding of both scalars instead of one NAF each, fewer total additions in exchange for digit pairs that do not index a per-point table of odd multiples
The speed techniques of libsecp256k1 v0.8.0 (btclib-org/btclib-secp256k1 at 86b2ac08) and what the Python arm does with each. The files read are ecmult, ecmult_gen, ecmult_const, group, field, scalar, ecdsa and eckey with the files their limbs live in, and the schnorrsig, extrakeys and ellswift modules. Blinding, a smaller table and a protocol’s steps are not speed techniques and are not listed.
Taken, in the function named:
wNAF recoding, secp256k1_ecmult_wnaf: _wNAF_of_m_var
table of odd multiples, secp256k1_ecmult_odd_multiples_table: _odd_multiples, _odd_multiples_aff
sign of a digit on the point, secp256k1_ecmult_table_get_ge: _additions_by_position_var
one loop and its doublings for all points, secp256k1_ecmult_strauss_wnaf: _multi_mult_w_NAF_var, _double_mult_w_NAF_var
a wide window for the generator, WINDOW_G beside WINDOW_A: _FIXED_POINT_W
a point with a zero scalar or at infinity skipped, secp256k1_ecmult_strauss_wnaf: _multi_mult_pairs
the lambda split and the lambda image of a point, secp256k1_scalar_split_lambda, secp256k1_ge_mul_lambda: _multiplier_decomposer, _endomorphism_split_secp256k1
the lambda image of a table by x * beta, secp256k1_ecmult_table_get_ge_lambda: _lambda_image, which _mult_endomorphism_secp256k1 forms its second table with
mixed addition, secp256k1_gej_add_ge_var: add_jac_aff
doubling for a == 0, secp256k1_gej_double: _double_jac_helper
one inversion for many points, secp256k1_ge_set_all_gej_var: aff_from_jac_batch_var
squareness by the Jacobi symbol, secp256k1_fe_is_square_var: legendre_symbol_var, curve._is_x_coordinate_var, ellswift._is_square_var
the square root by an addition chain, secp256k1_fe_sqrt: number_theory._sqrt_candidate_secp256k1, which mod_sqrt_var calls for secp256k1’s p
ElligatorSwift decoded as a fraction, secp256k1_ellswift_xswiftec_frac_var: ellswift._xswiftec_var, ellswift._is_x_fraction_var
ElligatorSwift encoded with the squareness tested before the inversion, secp256k1_ellswift_xswiftec_inv_var: ellswift._xswiftec_inv_var
signed odd digits, secp256k1_ecmult_const after Hamburg (eprint 2012/309, section 3.3): signed_odd_digits, Joye-Tunstall’s, which gives the same digits; see the measurement below
the signed-digit comb for the generator, secp256k1_ecmult_gen_gej over secp256k1_ecmult_gen_prec_table: _cached_fixed_base_multiples, _mult_fixed_base
the keypair holding the public key, secp256k1_keypair_create: ssa.Signer, on both arms, and bip340_nonce._nonce_with_pub_key_
Measured and not taken, with the verdict where it is kept. Best of five, secp256k1, Python 3.14.6, macOS arm64; a figure is the speed of libsecp256k1’s technique over the code here. The scripts for the figures with no issue of their own are in issue btclib-org/ecc#185:
the same image for the tables of a verification, secp256k1_ecmult_table_get_ge_lambda: 1.01x to 1.03x of _double_mult_endomorphism_secp256k1_var; it needs a hook in _multi_mult_w_NAF_var or a copy of its loop. The script is in a comment on issue btclib-org/ecc#193
Pippenger, secp256k1_ecmult_pippenger_wnaf: _multi_mult_var
secp256k1_gej_eq_x_var: the verification in dsa._assert_as_valid_
the field inverse by an addition chain or safegcd, secp256k1_fe_inv: pow(a, -1, p) is CPython’s extended Euclid in C, where 255 modular squarings in bytecode – fewer than any chain needs – cost several times what it does. pow(a, p - 2, p) costs about what those squarings do, which is why mod_inv_var does not spell Fermat either, and number_theory.mod_inv keeps the measurement:
Solinas reduction, secp256k1_fe_mul_inner: two products by a 33-bit constant and a conditional subtraction cost more than x % p. For n, secp256k1_scalar_reduce_512 folds by a 129-bit constant and measures 0.87x of x % n
limbs with delayed reduction and magnitudes, secp256k1_fe_normalize: letting intermediates grow is slower, as add_jac’s own comment measures. secp256k1_fe_normalizes_to_zero, secp256k1_fe_mul_int, secp256k1_fe_add and secp256k1_scalar_add have no routine here: each is an integer operator and a %
the lambda split by multiply and shift, secp256k1_scalar_mul_shift_var: _multiplier_decomposer rounds with (_B2 * m + n // 2) // n; the split is paid once per multiplication, three orders of magnitude under it. Splitting the generator at 2^128 instead, secp256k1_scalar_split_128, saves 0.6 us of 575 us of a double multiplication
the unified addition, secp256k1_gej_add_ge after Brier and Joye: its core, without the cases, is 0.97x of add_jac_aff
the halving doubling, secp256k1_fe_half in secp256k1_gej_double: 0.76x of _double_jac_helper
Hamburg’s offset in place of a parity correction, secp256k1_scalar_half with secp256k1_ecmult_const_K: 1.00x of _mult_regular_window at w=4, and 1.00x with the endomorphism. It needs n, which CurveGroup lacks
the tagged hash from a stored midstate, secp256k1_schnorrsig_sha256_tagged: hashes.tagged_hash
the x-only multiplication without a square root, secp256k1_ecmult_const_xonly: it gives the x of q*P from the x of P. BIP340 verification, one signature or a batch, adds products and needs their y, and dh.diffie_hellman is given P whole, so it has no root to save. The root is 12% of a mult
Not taken, for a reason, and not measured:
a separate squaring, secp256k1_fe_sqr_inner: CPython’s long_mul takes the squaring path when both operands are the same object
the masked table lookup, secp256k1_ecmult_const’s cmov scan: a list index is a list index
the table with no inversion, secp256k1_ge_table_set_globalz: it would remove the single inversion a call spends; the rest of the conversion is three products an entry, which forming the odd multiples on an isomorphic curve also pays, and the accumulator would have to live in that frame
the tweak added in one call, secp256k1_eckey_pubkey_tweak_add: curve._tweak_add_var declines the multiplication of P by one, and adds the generator’s product to P instead
ecc.curves.curve_group_f module¶
CurveGroup explorer functions, for low-cardinality didactic curves.
Enumerating the points of a group is feasible only when the group is tiny, which is what makes these useful in teaching and useless – and never used – in the rest of the library.
ecc.curves.sec_point module¶
SEC compressed/uncompressed point representation.
- class ecc.curves.sec_point.PubKeyTweakChain(pub_key: bytes | str | bytearray | memoryview, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectA public key and the tweaks added to it one after the other.
Each step adds t*G to the key the previous step returned, and returns the new key as compressed SEC octets. That is BIP32’s public derivation: each child is the next parent, and the next tweak is hashed from the child’s compressed key. TweakChain answers a different question, many tweaks of one fixed point, each measured from that point.
Where the bindings serve, the chain is keys.PubkeyTweakChain: the key is parsed once, held on the C side, and no Python point is built. Elsewhere the key is parsed with point_from_octets_var, and each step is ec.add_var(P, mult(t, ec.G, ec)). The choice is made once, when the chain is built.
A step whose sum is the point at infinity raises, and ends the chain: the chain records that it ended, and on either arm every later step raises. libsecp256k1 leaves the key it holds invalid after such a step, and BIP32 refuses that child as well.
- Parameters:
pub_key – the compressed SEC octets of the starting key.
ec – the curve the key belongs to.
- Raises:
EccTypeError – if the curve is not a Curve, or the key not octets.
EccValueError – if the octets are not a compressed key of a point of the curve.
- ecc.curves.sec_point.bytes_from_point(Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), compressed: bool = True) bytes[source]¶
Return a point as compressed/uncompressed octet sequence.
Return a point as compressed (0x02, 0x03) or uncompressed (0x04) octet sequence, according to SEC 1 v.2, section 2.3.3.
- ecc.curves.sec_point.bytes_from_prv_key_int(prv_key_int: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), compressed: bool = True) bytes[source]¶
Return the public key of a scalar, as SEC octets.
This is bytes_from_point(mult(prv_key_int, ec.G, ec), ec, compressed) and answers what that answers, the edges included: the scalar is reduced mod n, and zero – the infinity point – has no representation and raises.
That composition is what BIP32 derivation and every private-to-public conversion do once per key (issue btclib-org/btclib#127). For secp256k1 this never materializes the point: keys.pubkey_from_prvkey is one secp256k1_ec_pubkey_create plus one serialize, with the compressed flag passed straight through, so the bindings are the ones writing the compressed encoding rather than this package slicing it out of the uncompressed one (issue btclib-org/btclib#459).
- ecc.curves.sec_point.mult_pub_key(m: bytes | str | bytearray | memoryview | int, pub_key: bytes | str | bytearray | memoryview | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return m*P, for a public key P however it is spelled.
mult(m, point_from_pub_key(pub_key, ec), ec) is the same answer, and where the bindings serve it lifts a compressed key’s x to a point only for them to parse it again: this is _sec_from_pub_key and _mult_sec composed, which leave the proof that the key is a point of the curve to the multiplication’s own parse. What it is for is an ECDH-shaped computation, whose key is the other party’s and arrives as octets: a shared secret of BIP352 is one, and ecc.ecc.ecies.derive_keys, through _mult_sec_compressed, another.
The scalar is reduced mod n, and a key that is no point of the curve is refused.
The name is plain, as mult’s is, because the scalar is a secret: a private key, or BIP352’s input_hash*a. The Python arm is mult’s own, and the arm the bindings serve is secp256k1_ecdh, constant time in the scalar. The Python around that call is not: SECURITY.md’s limitations name its steps.
- ecc.curves.sec_point.point_from_octets_var(pub_key: bytes | str | bytearray | memoryview, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), *, hybrid: bool = False) tuple[int, int][source]¶
Return a tuple (x_Q, y_Q) that belongs to the curve.
Return a tuple (x_Q, y_Q) that belongs to the curve according to SEC 1 v.2, section 2.3.4.
The compressed prefixes are the only branch libsecp256k1 serves, and the whole cost of the function is there: lifting x to a point is a modular square root, nearly all of that cost on either arm, and delegated it costs a small fraction of what the Python one does, while the 65-byte forms carry the y and cost the same either way (issue btclib-org/btclib#284).
hybrid admits the 0x06 and 0x07 prefixes of that same section, which carry both coordinates like 0x04 does and repeat the parity of y in the prefix. It is off by default, and not out of squeamishness: the point is a point, and libsecp256k1’s ec_pubkey_parse takes all three 65-byte prefixes (eckey_impl.h). What decides is where the parsed key goes next – addresses, WIF and the descriptor language have no hybrid form to render, and nothing in bitcoin produces one. Consensus has to accept what was mined instead: Core rejects hybrid keys only under STRICTENC, so the script engine is the one caller that asks for them (issue btclib-org/btclib#129). It is refused rather than read for its truth: its True is the permissive value, and most non-bools are true, so hybrid=”no” would parse the very prefixes it was written down to keep out.
On a curve of cofactor above 1 a point on the curve need not be a point of ⟨G⟩: _assert_in_subgroup refuses one that is not, after the on-curve check every branch already makes and before either coordinate leaves this function (issue btclib-org/ecc#15).
- ecc.curves.sec_point.point_from_pub_key(pub_key: bytes | str | bytearray | memoryview | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return an elliptic curve point tuple from a public key.
point_from_octets_var is the parse of the one spelling that has to be parsed; this is that parse with the two spellings that are already a point in front of it, and it lives here for the reason scalar_from_prv_key does – what a public key is, at this layer, is a point of the curve, and nothing above knows more about one than this file (issue btclib-org/btclib#1188). A spelling that carries a network or a compression flag is not among them, an extended key being one.
A prepared point is read as the point it holds. curves.PreparedPoint is a Point plus a caller’s word that it will be multiplied again, and the word is the whole of the difference: a point is the point’s. What is not is the memoized tables, which whatever reads them reads off the object and not through here.
Nothing compares a curve on the way through: a prepared point of another curve fails the is_on_curve its tuple then faces, exactly as a bare Point of that curve does and with the same message.
A tuple is confined to ⟨G⟩ here as point_from_octets_var confines the octets it parses, on a curve whose cofactor makes the two different (issue btclib-org/ecc#15).
- ecc.curves.sec_point.prv_key_tweak_add(prv_key: bytes | str | bytearray | memoryview | int, t: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) int[source]¶
Return the private key plus a tweak, modulo n.
The private half of tweak_add_var: the public key of the answer is the public key of prv_key plus t*G. BIP32’s private derivation is this sum. The two differ on the tweak: tweak_add_var reduces it modulo n, and this refuses one out of 0..n-1, as libsecp256k1 does. A sum of zero is no private key and is refused.
On secp256k1 the bindings compute the sum, secp256k1_ec_seckey_tweak_add, in constant time; the Python checks and conversions around that call are not, and neither is Python’s % on the other arm. SECURITY.md’s limitations name the Python steps. ssa.prv_key_tweak_add is BIP341’s sum, which negates the key first where its point has an odd y.
- ecc.curves.sec_point.scalar_from_prv_key(prv_key: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) int[source]¶
Return a verified-as-valid private key integer.
Here rather than in a converter, and beside bytes_from_prv_key_int for its reason: a scalar in 1..n-1 is a fact about the curve and nothing above it knows more about it than this file does.
Integer and not a PrvKey of this module’s own, which would be that same union of types under a second name and so nothing mypy could check; the parameter carries the role the way mult’s m_int does. The spellings this takes are narrower than the ones Integer names – int_from_integer reads “0xc0ffee” and a short hex string, where a key is n_size octets or nothing. What rules those out is reading them with bytes_from_octets and the size handed to it, rather than with int_from_integer; the annotation could not, the two unions being one. A WIF and an extended key are not among the spellings: turning one into a scalar is a call a caller makes rather than a spelling this layer guesses at (issue btclib-org/btclib#1188).
The range is checked in Python for every curve. keys.prvkey_verify is libsecp256k1’s answer to the same question and is not called for want of anything to gain: a comparison on a value that is already a Python int, with no constant-time argument to pay for the call with, since whether a key is in range is precisely what the caller is being told. The comparison also serves every curve, where the binding is secp256k1 alone.
Module contents¶
Module ecc.curves.
The arithmetic. ecc.curves holds the elliptic curve itself: the field and group operations of curve_group, the Curve built on them, the catalogued curves (secp256k1 among them), and the SEC encoding of a point. Nothing here knows what a signature is.
What is built on a curve lives in ecc.ecc – dsa, ssa, borromean, pedersen, Diffie-Hellman, the nonces – and ARCHITECTURE.md says which of the two imports the other.
The two names are easy to conflate – everything in ecc.ecc is also about curves – so the anchor is worth stating: from ecc.curves import mult, from ecc.ecc import dsa.
What this package exports is the curve API: a Curve, the catalogue they are looked up in, the scalar multiplications, the SEC point codec, and PreparedPoint, which is a point plus a caller’s word that it will be multiplied again – the one thing the memoized tables below cannot infer and the whole of what stands between a repeated point and the treatment the generator gets. Beside them are the sum of two points that may be secret, a sum of public points, and the test of whether an x is a coordinate of the curve.
A point plus a multiple of the generator is there once, as many tweaks of one point, and as a compressed public key tweaked step after step, which is how a BIP32 path is walked. A private key plus a tweak is the same sum on the private side.
CURVES is what makes the catalogue reachable – secp256k1 is exported by name because nearly every caller wants that one, and every other curve is CURVES[“secp256r1”] – so exporting the name and not the dictionary left the paragraph above naming curves a caller could not get at without importing the module the name is defined in. The four catalogues it is the union of (SEC2v1, SEC2v2, NIST, Brainpool) stay where they are defined: which standard a curve comes from is a question about a curve, not a way of finding one.
The other multiplications of ecc.curves.curve_group and ecc.curves.curve_group_2 – _mult_aff_var, _mult_jac_var, _mult_base_3_var, _mult_mont_ladder_var, the two _mult_recursive_*_var, the two _mult_fixed_window*_var, _mult_fixed_base, _mult_regular_window, _mult_sliding_window_var, _mult_w_NAF_var, the three _double_mult_*, and the two _mult_endomorphism_secp256k1* – are private, as are the _multiples, _cached_multiples, _cached_fixed_base_multiples, _odd_multiples and _jac_from_aff they are built on.
The _var suffix on most of those is libsecp256k1’s, and it says the same thing: the number of point operations such a multiplication makes depends on the coefficient it is given, where _mult_regular_window, _mult_fixed_base and _double_mult_regular_window make the same number for every scalar of the curve. CONTRIBUTING.md states the rule, and states what its absence does not promise: nothing here is constant-time.
Which one runs is not a setting, and a census says why it is not (issue btclib-org/btclib#849). On the generator the regular form is also the faster one: _mult_fixed_base doubles only between comb offsets, where a wNAF makes one doubling per bit however wide its table is and however thoroughly it is cached. That holds against the memoized odd multiples of G at w=8, w=10 and w=12 alike, issue btclib-org/ecc#194. So the arm every key derivation, every BIP32 child and every signing nonce runs has no variable-time alternative to offer. What is left is a variable-base mult with a secret scalar, measured over ecc.ecc.dh: 1.13x on secp256k1 and 1.03x on nistp256, the second being that measurement’s own noise. Beside it, the blinded nonce inverse is a fraction of a percent of a signature, the projective blinding of curve_group._blinded_jac is 1%, and every verification is already _var. The 1.13x was measured with the bindings switched off. btclib_secp256k1 is the secp256k1 extra rather than a required dependency, so a caller who skips it is already in that state, with nothing to switch.
They are implementations of one operation, kept side by side to be measured against each other, and a menu of them is not an API: a caller reading them would find every way of multiplying a point this package implements and nothing to say that mult is the one to use, that it dispatches to libsecp256k1 for secp256k1, and that _mult_jac_var is not the faster alternative its name suggests.
The underscore says the second thing too, which is what decided it: each takes a point it assumes to be on the curve and checks nothing, so a malformed one is answered with a point rather than refused. mult, double_mult_var and multi_mult_var are where require_on_curve runs, on every argument and every path, and reaching past them is reaching past that. The test suite takes each variant from the module that defines it, which is what a private name is still good for.
Beside the two point conversions the codec carries bytes_from_prv_key_int, the composition of a multiplication and an encoding that every private-to-public conversion is, answered for secp256k1 out of the bindings’ own serialization, without materializing the point (issue btclib-org/btclib#127); and scalar_from_prv_key, which reads a private key the other way, as the scalar in 1..n-1 that it is. What a private key may be spelled as at this layer is the integer, its n_size octets, or their hex. That is narrower than everything Integer takes – int_from_integer reads a “0x” prefix and a short hex string, which a key of a fixed size must not be – and the annotation is Integer because the two are the same union of types and a second name for it would say nothing mypy could check.
point_from_pub_key is the public half of that pair, and PubKey is what it takes: the point as a tuple, as a PreparedPoint, or as the SEC octets point_from_octets_var parses. Both halves are here rather than in a converter above because a scalar in 1..n-1 and a point of the curve are facts about the curve, and nothing above knows more about either than this package does. A WIF and an extended key are not among the spellings of either: each carries a network and a compression flag, which are not facts about a curve (issue btclib-org/btclib#1188).
- class ecc.curves.Curve(p: bytes | str | bytearray | memoryview | int, a: bytes | str | bytearray | memoryview | int, b: bytes | str | bytearray | memoryview | int, G: tuple[int, int], n: bytes | str | bytearray | memoryview | int, cofactor: int, weakness_check: bool = True, order_check: bool = True, name: str | None = None)[source]¶
Bases:
CurveGroupCyclic subgroup of prime order n, generated by G, of the curve points.
The subgroup is ⟨G⟩ = {INF, G, 2G, …, (n-1)G}, of prime order n, inside the group of all the points of the curve — the CurveGroup this is built on, whose order is n times the cofactor h. The curve is that group and this is a subgroup of it, so the name is the wrong way round on purpose: this is the only group anything else in this package multiplies in, and Curve is what a caller asks for.
Two things follow from n being prime, and ecc.ecc relies on both: the integers modulo n are a field, so every scalar but zero has an inverse — the nonce and s that ecdsa inverts, the challenge that ssa’s key recovery does — and the subgroup has no subgroup other than itself and {INF}, so every point of ⟨G⟩ but INF is a generator of it.
That second guarantee is about the points already known to be in ⟨G⟩; it says nothing about a point a caller hands in. A curve of cofactor 1 has no other point to confuse ⟨G⟩ with, so there confinement to a small subgroup is not possible either way. A curve of cofactor above 1 does: a point outside ⟨G⟩ can have any order dividing the cofactor, small and attacker-chosen, and mult does not refuse one on its own, reducing whatever scalar it is given mod n regardless of the point’s own order. sec_point.point_from_pub_key and point_from_octets_var are where a point read from the outside is confined to ⟨G⟩, refusing one whose unreduced n·Q is not INF (issue btclib-org/ecc#15).
n is a parameter and not a computed quantity: the order of G is the order of the whole group divided by the index of ⟨G⟩ in it, and counting the points of a curve this size is Schoof-Elkies-Atkin rather than arithmetic on p, a and b. What the constructor does compute is that the parameter is the order, and that is where the primality of n earns its second keep. nG = INF proves only that n annihilates G, i.e. that the order of G divides n; pinning it to n in general asks for (n/q)G ≠ INF for every prime q dividing n, which is the factorization of n — a number no curve publishes alongside its parameters. With n prime and G ≠ INF the single nG = INF is the whole of it.
Which is also why the two classes are the whole hierarchy, with no third one between them for the cyclic subgroup of unstated order: ⟨G⟩ carrying an n it cannot verify, or no n at all, is a cyclic group that cannot say how many elements it has, cannot reduce a scalar, and cannot bound a private key. Nothing here could take one as an argument.
- is_on_curve(Q: tuple[int, int]) bool[source]¶
Return True if the point is on the curve.
As CurveGroup.is_on_curve, with one further refusal: a curve of cofactor above 1 can hold a real point at y == 0, the curve’s own element of order 2, and that tuple is indistinguishable from INF, which is spelled the same way. CurveGroup has no G and no n and reads every such tuple as INF unconditionally, which is correct there – find_all_points walks a bare group, off ⟨G⟩ entirely, and a real two-torsion point is exactly as legitimate a member of it as any other. Here, where ⟨G⟩ is what everything else in this package multiplies in, the ambiguity is refused instead (issue btclib-org/ecc#16). A curve of cofactor 1 never takes this branch: a real root at y == 0 makes the curve’s full order even, and n, being prime, is odd.
- class ecc.curves.CurveGroup(p: bytes | str | bytearray | memoryview | int, a: bytes | str | bytearray | memoryview | int, b: bytes | str | bytearray | memoryview | int)[source]¶
Bases:
objectFinite group of the points of an elliptic curve over Fp.
The elliptic curve is the set of points (x, y) that are solutions to a Weierstrass equation y^2 = x^3 + a*x + b, with x, y, a, and b in Fp (p being a prime), together with a point at infinity INF. The constants a, b must satisfy the relationship 4 a^3 + 27 b^2 ≠ 0.
The group is defined by the point addition group law, INF being its neutral element, and it is finite and abelian — hence a product of at most two cyclic groups, Z_n1 x Z_n2 with n2 dividing n1, so neither cyclic nor of prime order in general.
How many points it holds is not among its data, and cannot be computed from them: the order is p+1 minus the trace of Frobenius, which Hasse’s theorem bounds by twice the square root of p and nothing here counts. Nor is any point of the group distinguished from the others. The generator G, the prime order n of the cyclic subgroup it generates, and the cofactor relating that n to the order of this group are the parameters of Curve, in ecc.curves.curve, which is what a caller of the library multiplies in; what this class is for is the arithmetic underneath, shared by the two and defined by p, a and b alone.
- add_aff_var(Q: tuple[int, int], R: tuple[int, int]) tuple[int, int][source]¶
Return the sum of two affine points, assumed on the curve.
One modular inversion; the special cases are branched on, the comment below saying why affine coordinates leave no choice.
- add_jac(Q: tuple[int, int, int], R: tuple[int, int, int]) tuple[int, int, int][source]¶
Return the sum of two Jacobian points, branch-free.
The input points are assumed to be on the curve. One sequence of operations whatever the operands – infinity and doubling included, the comment of _add_jac saying why that is load-bearing.
- add_jac_aff(Q: tuple[int, int, int], R: tuple[int, int]) tuple[int, int, int][source]¶
Return the sum of a Jacobian point and an affine one, branch-free.
add_jac with the second operand’s Z known to be one, which is what an affine point is: five of the sixteen products become multiplications by one – R’s two powers of Z, the two that put Q in R’s frame, and one factor of the answer’s Z. libsecp256k1 keeps its tables in affine coordinates for exactly this and states secp256k1_gej_add_ge_var as 8 mul and 3 sqr against the 12 and 4 of secp256k1_gej_add_var.
The affine operand is a Point and not a JacPoint whose Z happens to be one, so the precondition is in the signature rather than in a sentence nothing checks.
The input points are assumed to be on the curve. Every case add_jac answers is answered here the same way and for the same reasons, which its comments carry: infinity through a stand-in and a selection at the end, spelled R[1] == 0 because that is what infinity is in affine coordinates, and the doubling and the sum that is infinity through the one branch on V.
- add_var(Q1: tuple[int, int], Q2: tuple[int, int]) tuple[int, int][source]¶
Return the sum of two points.
The input points must be on the curve.
- aff_from_jac_batch_var(Qs: Sequence[tuple[int, int, int]]) list[tuple[int, int]][source]¶
Return the affine points: one modular inversion for all of them.
aff_from_jac_var over a sequence, with mod_inv_batch_var in place of the one inverse each: the conversion is two products a point once the inverse is in hand, so a caller holding several Jacobian points pays one extended Euclid instead of one per point.
The input points are assumed to be on the curve. Infinity is not in the batch, having no Z to invert, and comes back as INF where it stood.
- aff_from_jac_var(Q: tuple[int, int, int]) tuple[int, int][source]¶
Return the affine point: one modular inversion.
The input point is assumed to be on the curve; infinity comes back as INF, its affine spelling.
- double_aff_var(Q: tuple[int, int]) tuple[int, int][source]¶
Return twice the affine point, assumed to be on the curve.
- double_jac(Q: tuple[int, int, int]) tuple[int, int, int][source]¶
Return twice the Jacobian point, assumed to be on the curve.
- is_jac_equal(QJ: tuple[int, int, int], PJ: tuple[int, int, int]) bool[source]¶
Return True if Jacobian points are equal in affine coordinates.
The input points are assumed to be on curve.
- negate(Q: tuple[int, int]) tuple[int, int][source]¶
Return the opposite point.
The input point is not checked to be on the curve.
- negate_jac(Q: tuple[int, int, int]) tuple[int, int, int][source]¶
Return the opposite Jacobian point.
The input point is not checked to be on the curve.
- require_on_curve(Q: tuple[int, int]) None[source]¶
Require the input curve Point to be on the curve.
An Error is raised if not.
- x_aff_from_jac_var(Q: tuple[int, int, int]) int[source]¶
Return the affine x alone, without the products y costs.
One inversion, as aff_from_jac_var, of Z^2 rather than of Z: the power x wants is the one inverted, so nothing is rebuilt from it. The input point is assumed to be on the curve; infinity has no x and is refused.
- y_aff_from_jac_var(Q: tuple[int, int, int]) int[source]¶
Return the affine y alone, without the product x costs.
One inversion, as aff_from_jac_var, of Z^3 rather than of Z, for the same reason x_aff_from_jac_var inverts Z^2. The input point is assumed to be on the curve; infinity has no y and is refused.
- class ecc.curves.PreparedPoint(point: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectA point whose multiplication tables are kept, because it will come back.
The tables of curve_group are memoized on the point, the curve and the table’s parameters already, so a repeated point would find its own: what is missing is anyone to say that a point is repeated. Only the generator is assumed to be, and everything else is treated as arriving once – which is right for most callers and wrong for a few, and no measurement can tell which a caller is. This is where a caller says so.
Two tables answer to it, one per operation:
mult takes the fixed-base comb of the generator instead of the GLV endomorphism, which builds nothing: a warm call is cheaper, and the table a build to pay for. Issue btclib-org/ecc#194 holds the break-even and its measurement. A point that comes back is where it pays: dh.diffie_hellman against a counterparty, a taproot internal key tweaked repeatedly, pedersen against a fixed second generator.
a verification under it – dsa and ssa both take one where they take a public key – memoizes the wNAF tables of the key’s two endomorphism halves at _FIXED_POINT_W instead of rebuilding them at _DOUBLE_MULT_W per signature: 2 tables built per verification become 0, and the verification a fifth cheaper for ECDSA and for BIP340 alike. Break-even is 22 signatures under the one key, the first verification costing several times what a bare key’s does.
Both are the Python arithmetic, and what puts a call on them is that call’s own guard: _libsecp256k1_serves – the process-wide switch, which an installation without the bindings leaves off and which set_libsecp256k1_serving and ECC_NO_LIBSECP256K1 turn off in a process that has them, and the curve – with whatever that call ands onto it. Neither call passes a hash function: a verification’s is spent on its own module’s guard, and _jac_double_mult_var under that guard asks the predicate again with none, so a hash function the bindings do not serve leaves the multiplication delegated and the tables handed to it unused. Where a call’s whole guard holds there is nothing here to reach – libsecp256k1 verifies in a fraction of either arm and holds tables of its own – so handing a prepared point in on that path is not an error and costs nothing; it simply buys nothing.
Preparing is a caller’s word and never inferred, and the memory is why: the tables are per distinct point, so a library that memoized whatever public key arrived would hold a few MB for keys nobody will see again – issue btclib-org/btclib#287, the bound _cached_base58_decode and pedersen.second_generator hold too. What bounds it here is that nothing is prepared unless asked, and beyond that the lru_cache maxsize the tables live under.
Nothing is built by the constructor. It parses and validates the point, which is the other half of what a verifier repeats – a decompression per signature, on the Python path where a compressed key is a field square root – and leaves the tables to the first multiplication that wants them, because which of the two families above is wanted is a question only that call answers.
On a curve with a cofactor the constructor also asks, by one multiplication by n, whether the point is in ⟨G⟩. A point outside it can have a multiple of order 2, whose affine form (x, 0) the comb’s tables cannot tell from infinity. So mult sends such a point to the arm curve.mult takes, which keeps no table. A point of ⟨G⟩ has no multiple of order 2, n being an odd prime.
The verification figures are measured on an Apple M5, macOS 26.6, arm64, CPython 3.14, with curve._libsecp256k1_available set to False; best of five alternating rounds of 300 to 800 calls, and the median of seven for the cold rows, each on a freshly derived point so that the tables are built rather than found. A working desktop rather than a quiesced machine: a ratio, not a figure to quote.
- ec¶
the curve it was validated against.
- Type:
- fixed¶
the Jacobian set a verification hands straight down, derived by the constructor from the two above; the comment beside the field is what it holds and why it is derived.
- Parameters:
point – the point to prepare, on the curve and not infinity.
ec – the curve it belongs to.
- Raises:
EccValueError – if the point is not on the curve, or is infinity, which has no tables and multiplies to itself.
- mult(m_int: bytes | str | bytearray | memoryview | int) tuple[int, int][source]¶
Return m*point, through the tables this point keeps.
curve.mult with the fixed-base arm taken for this point, where it is in ⟨G⟩, instead of only for the generator; everything else about the call, the dispatch to the bindings included, is the same.
- class ecc.curves.PubKeyTweakChain(pub_key: bytes | str | bytearray | memoryview, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectA public key and the tweaks added to it one after the other.
Each step adds t*G to the key the previous step returned, and returns the new key as compressed SEC octets. That is BIP32’s public derivation: each child is the next parent, and the next tweak is hashed from the child’s compressed key. TweakChain answers a different question, many tweaks of one fixed point, each measured from that point.
Where the bindings serve, the chain is keys.PubkeyTweakChain: the key is parsed once, held on the C side, and no Python point is built. Elsewhere the key is parsed with point_from_octets_var, and each step is ec.add_var(P, mult(t, ec.G, ec)). The choice is made once, when the chain is built.
A step whose sum is the point at infinity raises, and ends the chain: the chain records that it ended, and on either arm every later step raises. libsecp256k1 leaves the key it holds invalid after such a step, and BIP32 refuses that child as well.
- Parameters:
pub_key – the compressed SEC octets of the starting key.
ec – the curve the key belongs to.
- Raises:
EccTypeError – if the curve is not a Curve, or the key not octets.
EccValueError – if the octets are not a compressed key of a point of the curve.
- class ecc.curves.TweakChain(base: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1))[source]¶
Bases:
objectOne point, many tweaks of it, and one parse on the far side.
_tweak_add_var above crosses the boundary once per tweak: the point is serialized here, libsecp256k1 parses it, tweaks it and serializes the answer, and this side reads that back. A caller tweaking one point over and over pays the parse at every tweak, for a point that has not changed since the last one – BIP352’s scan_outputs is the caller, walking k upward from one spend key.
The tweaks asked of this are absolute, each measured from the point the chain was built on, because that is what the caller means by t_k. What crosses is their differences:
P_k = B + t_k*G P_(k+1) = P_k + (t_(k+1) - t_k)*G
which makes many tweaks of one point a chain of steps, each step’s output the next one’s input, and keys.PubkeyTweakChain is the object that holds the parsed point across such a chain. So this needs no fan-out entry point of its own, and the bindings have none: the fan-out is a chain read the other way round. Sixteen tweaks of one point cost less than sixteen separate ones, the difference being the fifteen parses that do not happen; what that is worth at the one caller, per number of outputs found, is in btclib-org/btclib’s CHANGELOG, in the entry that took it.
Nothing is required of the order the tweaks arrive in. The difference is taken modulo n, so the same tweak may be asked for twice – a zero step, which libsecp256k1 adds like any other and answers with the point it already holds, its header saying so of every tweak it takes – and a later tweak may be smaller than an earlier one. An instance is reusable for that reason: the base never moves, and what is held beside it is only the tweak the next difference is measured from.
- Parameters:
base – the point every tweak is added to.
ec – the curve it belongs to.
- Raises:
EccTypeError – if the curve is not a Curve.
EccValueError – if the point is not on the curve.
- ecc.curves.add(P: tuple[int, int], Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return P + Q, for two points that may be secret.
_add behind a check of its arguments: the curve, and both points on the curve, which on the bindings arm the parse inside _add completes. sum_var is the sum of points that are public.
The contract differs by arm. Where the libsecp256k1 bindings serve, which is for secp256k1 while is_libsecp256k1_serving() is True, the sum is secp256k1_ec_pubkey_combine: the call is constant time on the C side of the boundary, and the Python steps around it are not. Everywhere else it is the Python arithmetic on two blinded points, which is not constant time. SECURITY.md’s Limitations, not vulnerabilities says which Python steps those are, and what each arm leaves.
Where the bindings serve, the Python checks only the form of each point, and the parse of secp256k1_ec_pubkey_combine proves a finite one is on the curve: require_on_curve runs only on the path that refuses, to raise what it raises on the other arm. _mult_sec makes the same trade for a key handed over as octets.
- ecc.curves.bytes_from_point(Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), compressed: bool = True) bytes[source]¶
Return a point as compressed/uncompressed octet sequence.
Return a point as compressed (0x02, 0x03) or uncompressed (0x04) octet sequence, according to SEC 1 v.2, section 2.3.3.
- ecc.curves.bytes_from_prv_key_int(prv_key_int: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), compressed: bool = True) bytes[source]¶
Return the public key of a scalar, as SEC octets.
This is bytes_from_point(mult(prv_key_int, ec.G, ec), ec, compressed) and answers what that answers, the edges included: the scalar is reduced mod n, and zero – the infinity point – has no representation and raises.
That composition is what BIP32 derivation and every private-to-public conversion do once per key (issue btclib-org/btclib#127). For secp256k1 this never materializes the point: keys.pubkey_from_prvkey is one secp256k1_ec_pubkey_create plus one serialize, with the compressed flag passed straight through, so the bindings are the ones writing the compressed encoding rather than this package slicing it out of the uncompressed one (issue btclib-org/btclib#459).
- ecc.curves.double_mult_var(u: Public, H: tuple[int, int], v: Public, Q: tuple[int, int], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Double scalar multiplication (u*H + v*Q).
- ecc.curves.find_all_points(ec: CurveGroup) list[tuple[int, int]][source]¶
Attempt to find all group points, if p is low.
Very unsofisticated walk-through approach, for didactic sake only.
- ecc.curves.find_subgroup_points(ec: CurveGroup, G: tuple[int, int]) list[tuple[int, int]][source]¶
Attempt to count all G-generated subgroup points, if p is low.
Very unsofisticated walk-through approach, for didactic sake only.
- ecc.curves.is_libsecp256k1_serving() bool[source]¶
Return True if the bindings are what this process delegates to.
One question and not two: installed, and not refused. A caller has no use for the difference – what it can act on is whether the answer it is about to get comes from libsecp256k1 or from the Python arithmetic – and two observable states where there is one is how a caller comes to handle only the state it happened to meet.
The public reading of the seam every dispatch consults. It is what a project built on this package asks when it must not check libsecp256k1 with libsecp256k1: Bitcoin Core’s own test framework keeps that rule – crypto/secp256k1.py is “designed for ease of understanding, not performance” – and issue btclib-org/btclib#198 is btclib’s side of it.
- ecc.curves.is_x_coordinate_var(x: Public, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) bool[source]¶
Return True if x is the x-coordinate of a point of the curve.
_is_x_coordinate_var behind a check of its arguments: a caller that reads a compressed key and has no use for its y – an xpub’s key is one, parsed at every level of a derivation – asks this rather than lifting the point. An integer that is no field element is False.
- ecc.curves.mult(m_int: bytes | str | bytearray | memoryview | int, Q: tuple[int, int] | None = None, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Elliptic curve scalar multiplication.
- ecc.curves.mult_pub_key(m: bytes | str | bytearray | memoryview | int, pub_key: bytes | str | bytearray | memoryview | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return m*P, for a public key P however it is spelled.
mult(m, point_from_pub_key(pub_key, ec), ec) is the same answer, and where the bindings serve it lifts a compressed key’s x to a point only for them to parse it again: this is _sec_from_pub_key and _mult_sec composed, which leave the proof that the key is a point of the curve to the multiplication’s own parse. What it is for is an ECDH-shaped computation, whose key is the other party’s and arrives as octets: a shared secret of BIP352 is one, and ecc.ecc.ecies.derive_keys, through _mult_sec_compressed, another.
The scalar is reduced mod n, and a key that is no point of the curve is refused.
The name is plain, as mult’s is, because the scalar is a secret: a private key, or BIP352’s input_hash*a. The Python arm is mult’s own, and the arm the bindings serve is secp256k1_ecdh, constant time in the scalar. The Python around that call is not: SECURITY.md’s limitations name its steps.
- ecc.curves.multi_mult_var(scalars: Sequence[Public], points: Sequence[tuple[int, int]], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return the multi scalar multiplication u1*Q1 + … + un*Qn.
Interleaved wNAF on few scalars, Bos-Coster on many: curve_group’s _multi_mult_var dispatches on the count, at the size the two measure the same. On secp256k1 the bindings serve the whole sum instead.
The callers handing it many scalars at once are musig2’s key aggregation, frost’s interpolation over its public shares, and the Python arm of _multi_mult_x_only_var, which serves ssa’s batch verification and ssa_halfagg’s aggregate verification. On the delegated arm those verifications reach _libsecp256k1_multi_mult_var_ with their terms as octets, this signature taking points and a point being what their terms would have to be lifted into for the multiplication to write them straight back out.
- ecc.curves.point_from_octets_var(pub_key: bytes | str | bytearray | memoryview, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1), *, hybrid: bool = False) tuple[int, int][source]¶
Return a tuple (x_Q, y_Q) that belongs to the curve.
Return a tuple (x_Q, y_Q) that belongs to the curve according to SEC 1 v.2, section 2.3.4.
The compressed prefixes are the only branch libsecp256k1 serves, and the whole cost of the function is there: lifting x to a point is a modular square root, nearly all of that cost on either arm, and delegated it costs a small fraction of what the Python one does, while the 65-byte forms carry the y and cost the same either way (issue btclib-org/btclib#284).
hybrid admits the 0x06 and 0x07 prefixes of that same section, which carry both coordinates like 0x04 does and repeat the parity of y in the prefix. It is off by default, and not out of squeamishness: the point is a point, and libsecp256k1’s ec_pubkey_parse takes all three 65-byte prefixes (eckey_impl.h). What decides is where the parsed key goes next – addresses, WIF and the descriptor language have no hybrid form to render, and nothing in bitcoin produces one. Consensus has to accept what was mined instead: Core rejects hybrid keys only under STRICTENC, so the script engine is the one caller that asks for them (issue btclib-org/btclib#129). It is refused rather than read for its truth: its True is the permissive value, and most non-bools are true, so hybrid=”no” would parse the very prefixes it was written down to keep out.
On a curve of cofactor above 1 a point on the curve need not be a point of ⟨G⟩: _assert_in_subgroup refuses one that is not, after the on-curve check every branch already makes and before either coordinate leaves this function (issue btclib-org/ecc#15).
- ecc.curves.point_from_pub_key(pub_key: bytes | str | bytearray | memoryview | tuple[int, int] | PreparedPoint, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return an elliptic curve point tuple from a public key.
point_from_octets_var is the parse of the one spelling that has to be parsed; this is that parse with the two spellings that are already a point in front of it, and it lives here for the reason scalar_from_prv_key does – what a public key is, at this layer, is a point of the curve, and nothing above knows more about one than this file (issue btclib-org/btclib#1188). A spelling that carries a network or a compression flag is not among them, an extended key being one.
A prepared point is read as the point it holds. curves.PreparedPoint is a Point plus a caller’s word that it will be multiplied again, and the word is the whole of the difference: a point is the point’s. What is not is the memoized tables, which whatever reads them reads off the object and not through here.
Nothing compares a curve on the way through: a prepared point of another curve fails the is_on_curve its tuple then faces, exactly as a bare Point of that curve does and with the same message.
A tuple is confined to ⟨G⟩ here as point_from_octets_var confines the octets it parses, on a curve whose cofactor makes the two different (issue btclib-org/ecc#15).
- ecc.curves.prv_key_tweak_add(prv_key: bytes | str | bytearray | memoryview | int, t: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) int[source]¶
Return the private key plus a tweak, modulo n.
The private half of tweak_add_var: the public key of the answer is the public key of prv_key plus t*G. BIP32’s private derivation is this sum. The two differ on the tweak: tweak_add_var reduces it modulo n, and this refuses one out of 0..n-1, as libsecp256k1 does. A sum of zero is no private key and is refused.
On secp256k1 the bindings compute the sum, secp256k1_ec_seckey_tweak_add, in constant time; the Python checks and conversions around that call are not, and neither is Python’s % on the other arm. SECURITY.md’s limitations name the Python steps. ssa.prv_key_tweak_add is BIP341’s sum, which negates the key first where its point has an odd y.
- ecc.curves.scalar_from_prv_key(prv_key: bytes | str | bytearray | memoryview | int, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) int[source]¶
Return a verified-as-valid private key integer.
Here rather than in a converter, and beside bytes_from_prv_key_int for its reason: a scalar in 1..n-1 is a fact about the curve and nothing above it knows more about it than this file does.
Integer and not a PrvKey of this module’s own, which would be that same union of types under a second name and so nothing mypy could check; the parameter carries the role the way mult’s m_int does. The spellings this takes are narrower than the ones Integer names – int_from_integer reads “0xc0ffee” and a short hex string, where a key is n_size octets or nothing. What rules those out is reading them with bytes_from_octets and the size handed to it, rather than with int_from_integer; the annotation could not, the two unions being one. A WIF and an extended key are not among the spellings: turning one into a scalar is a call a caller makes rather than a spelling this layer guesses at (issue btclib-org/btclib#1188).
The range is checked in Python for every curve. keys.prvkey_verify is libsecp256k1’s answer to the same question and is not called for want of anything to gain: a comparison on a value that is already a Python int, with no constant-time argument to pay for the call with, since whether a key is in range is precisely what the caller is being told. The comparison also serves every curve, where the binding is secp256k1 alone.
- ecc.curves.set_libsecp256k1_serving(*, serving: bool, required: bool = False) None[source]¶
Ask for the bindings, or for the Python arithmetic, from here on.
Process-wide and immediate: every dispatch in the package asks _libsecp256k1_serves, and that predicate reads the global this assigns, so nothing has to be re-imported and no module keeps an answer of its own.
serving=True with the bindings not installed is a request that cannot be served, and is refused rather than silently ignored: a caller that asked for C and got Python would be timing Python and calling it C. is_libsecp256k1_serving is how the answer is read back.
required=True goes further: a call on secp256k1 that the bindings decline for something the caller chose raises EccRuntimeError instead of running the Python arithmetic; SECURITY.md says which choices. With serving=False it raises EccValueError: a caller asking for both would get neither.
The environment variable is the other way in, and the earlier one: ECC_NO_LIBSECP256K1 set to a non-empty value makes the initial state False, which is what a test runner wants – it settles before the first import, where this function cannot. It is not a lock: any code in the process can reverse it with this function, so a test framework that must not run on the bindings checks is_libsecp256k1_serving itself, where it needs the answer.
The first serving=True after that variable refused the bindings checks them against known answers. If one is wrong it raises EccRuntimeError and the state is as it was.
- ecc.curves.sum_var(points: Sequence[tuple[int, int]], ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return the sum of points, delegated as one call where it can be.
_sum_var behind a check of its arguments: the curve, the sequence, and every point on the curve, the last of which the twin asks itself.
- ecc.curves.tweak_add_var(P: tuple[int, int], t: Public, ec: Curve = Curve('FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F', 0, 7, ('79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798', '483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8'), 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141', 1)) tuple[int, int][source]¶
Return P + t*G, a point plus a multiple of the generator.
_tweak_add_var behind a check of its arguments: the curve, the tweak as an integer, reduced mod n, and the point on the curve, which the twin asks itself.