Secp256k1 (secp256k1 v0.8.0)

View Source

This is the unified API for the stable secp256k1 functions this library provides.

Experimental MuSig2 signing uses process-local resources and intentionally remains outside this facade. See Secp256k1.MuSig for its protocol API.

Examples

Generate new keypair

iex> {_seckey, _pubkey} = Secp256k1.keypair(:xonly)

Derive pubkey from your awesome seckey

iex> seckey = <<0x1111111111111111111111111111111111111111111111111111111111111111::256>>
iex> pubkey = Secp256k1.pubkey(seckey, :compressed)
iex> Base.encode16(pubkey, case: :lower)
"034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"

Calculate ECDSA signature

iex> # your keypair
iex> {seckey, pubkey} = Secp256k1.keypair(:compressed)
iex> # prepare your message hash
iex> msg_hash = :crypto.hash(:sha256, "My awesome message")
iex> # generate signature
iex> sig = Secp256k1.ecdsa_sign(msg_hash, seckey)
iex> # validate your signature
iex> Secp256k1.ecdsa_valid?(sig, msg_hash, pubkey)
true

Calculate Schnorr signature

iex> # your keypair
iex> {seckey, pubkey} = Secp256k1.keypair(:xonly)
iex> # prepare your message hash
iex> msg_hash = :crypto.hash(:sha256, "My awesome message")
iex> # generate signature
iex> sig = Secp256k1.schnorr_sign(msg_hash, seckey)
iex> # validate your signature
iex> Secp256k1.schnorr_valid?(sig, msg_hash, pubkey)
true

Calculate ECDH shared secret

iex> {alice_seckey, _alice_pubkey} = Secp256k1.keypair(<<1::256>>, :compressed)
iex> {_bob_seckey, bob_pubkey} = Secp256k1.keypair(<<2::256>>, :compressed)
iex> shared_secret = Secp256k1.ecdh(alice_seckey, bob_pubkey)
iex> byte_size(shared_secret)
32

Summary

Types

Compressed pubkey is binary of 33 byte length

Standard-sized strict DER-encoded ECDSA signature (8-72 bytes), excluding any Bitcoin transaction sighash byte

Serialized compressed ECDSA signature is 64 bytes long binary

Hash is 32 bytes long binary

Pubkey is binary of 32, 33 or 65 byte length

Parity of a full public key represented in x-only form

Pubkey can be parsed in compressed (33 bytes), uncompressed (65 bytes) or xonly (32 bytes) format

Schnorr signature is 64 bytes long binary

EC secp256k1 seckey is 32 bytes long binary

libsecp256k1 default hashed ECDH shared secret is 32 bytes long binary

Scalar tweak is a 32-byte big-endian integer

Uncompressed pubkey is binary of 65 byte length

X-only pubkey is binary of 32 byte length

Functions

Convert a public key to another serialization format.

Adds a scalar multiple of the generator to a compressed or uncompressed public key.

Adds a scalar tweak to a secret key for BIP-32-style private derivation.

Compute libsecp256k1's default hashed ECDH shared secret.

Create an ECDSA signature

Converts a compact ECDSA signature to the low-S form required by libsecp256k1 verification.

Parses an 8-72-byte strict DER ECDSA signature into compact 64-byte r || s form.

Serializes a compact 64-byte ECDSA signature as strict DER.

Validate ECDSA signature.

Generate new secp256k1 keypair

Generate new secp256k1 keypair from provided seckey

Derive pubkey from provided seckey

Calculate Schnorr signature according to BIP 340

Validate Schnorr signature

Checks whether a binary encodes a valid secp256k1 public key.

Checks whether a 32-byte binary is a valid secp256k1 secret key.

Adds a scalar tweak to an x-only internal public key.

Checks an x-only public-key tweak result and parity.

Tweaks a secret key using x-only semantics for signing Taproot outputs.

Types

compressed_pubkey()

@type compressed_pubkey() :: <<_::264>>

Compressed pubkey is binary of 33 byte length

ecdsa_der_sig()

@type ecdsa_der_sig() :: binary()

Standard-sized strict DER-encoded ECDSA signature (8-72 bytes), excluding any Bitcoin transaction sighash byte

ecdsa_sig()

@type ecdsa_sig() :: <<_::512>>

Serialized compressed ECDSA signature is 64 bytes long binary

hash()

@type hash() :: <<_::256>>

Hash is 32 bytes long binary

pubkey()

@type pubkey() :: xonly_pubkey() | compressed_pubkey() | uncompressed_pubkey()

