Class Secp256k1
ECDSA over the secp256k1 curve, the signature scheme of Bitcoin and Ethereum, written in portable Java so a wallet can sign on the device and a server can verify or sign with the same class.
Keys are raw bytes: a private key is 32 big-endian bytes in [1, n-1], a
public key is the SEC 1 encoding of the point, either compressed (33
bytes, 02/03 then x) or uncompressed (65 bytes, 04 then x then
y). Every method accepts a public key in either form.
Signing an Ethereum transaction
byte[] priv = Hash.fromHex("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318");
// the address is the last 20 bytes of keccak256(x || y)
byte[] pub = Secp256k1.publicKey(priv, false); // 04 || x || y
byte[] xy = new byte[64];
System.arraycopy(pub, 1, xy, 0, 64);
byte[] digest = Hash.keccak256(xy);
byte[] address = new byte[20];
System.arraycopy(digest, 12, address, 0, 20); // 2c7536e3605d9c16a7a3d7b1898e529396a65c23
// sign the keccak256 of the RLP-encoded transaction
byte[] rlp = Hash.fromHex("02c0"); // your encoded transaction
Secp256k1.EcdsaSignature sig = Secp256k1.sign(Hash.keccak256(rlp), priv);
byte[] r = sig.r();
byte[] s = sig.s();
int v = 27 + sig.recoveryId(); // or chainId * 2 + 35 + recoveryId
Signatures are deterministic (RFC 6979, HMAC-SHA256) and always low-S, as
Bitcoin's standardness rules and Ethereum's EIP-2 require, and they carry
the recovery id Ethereum derives the signer's address from. verify(byte[], Secp256k1.EcdsaSignature, byte[])
rejects a high-S signature the way libsecp256k1 does; recoverPublicKey(byte[], Secp256k1.EcdsaSignature, boolean)
accepts either, the way the EVM's ecrecover does. A signature from
another library can be lowered with Secp256k1.EcdsaSignature.normalize().
Constant time
Everything that touches a private key or a nonce -- key derivation, signing, the tweak used by BIP32 derivation -- runs in constant time: the field and scalar arithmetic uses fixed 16-bit limbs with no data-dependent branches, the curve arithmetic uses complete addition formulas that have no special case for doubling or the point at infinity, and the scalar multiplication is a fixed-window ladder whose table lookup reads every entry. Verification and recovery handle public data only and share the same code. The implementation is independent of libsecp256k1 and depends on nothing but this package and the core JDK subset, so it runs identically on every port and on the backend.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classAn ECDSA signature:randsas 32-byte big-endian integers, and the recovery id a signer knows and a verifier does not need. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intLength in bytes of a compressed public key (02/03thenx).static final intLength in bytes of the message hash a signature covers.static final intLength in bytes of a private key.static final intLength in bytes of an uncompressed public key (04thenxtheny). -
Method Summary
Modifier and TypeMethodDescriptionstatic byte[]convertPublicKey(byte[] publicKey, boolean compressed) Re-encodes a public key in the requested form.static booleanisValidPrivateKey(byte[] privateKey) Tells whether the bytes are a usable private key: exactly 32 bytes holding a big-endian integer in[1, n-1].static booleanisValidPublicKey(byte[] publicKey) Tells whether the bytes encode a point on the curve, in either SEC 1 form.static byte[]privateKeyTweakAdd(byte[] privateKey, byte[] tweak) Adds a tweak to a private key modulo the curve order: the private half of a BIP32 child derivation.static byte[]publicKey(byte[] privateKey, boolean compressed) Derives the public key of a private key.static byte[]publicKeyAdd(byte[] a, byte[] b, boolean compressed) Adds two public keys as curve points.static byte[]publicKeyTweakAdd(byte[] publicKey, byte[] tweak, boolean compressed) Addstweak * Gto a public key: the public half of a BIP32 child derivation, matchingprivateKeyTweakAdd(byte[], byte[])on the private key.static byte[]recoverPublicKey(byte[] hash, Secp256k1.EcdsaSignature signature, boolean compressed) Recovers the public key that made a signature, from the signature's recovery id: what Ethereum does instead of carrying the sender's key in a transaction.static Secp256k1.EcdsaSignaturesign(byte[] hash, byte[] privateKey) Signs a 32-byte message hash.static booleanverify(byte[] hash, Secp256k1.EcdsaSignature signature, byte[] publicKey) Verifies a signature over a 32-byte hash.
-
Field Details
-
PRIVATE_KEY_LENGTH
public static final int PRIVATE_KEY_LENGTHLength in bytes of a private key.- See Also:
-
HASH_LENGTH
public static final int HASH_LENGTHLength in bytes of the message hash a signature covers.- See Also:
-
COMPRESSED_PUBLIC_KEY_LENGTH
public static final int COMPRESSED_PUBLIC_KEY_LENGTHLength in bytes of a compressed public key (02/03thenx).- See Also:
-
UNCOMPRESSED_PUBLIC_KEY_LENGTH
public static final int UNCOMPRESSED_PUBLIC_KEY_LENGTHLength in bytes of an uncompressed public key (04thenxtheny).- See Also:
-
-
Method Details
-
isValidPrivateKey
public static boolean isValidPrivateKey(byte[] privateKey) Tells whether the bytes are a usable private key: exactly 32 bytes holding a big-endian integer in[1, n-1]. -
publicKey
public static byte[] publicKey(byte[] privateKey, boolean compressed) Derives the public key of a private key.
Parameters
-
privateKey: 32 bytes in[1, n-1] -
compressed:truefor the 33-byte SEC 1 form,falsefor the 65-byte form Ethereum derives an address from
Throws
CryptoException: if the private key is not valid
-
-
isValidPublicKey
public static boolean isValidPublicKey(byte[] publicKey) Tells whether the bytes encode a point on the curve, in either SEC 1 form. -
convertPublicKey
public static byte[] convertPublicKey(byte[] publicKey, boolean compressed) Re-encodes a public key in the requested form.
Throws
CryptoException: if the key is not a point on the curve
-
privateKeyTweakAdd
public static byte[] privateKeyTweakAdd(byte[] privateKey, byte[] tweak) Adds a tweak to a private key modulo the curve order: the private half of a BIP32 child derivation. Constant time.
Parameters
-
privateKey: 32 bytes in[1, n-1] -
tweak: 32 bytes, less thann
Returns
(privateKey + tweak) mod n, 32 bytesThrows
CryptoException: if either input is out of range or the sum is zero
-
-
publicKeyTweakAdd
public static byte[] publicKeyTweakAdd(byte[] publicKey, byte[] tweak, boolean compressed) Adds
tweak * Gto a public key: the public half of a BIP32 child derivation, matchingprivateKeyTweakAdd(byte[], byte[])on the private key.Throws
CryptoException: if the key is not on the curve, the tweak is not less thann, or the result is the point at infinity
-
publicKeyAdd
public static byte[] publicKeyAdd(byte[] a, byte[] b, boolean compressed) Adds two public keys as curve points.
Throws
CryptoException: if either key is not on the curve or the sum is the point at infinity
-
sign
Signs a 32-byte message hash. The nonce is derived from the key and the hash as RFC 6979 specifies with HMAC-SHA256, so signing the same hash twice gives the same signature and no random number generator is involved. The result is always low-S and carries its recovery id.
Parameters
-
hash: 32 bytes, the digest of the message (Keccak-256 of the RLP-encoded transaction on Ethereum, double SHA-256 on Bitcoin) -
privateKey: 32 bytes in[1, n-1]
Throws
CryptoException: if the hash is not 32 bytes or the key is not valid
-
-
verify
Verifies a signature over a 32-byte hash. A signature whose
sis in the upper half of the range is rejected, as libsecp256k1 and the Bitcoin and Ethereum networks reject it; seeSecp256k1.EcdsaSignature.normalize().Returns
trueif the signature was made overhashby the holder of the private key behindpublicKeyThrows
CryptoException: if the hash is not 32 bytes or the public key is not a point on the curve
-
recoverPublicKey
public static byte[] recoverPublicKey(byte[] hash, Secp256k1.EcdsaSignature signature, boolean compressed) Recovers the public key that made a signature, from the signature's recovery id: what Ethereum does instead of carrying the sender's key in a transaction. Unlike
verify(byte[], Secp256k1.EcdsaSignature, byte[])this accepts a high-S signature, as the EVM'secrecoverdoes.Parameters
-
hash: the 32 bytes that were signed -
signature: a signature whose recovery id is known (0 to 3) -
compressed: the form to return the key in
Returns
the public key, or
nullif no key could have produced the signatureThrows
CryptoException: if the hash is not 32 bytes or the signature has no recovery id
-
-