brainpoolP512r1
brainpoolP512r1, from RFC 5639
This entry handles SEC1 point math and ECDSA verification. It verifies OpenPGP and TLS Brainpool signatures. RFC 9580 lists the curve for ECDSA and ECDH. RFC 7027 specifies Brainpool curves for TLS. The package doesn't include twisted brainpoolP512t1. This entry doesn't sign, recover keys or provide scalar math.
RFC 5639 section 3.7 defines brainpoolP512r1. The equation is y² = x³ + ax + b. It uses a 512-bit prime field and cofactor 1. The coefficient a is neither 0 nor -3:
7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca
What does a point look like here? Try the generator and two x values:
import { isOnCurve, multiplyGenerator } from "@agntn/curves/brainpoolp512r1";
multiplyGenerator(1n); // "0281aee4bdd82ed9…bcb9f822"
isOnCurve(`02${"0".repeat(128)}`); // false, x = 0
isOnCurve(`02${"0".repeat(127)}3`); // true, x = 3
The generator's y is even, so its compressed form starts with 02. Coordinates and scalars take 64 bytes, or 128 hex digits. Compressed points take 65 bytes. Uncompressed points take 129.
No other curve in this package shares that width. That's a welcome clue when checking a key.
Verify a brainpoolP512r1 ECDSA signature
Have a digest and a SEC1 public key? Give both to verify with the signature:
import { verify } from "@agntn/curves/brainpoolp512r1";
const publicKey =
"0467cea1bedf84cbdcba69a05bb2ce3a2d1c9d911d236c480929a16ad697b45a6ca127079fe8d7868671e28ef33bdf9319e2e51c84b190ac5c91b51baf0a980ba5" +
"00a7e79006194b5378f65cbe625ef2c47c64e56040d873b995b5b1ebaa4a6ce971da164391ff619af3bcfc71c5e1ad27ee0e859c2943e2de8ef7c43d3c976e9b";
const digest =
"43f800fbeaf9238c58af795bcdad04bc49cd850c394d3382953356b023210281757b30e19218a37cbd612086fbc158caa8b4e1acb2ec00837e5d941f342fb3cc"; // SHA-512 of 313233343030
const signature =
"0bd2593447cc6c02caf99d60418dd42e9a194c910e6755ed0c7059acac656b04ccfe1e8348462ee43066823aee2fed7ca012e9890dfb69866d7ae88b6506f9c7" +
"44b42304e693796618d090dbcb2a2551c3cb78534611e61fd9d1a5c0938b5b8ec6ed53d2d28999eabbd8e7792d167fcf582492403a6a0f7cc94c73a28fb76b71";
verify(signature, { digest, publicKey }); // true
verify(signature, { digest: `00${digest.slice(2)}`, publicKey }); // false
This is Wycheproof's SHA-512 vector tcId 1. The six-byte message is 313233343030 in hex. In ASCII, that's 123400. The second call replaces the digest's first byte. It doesn't add a byte.
The signature is r followed by s. That's 128 bytes, or 256 hex digits. The public key can be compressed or uncompressed SEC1 hex.
What about DER and the digest?
X.509 DER and OpenPGP MPIs need conversion first. Extract r and s. Pad each to 128 hex digits. Join them without 0x.
The digest must be hex without 0x. It can be 1 to 64 bytes. The library doesn't hash the message.
OpenPGP requires a digest at least as large as the curve's 64-byte field size. SHA-512 meets that requirement. Its 512 bits fit the 512-bit order exactly. Every bit gets a seat.
What does it refuse?
verify("00", { digest, publicKey }) // Signature must be 256 hex digits without 0x, r then s
verify(signature, { digest: "0x00", publicKey }) // Digest must be hex without 0x, 1 to 64 bytes
verify(signature, { digest: digest + "00", publicKey }) // Digest must be hex without 0x, 1 to 64 bytes
verify(signature, { digest, publicKey: p521G }) // Invalid SEC1 brainpoolP512r1 public key
liftX("0".repeat(128)) // No point of brainpoolP512r1 has this x
The third call supplies a 65-byte digest. It's refused, not truncated. Here p521G is P-521's uncompressed generator, at 133 bytes. Its length alone rules it out. Malformed inputs and points off this curve throw.
An r or s outside 1 to n - 1 returns false. The optional lowS defaults to false. Set it to true to reject s above n / 2.
CLI and agent tool
Prefer a terminal? The CLI uses curves brainpoolp512r1 <operation>:
curves brainpoolp512r1 verify --point 0467cea1bedf84cbdcba69a05bb2ce3a2d1c9d911d236c480929a16ad697b45a6ca127079fe8d7868671e28ef33bdf9319e2e51c84b190ac5c91b51baf0a980ba500a7e79006194b5378f65cbe625ef2c47c64e56040d873b995b5b1ebaa4a6ce971da164391ff619af3bcfc71c5e1ad27ee0e859c2943e2de8ef7c43d3c976e9b --digest 43f800fbeaf9238c58af795bcdad04bc49cd850c394d3382953356b023210281757b30e19218a37cbd612086fbc158caa8b4e1acb2ec00837e5d941f342fb3cc --signature 0bd2593447cc6c02caf99d60418dd42e9a194c910e6755ed0c7059acac656b04ccfe1e8348462ee43066823aee2fed7ca012e9890dfb69866d7ae88b6506f9c744b42304e693796618d090dbcb2a2551c3cb78534611e61fd9d1a5c0938b5b8ec6ed53d2d28999eabbd8e7792d167fcf582492403a6a0f7cc94c73a28fb76b71
It returns:
{"operation":"verify","valid":true}
The operations are add, subtract, negate, multiply, lift, check and verify. The agent tool curves_brainpoolp512r1_compute offers the same operations. Its arguments are point, other, scalar, x, digest, signature, lowS and compressed.
brainpoolP512r1 point math
The @agntn/curves/brainpoolp512r1 entry exports:
addPoints,subtractPointsandnegatePointmultiplyPointandmultiplyGeneratorliftX,isOnCurveandconvertPointverify,brainpoolP512r1andINVALID_POINT
brainpoolP512r1 holds the domain parameters. The other export names match the P-521 entry. Pass liftX exactly 128 hex digits for x. Scalars run from 1 to n - 1. Use a bigint or hex with at most 128 digits.
Why do those small x values matter? At x = 0, b isn't a square modulo p. So there's no point. Neither x = 1 nor x = 2 has one. Three misses, then x = 3 hits.
Length is a useful clue here, unlike the other Brainpool curves. P-256 and brainpoolP256r1 both use 32-byte coordinates. P-384 and brainpoolP384r1 both use 48. P-521 uses 66, with 67-byte compressed or 133-byte uncompressed points.
A P-384 or P-521 key fails on length alone. But the right length doesn't prove a point is valid. Use isOnCurve to check the point itself.
Written from the specs
Brainpool's parameters are pseudo-random and reproducible. They're verifiable rather than chosen as special primes for speed. Neither the prime nor a has a shortcut form.
This curve shares src/core/sec1.ts with six others. They are secp256k1, P-256, P-384, P-521, brainpoolP256r1 and brainpoolP384r1. Seven curves share one implementation.
The a = 0 and a = -3 formulas refuse this curve. They report These formulas are for another a. Addition and doubling instead use algorithms 1 and 3 from Renes, Costello and Batina (2016). These complete formulas work for any a. They cost a few more multiplications. That's a real price for generality.
The other two Brainpool curves use those formulas too. This curve changes the widths. Both n and p have 512 bits. Since p is 3 mod 4, a square root takes one power.
Multiplication walks 128 hex digits. The secret comb has four rows, 32 steps apart. The verification table has 128 rows of 15 points. Verification reads G from that faster table. Its scalars are public.
A secret multiply takes the same steps for every scalar. test/brainpoolp512r1.test.ts times 1 against n - 1. JavaScript bigints don't promise constant time. The library isn't audited. Keep real money away.
In a local run, G multiplication took about 2.1 ms. Another point took about 5.7 ms. Verification took about 6.3 ms. Generic code for curves you define took about 41 ms per multiplication. These timings aren't test assertions.
All 337 Wycheproof SHA-512 P1363 vectors pass. That's 251 valid and 86 invalid signatures. RFC 7027 appendix A.3 gives two key pairs and their shared secret. Both match.
@noble/curves has no Brainpool. Multiples of G are frozen from OpenSSL 3.6.5 through node:crypto. In a local run, OpenSSL agreed on 200 random keys. The library also verified 20 signatures made by OpenSSL.