Pubkey is binary of 32, 33 or 65 byte length

pubkey_parity()

@type pubkey_parity() :: 0 | 1

Parity of a full public key represented in x-only form

pubkey_type()

@type pubkey_type() :: :compressed | :uncompressed | :xonly

Pubkey can be parsed in compressed (33 bytes), uncompressed (65 bytes) or xonly (32 bytes) format

schnorr_sig()

@type schnorr_sig() :: <<_::512>>

Schnorr signature is 64 bytes long binary

seckey()

@type seckey() :: <<_::256>>

EC secp256k1 seckey is 32 bytes long binary

shared_secret()

@type shared_secret() :: <<_::256>>

libsecp256k1 default hashed ECDH shared secret is 32 bytes long binary

tweak()

@type tweak() :: <<_::256>>

Scalar tweak is a 32-byte big-endian integer

uncompressed_pubkey()

@type uncompressed_pubkey() :: <<_::520>>

Uncompressed pubkey is binary of 65 byte length

xonly_pubkey()

@type xonly_pubkey() :: <<_::256>>

X-only pubkey is binary of 32 byte length

Functions

convert_pubkey(pubkey, atom)

@spec convert_pubkey(
  pubkey :: compressed_pubkey() | uncompressed_pubkey(),
  type :: :compressed | :uncompressed | :xonly
) ::
  compressed_pubkey()
  | uncompressed_pubkey()
  | xonly_pubkey()
  | {:error, binary() | :allocation_failed}

Convert a public key to another serialization format.

Inputs

  • pubkey an uncompressed public key when converting to :compressed, or a compressed public key when converting to :uncompressed or :xonly
  • type the target format, either :compressed, :uncompressed, or :xonly

Returns the converted public key, or {:error, reason} when the correctly sized input does not encode a valid secp256k1 public key.

ec_pubkey_tweak_add(pubkey, tweak)

@spec ec_pubkey_tweak_add(compressed_pubkey() | uncompressed_pubkey(), tweak()) ::
  compressed_pubkey()
  | uncompressed_pubkey()
  | {:error, binary() | :allocation_failed}

Adds a scalar multiple of the generator to a compressed or uncompressed public key.

The output preserves the input serialization format. This is the public-key counterpart of ec_seckey_tweak_add/2 for BIP-32-style public derivation.

ec_seckey_tweak_add(seckey, tweak)

@spec ec_seckey_tweak_add(seckey(), tweak()) ::
  seckey() | {:error, binary() | :allocation_failed}

Adds a scalar tweak to a secret key for BIP-32-style private derivation.

Returns an error when the tweak is outside the scalar field or the resulting key would be zero.

ecdh(seckey, pubkey)

@spec ecdh(seckey :: seckey(), pubkey :: compressed_pubkey() | uncompressed_pubkey()) ::
  shared_secret()

Compute libsecp256k1's default hashed ECDH shared secret.

Inputs

  • seckey 32 byte long binary
  • pubkey compressed or uncompressed secp256k1 public key

Output

  • shared_secret 32 byte binary

This wraps libsecp256k1's ECDH module. It returns the upstream library's default hashed ECDH output, currently SHA256 over the compressed shared point. For generic raw ECDH, use :crypto.compute_key/4.

ecdsa_sign(msg_hash, seckey)

@spec ecdsa_sign(msg_hash :: hash(), seckey :: seckey()) :: ecdsa_sig()

Create an ECDSA signature

Inputs

  • msg_hash 32 byte long message hash to sign
  • seckey 32 byte long binary

Output

  • signature ECDSA signature serialized in compressed format (64 byte binary)

ecdsa_signature_normalize(signature)

@spec ecdsa_signature_normalize(ecdsa_sig()) ::
  ecdsa_sig() | {:error, binary() | :allocation_failed}

Converts a compact ECDSA signature to the low-S form required by libsecp256k1 verification.

Already-normalized signatures are returned unchanged. Normalization accepts a malleable alternate encoding; use it only when the protocol deliberately accepts mathematical equivalence, and use the normalized bytes thereafter. Protocols requiring canonical low-S signatures should reject high-S instead.

ecdsa_signature_parse_der(signature)

@spec ecdsa_signature_parse_der(ecdsa_der_sig()) ::
  ecdsa_sig() | {:error, binary() | :allocation_failed}

Parses an 8-72-byte strict DER ECDSA signature into compact 64-byte r || s form.

