secp256k1
Points in, points out
import { addPoints, liftX, multiplyGenerator, multiplyPoint } from "@agntn/curves/secp256k1";
const G = multiplyGenerator("1"); // "0279be667ef9dcbb…1798"
multiplyGenerator(3n); // "02f9308a019258c3…36f9"
addPoints(G, multiplyGenerator(2n)); // "02f9308a019258c3…36f9", 3G again
Points are SEC1 hex without 0x, 33 bytes compressed or 65 uncompressed. Output is compressed unless you pass { compressed: false }. A scalar is hex up to 64 digits or a bigint, from 1 to n - 1.
| Function | Does |
|---|---|
addPoints(a, b) | a + b, as in a split key vanity address |
subtractPoints(a, b) | a - b, the offset between two known keys |
negatePoint(p) | same x, the other y |
multiplyPoint(p, k) | k times p |
multiplyGenerator(k) | k times G, the public key of k |
liftX(x) | both points above x, even first |
isOnCurve(p) | true or false |
convertPoint(p) | between 33 and 65 bytes |
Half of G
What's G times the inverse of 2?
import { invertScalar, multiplyPoint } from "@agntn/curves/secp256k1";
invertScalar(2n); // "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1"
multiplyPoint(G, invertScalar(2n)); // "0200000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63"
Eleven zero bytes at the start of x. Out of 2^256 possible values. Make of that what you will.
Scalars mod n
addScalars, subtractScalars, multiplyScalars and invertScalar work mod the group order and give 64 hex digits back:
addScalars("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", "2"); // "000…001"
That first number is n - 1, so adding 2 wraps to 1.
What it refuses
subtractPoints(G, G) // The result is the point at infinity, which is no public key, as with P minus P
multiplyPoint(G, "0") // Scalar must be hex without 0x or a bigint, from 1 to the curve order minus 1
negatePoint("02000…0005") // Invalid SEC1 secp256k1 public key
No point of secp256k1 has x = 5, so the compressed form above is no point at all. isOnCurve says false for it instead of throwing. A coordinate at or above p is refused too, as SEC 1 asks.
Written from the specs
The domain parameters come from SEC 2, section 2.4.1. The encodings follow SEC 1, sections 2.3.3 and 2.3.4. Points multiply in Jacobian coordinates, one inversion per result. No @noble/curves underneath, though the tests hold every result to it.
Is it as fast? No. About 0.9 ms for a multiply of G, against 0.36 ms in noble, which has precomputed tables. Plenty for a puzzle. And it doesn't run in constant time, so keep real keys away from it.
Over MCP
curves_secp256k1_compute does the point side: add, subtract, negate, multiply, lift and check. Scalar math stays in the library.
{"operation":"lift","even":"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","odd":"0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}