Remove any trailing Bitcoin transaction sighash byte before parsing. Wrong-sized input raises FunctionClauseError; malformed DER in the accepted size range raises ArgumentError.

ecdsa_signature_serialize_der(signature)

@spec ecdsa_signature_serialize_der(ecdsa_sig()) ::
  ecdsa_der_sig() | {:error, binary() | :allocation_failed}

Serializes a compact 64-byte ECDSA signature as strict DER.

The result does not include a Bitcoin transaction sighash byte.

ecdsa_valid?(signature, msg_hash, pubkey)

@spec ecdsa_valid?(
  signature :: ecdsa_sig(),
  msg_hash :: hash(),
  pubkey :: compressed_pubkey() | uncompressed_pubkey()
) :: boolean()

Validate ECDSA signature.

High-S signatures return false. Normalize only if the surrounding protocol deliberately accepts malleable signature encodings.

Inputs

  • signature 64 byte long binary
  • msg_hash 32 byte long message hash that was signed
  • pubkey compressed (33-byte) or uncompressed (65-byte) public key

keypair(type)

@spec keypair(type :: pubkey_type()) :: {seckey(), pubkey()}

Generate new secp256k1 keypair

Input

Output

  • 2-tuple with seckey on the first place and pubkey on the second place

keypair(seckey, type)

@spec keypair(seckey :: seckey(), type :: pubkey_type()) :: {seckey(), pubkey()}

Generate new secp256k1 keypair from provided seckey

For options see pubkey/2

pubkey(seckey, atom)

@spec pubkey(seckey :: seckey(), type :: pubkey_type()) :: pubkey()

Derive pubkey from provided seckey

Inputs

  • seckey 32 byte long binary
  • type one of :xonly, :compressed or :uncompressed

Output

  • pubkey serialization type depends on the type provided

schnorr_sign(message, seckey)

@spec schnorr_sign(message :: binary(), seckey :: seckey()) :: schnorr_sig()

Calculate Schnorr signature according to BIP 340

Inputs

  • message can accept arbitrary long binary but only 32 byte long hash is the only option strictly according to specification
  • seckey 32 byte long binary

Output

  • signature Schnorr signature is 64 byte long binary

Note: automatic random nonce is added to every run so generated signature is not deterministic

schnorr_valid?(signature, message, pubkey)

@spec schnorr_valid?(
  signature :: schnorr_sig(),
  message :: binary(),
  pubkey :: xonly_pubkey()
) :: boolean()

Validate Schnorr signature

Inputs

  • signature 64 byte long binary
  • message arbitrary long binary
  • pubkey xonly pubkey (32 byte long binary)

valid_pubkey?(pubkey)

@spec valid_pubkey?(term()) :: boolean()

Checks whether a binary encodes a valid secp256k1 public key.

Accepts x-only (32-byte), compressed (33-byte), and uncompressed (65-byte) public-key encodings. Returns false for unsupported encodings, wrong-sized binaries, and non-binary terms.

valid_seckey?(seckey)

@spec valid_seckey?(term()) :: boolean()

Checks whether a 32-byte binary is a valid secp256k1 secret key.

This validates the scalar value, not only the binary size. A valid secret key is greater than zero and smaller than the secp256k1 curve order. Returns false for wrong-sized binaries and non-binary terms.

xonly_pubkey_tweak_add(internal_pubkey, tweak)

@spec xonly_pubkey_tweak_add(xonly_pubkey(), tweak()) ::
  {:ok, xonly_pubkey(), pubkey_parity()}
  | {:error, binary() | :allocation_failed}

Adds a scalar tweak to an x-only internal public key.

Returns the x-only output key and its full-point parity. Keep both values when constructing and verifying Taproot commitments.

xonly_pubkey_tweak_add_check(tweaked_pubkey, parity, internal_pubkey, tweak)

@spec xonly_pubkey_tweak_add_check(
  xonly_pubkey(),
  pubkey_parity(),
  xonly_pubkey(),
  tweak()
) :: boolean()

Checks an x-only public-key tweak result and parity.

This verifies the key arithmetic only. The caller remains responsible for deriving the tweak according to BIP-341 when using it as a Taproot commitment.

xonly_seckey_tweak_add(seckey, tweak)

@spec xonly_seckey_tweak_add(seckey(), tweak()) ::
  seckey() | {:error, binary() | :allocation_failed}

Tweaks a secret key using x-only semantics for signing Taproot outputs.

The keypair is normalized to an even-Y internal public key before adding the tweak.