Skip to content

Developer Interface


All objects are available directly under the root namespace coincurve.

verify_signature(signature, message, public_key, hasher=sha256, context=GLOBAL_CONTEXT)

Parameters:

Name Type Description Default
signature bytes

The ECDSA signature.

required
message bytes

The message that was supposedly signed.

required
public_key bytes

The formatted public key.

required
hasher Optional[collections.abc.Callable[[bytes], bytes]]

The hash function to use, which must return 32 bytes. By default, the sha256 algorithm is used. If None, no hashing occurs.

sha256
context Context GLOBAL_CONTEXT

Returns:

Type Description
bool

A boolean indicating whether or not the signature is correct.

Exceptions:

Type Description
ValueError

If the public key could not be parsed or was invalid, the message hash was not 32 bytes long, or the DER-encoded signature could not be parsed.

Source code in coincurve/utils.py
def verify_signature(
    signature: bytes, message: bytes, public_key: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT
) -> bool:
    """
    :param signature: The ECDSA signature.
    :param message: The message that was supposedly signed.
    :param public_key: The formatted public key.
    :param hasher: The hash function to use, which must return 32 bytes. By default,
                   the `sha256` algorithm is used. If `None`, no hashing occurs.
    :param context:
    :return: A boolean indicating whether or not the signature is correct.
    :raises ValueError: If the public key could not be parsed or was invalid, the message hash was
                        not 32 bytes long, or the DER-encoded signature could not be parsed.
    """
    pubkey = ffi.new('secp256k1_pubkey *')

    pubkey_parsed = lib.secp256k1_ec_pubkey_parse(context.ctx, pubkey, public_key, len(public_key))

    if not pubkey_parsed:
        raise ValueError('The public key could not be parsed or is invalid.')

    msg_hash = hasher(message) if hasher is not None else message
    if len(msg_hash) != 32:
        raise ValueError('Message hash must be 32 bytes long.')

    sig = ffi.new('secp256k1_ecdsa_signature *')

    sig_parsed = lib.secp256k1_ecdsa_signature_parse_der(context.ctx, sig, signature, len(signature))

    if not sig_parsed:
        raise ValueError('The DER-encoded signature could not be parsed.')

    verified = lib.secp256k1_ecdsa_verify(context.ctx, sig, msg_hash, pubkey)

    # A performance hack to avoid global bool() lookup.
    return not not verified

PrivateKey

Source code in coincurve/keys.py
class PrivateKey:
    def __init__(self, secret: Optional[bytes] = None, context: Context = GLOBAL_CONTEXT):
        """
        :param secret: The secret used to initialize the private key.
                       If not provided or `None`, a new key will be generated.
        """
        self.secret: bytes = validate_secret(secret) if secret is not None else get_valid_secret()
        self.context = context
        self.public_key: PublicKey = PublicKey.from_valid_secret(self.secret, self.context)
        self.public_key_xonly: PublicKeyXOnly = PublicKeyXOnly.from_valid_secret(self.secret, self.context)

    def sign(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
        """
        Create an ECDSA signature.

        :param message: The message to sign.
        :param hasher: The hash function to use, which must return 32 bytes. By default,
                       the `sha256` algorithm is used. If `None`, no hashing occurs.
        :param custom_nonce: Custom nonce data in the form `(nonce_function, input_data)`. Refer to
                             [secp256k1.h](https://github.com/bitcoin-core/secp256k1/blob/f8c0b57e6ba202b1ce7c5357688de97c9c067697/include/secp256k1.h#L546-L547).
        :return: The ECDSA signature.
        :raises ValueError: If the message hash was not 32 bytes long, the nonce generation
                            function failed, or the private key was invalid.
        """
        msg_hash = hasher(message) if hasher is not None else message
        if len(msg_hash) != 32:
            raise ValueError('Message hash must be 32 bytes long.')

        signature = ffi.new('secp256k1_ecdsa_signature *')
        nonce_fn, nonce_data = custom_nonce

        signed = lib.secp256k1_ecdsa_sign(self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data)

        if not signed:
            raise ValueError('The nonce generation function failed, or the private key was invalid.')

        return cdata_to_der(signature, self.context)

    def sign_schnorr(self, message: bytes, aux_randomness: bytes = b'') -> bytes:
        """Create a Schnorr signature.

        :param message: The message to sign.
        :param aux_randomness: An optional 32 bytes of fresh randomness. By default (empty bytestring), this
                               will be generated automatically. Set to `None` to disable this behavior.
        :return: The Schnorr signature.
        :raises ValueError: If the message was not 32 bytes long, the optional auxiliary random data was not
                            32 bytes long, signing failed, or the signature was invalid.
        """
        if len(message) != 32:
            raise ValueError('Message must be 32 bytes long.')
        elif aux_randomness == b'':
            aux_randomness = os.urandom(32)
        elif aux_randomness is None:
            aux_randomness = ffi.NULL
        elif len(aux_randomness) != 32:
            raise ValueError('Auxiliary random data must be 32 bytes long.')

        keypair = ffi.new('secp256k1_keypair *')
        res = lib.secp256k1_keypair_create(self.context.ctx, keypair, self.secret)
        if not res:
            raise ValueError('Secret was invalid')

        signature = ffi.new('unsigned char[64]')
        res = lib.secp256k1_schnorrsig_sign32(self.context.ctx, signature, message, keypair, aux_randomness)
        if not res:
            raise ValueError('Signing failed')

        res = lib.secp256k1_schnorrsig_verify(
            self.context.ctx, signature, message, len(message), self.public_key_xonly.public_key
        )
        if not res:
            raise ValueError('Invalid signature')

        return bytes(ffi.buffer(signature))

    def sign_recoverable(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
        """
        Create a recoverable ECDSA signature.

        :param message: The message to sign.
        :param hasher: The hash function to use, which must return 32 bytes. By default,
                       the `sha256` algorithm is used. If `None`, no hashing occurs.
        :param custom_nonce: Custom nonce data in the form `(nonce_function, input_data)`. Refer to
                             [secp256k1_recovery.h](https://github.com/bitcoin-core/secp256k1/blob/f8c0b57e6ba202b1ce7c5357688de97c9c067697/include/secp256k1_recovery.h#L78-L79).
        :return: The recoverable ECDSA signature.
        :raises ValueError: If the message hash was not 32 bytes long, the nonce generation
                            function failed, or the private key was invalid.
        """
        msg_hash = hasher(message) if hasher is not None else message
        if len(msg_hash) != 32:
            raise ValueError('Message hash must be 32 bytes long.')

        signature = ffi.new('secp256k1_ecdsa_recoverable_signature *')
        nonce_fn, nonce_data = custom_nonce

        signed = lib.secp256k1_ecdsa_sign_recoverable(
            self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data
        )

        if not signed:
            raise ValueError('The nonce generation function failed, or the private key was invalid.')

        return serialize_recoverable(signature, self.context)

    def ecdh(self, public_key: bytes) -> bytes:
        """
        Compute an EC Diffie-Hellman secret in constant time.

        !!! note
            This prevents malleability by returning `sha256(compressed_public_key)` instead of the `x` coordinate
            directly. See #9.

        :param public_key: The formatted public key.
        :return: The 32 byte shared secret.
        :raises ValueError: If the public key could not be parsed or was invalid.
        """
        secret = ffi.new('unsigned char [32]')

        lib.secp256k1_ecdh(self.context.ctx, secret, PublicKey(public_key).public_key, self.secret, ffi.NULL, ffi.NULL)

        return bytes(ffi.buffer(secret, 32))

    def add(self, scalar: bytes, update: bool = False):
        """
        Add a scalar to the private key.

        :param scalar: The scalar with which to add.
        :param update: Whether or not to update and return the private key in-place.
        :return: The new private key, or the modified private key if `update` is `True`.
        :rtype: PrivateKey
        :raises ValueError: If the tweak was out of range or the resulting private key was invalid.
        """
        scalar = pad_scalar(scalar)

        secret = ffi.new('unsigned char [32]', self.secret)

        success = lib.secp256k1_ec_seckey_tweak_add(self.context.ctx, secret, scalar)

        if not success:
            raise ValueError('The tweak was out of range, or the resulting private key is invalid.')

        secret = bytes(ffi.buffer(secret, 32))

        if update:
            self.secret = secret
            self._update_public_key()
            return self

        return PrivateKey(secret, self.context)

    def multiply(self, scalar: bytes, update: bool = False):
        """
        Multiply the private key by a scalar.

        :param scalar: The scalar with which to multiply.
        :param update: Whether or not to update and return the private key in-place.
        :return: The new private key, or the modified private key if `update` is `True`.
        :rtype: PrivateKey
        """
        scalar = validate_secret(scalar)

        secret = ffi.new('unsigned char [32]', self.secret)

        lib.secp256k1_ec_seckey_tweak_mul(self.context.ctx, secret, scalar)

        secret = bytes(ffi.buffer(secret, 32))

        if update:
            self.secret = secret
            self._update_public_key()
            return self

        return PrivateKey(secret, self.context)

    def to_hex(self) -> str:
        """
        :return: The private key encoded as a hex string.
        """
        return self.secret.hex()

    def to_int(self) -> int:
        """
        :return: The private key as an integer.
        """
        return bytes_to_int(self.secret)

    def to_pem(self) -> bytes:
        """
        :return: The private key encoded in PEM format.
        """
        return der_to_pem(self.to_der())

    def to_der(self) -> bytes:
        """
        :return: The private key encoded in DER format.
        """
        pk = ECPrivateKey(
            {
                'version': 'ecPrivkeyVer1',
                'private_key': self.to_int(),
                'public_key': ECPointBitString(self.public_key.format(compressed=False)),
            }
        )

        return PrivateKeyInfo(
            {
                'version': 0,
                'private_key_algorithm': PrivateKeyAlgorithm(
                    {
                        'algorithm': 'ec',
                        'parameters': ECDomainParameters(name='named', value='1.3.132.0.10'),
                    }
                ),
                'private_key': pk,
            }
        ).dump()

    @classmethod
    def from_hex(cls, hexed: str, context: Context = GLOBAL_CONTEXT):
        """
        :param hexed: The private key encoded as a hex string.
        :param context:
        :return: The private key.
        :rtype: PrivateKey
        """
        return PrivateKey(hex_to_bytes(hexed), context)

    @classmethod
    def from_int(cls, num: int, context: Context = GLOBAL_CONTEXT):
        """
        :param num: The private key as an integer.
        :param context:
        :return: The private key.
        :rtype: PrivateKey
        """
        return PrivateKey(int_to_bytes_padded(num), context)

    @classmethod
    def from_pem(cls, pem: bytes, context: Context = GLOBAL_CONTEXT):
        """
        :param pem: The private key encoded in PEM format.
        :param context:
        :return: The private key.
        :rtype: PrivateKey
        """
        return PrivateKey(
            int_to_bytes_padded(PrivateKeyInfo.load(pem_to_der(pem)).native['private_key']['private_key']), context
        )

    @classmethod
    def from_der(cls, der: bytes, context: Context = GLOBAL_CONTEXT):
        """
        :param der: The private key encoded in DER format.
        :param context:
        :return: The private key.
        :rtype: PrivateKey
        """
        return PrivateKey(int_to_bytes_padded(PrivateKeyInfo.load(der).native['private_key']['private_key']), context)

    def _update_public_key(self):
        created = lib.secp256k1_ec_pubkey_create(self.context.ctx, self.public_key.public_key, self.secret)

        if not created:
            raise ValueError('Invalid secret.')

    def __eq__(self, other) -> bool:
        return self.secret == other.secret

__init__(self, secret=None, context=GLOBAL_CONTEXT) special

Parameters:

Name Type Description Default
secret Optional[bytes]

The secret used to initialize the private key. If not provided or None, a new key will be generated.

None
Source code in coincurve/keys.py
def __init__(self, secret: Optional[bytes] = None, context: Context = GLOBAL_CONTEXT):
    """
    :param secret: The secret used to initialize the private key.
                   If not provided or `None`, a new key will be generated.
    """
    self.secret: bytes = validate_secret(secret) if secret is not None else get_valid_secret()
    self.context = context
    self.public_key: PublicKey = PublicKey.from_valid_secret(self.secret, self.context)
    self.public_key_xonly: PublicKeyXOnly = PublicKeyXOnly.from_valid_secret(self.secret, self.context)

add(self, scalar, update=False)

Add a scalar to the private key.

Parameters:

Name Type Description Default
scalar bytes

The scalar with which to add.

required
update bool

Whether or not to update and return the private key in-place.

False

Returns:

Type Description
PrivateKey

The new private key, or the modified private key if update is True.

Exceptions:

Type Description
ValueError

If the tweak was out of range or the resulting private key was invalid.

Source code in coincurve/keys.py
def add(self, scalar: bytes, update: bool = False):
    """
    Add a scalar to the private key.

    :param scalar: The scalar with which to add.
    :param update: Whether or not to update and return the private key in-place.
    :return: The new private key, or the modified private key if `update` is `True`.
    :rtype: PrivateKey
    :raises ValueError: If the tweak was out of range or the resulting private key was invalid.
    """
    scalar = pad_scalar(scalar)

    secret = ffi.new('unsigned char [32]', self.secret)

    success = lib.secp256k1_ec_seckey_tweak_add(self.context.ctx, secret, scalar)

    if not success:
        raise ValueError('The tweak was out of range, or the resulting private key is invalid.')

    secret = bytes(ffi.buffer(secret, 32))

    if update:
        self.secret = secret
        self._update_public_key()
        return self

    return PrivateKey(secret, self.context)

ecdh(self, public_key)

Compute an EC Diffie-Hellman secret in constant time.

Note

This prevents malleability by returning sha256(compressed_public_key) instead of the x coordinate directly. See #9.

Parameters:

Name Type Description Default
public_key bytes

The formatted public key.

required

Returns:

Type Description
bytes

The 32 byte shared secret.

Exceptions:

Type Description
ValueError

If the public key could not be parsed or was invalid.

Source code in coincurve/keys.py
def ecdh(self, public_key: bytes) -> bytes:
    """
    Compute an EC Diffie-Hellman secret in constant time.

    !!! note
        This prevents malleability by returning `sha256(compressed_public_key)` instead of the `x` coordinate
        directly. See #9.

    :param public_key: The formatted public key.
    :return: The 32 byte shared secret.
    :raises ValueError: If the public key could not be parsed or was invalid.
    """
    secret = ffi.new('unsigned char [32]')

    lib.secp256k1_ecdh(self.context.ctx, secret, PublicKey(public_key).public_key, self.secret, ffi.NULL, ffi.NULL)

    return bytes(ffi.buffer(secret, 32))

from_der(der, context=GLOBAL_CONTEXT) classmethod

Parameters:

Name Type Description Default
der bytes

The private key encoded in DER format.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PrivateKey

The private key.

Source code in coincurve/keys.py
@classmethod
def from_der(cls, der: bytes, context: Context = GLOBAL_CONTEXT):
    """
    :param der: The private key encoded in DER format.
    :param context:
    :return: The private key.
    :rtype: PrivateKey
    """
    return PrivateKey(int_to_bytes_padded(PrivateKeyInfo.load(der).native['private_key']['private_key']), context)

from_hex(hexed, context=GLOBAL_CONTEXT) classmethod

Parameters:

Name Type Description Default
hexed str

The private key encoded as a hex string.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PrivateKey

The private key.

Source code in coincurve/keys.py
@classmethod
def from_hex(cls, hexed: str, context: Context = GLOBAL_CONTEXT):
    """
    :param hexed: The private key encoded as a hex string.
    :param context:
    :return: The private key.
    :rtype: PrivateKey
    """
    return PrivateKey(hex_to_bytes(hexed), context)

from_int(num, context=GLOBAL_CONTEXT) classmethod

Parameters:

Name Type Description Default
num int

The private key as an integer.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PrivateKey

The private key.

Source code in coincurve/keys.py
@classmethod
def from_int(cls, num: int, context: Context = GLOBAL_CONTEXT):
    """
    :param num: The private key as an integer.
    :param context:
    :return: The private key.
    :rtype: PrivateKey
    """
    return PrivateKey(int_to_bytes_padded(num), context)

from_pem(pem, context=GLOBAL_CONTEXT) classmethod

Parameters:

Name Type Description Default
pem bytes

The private key encoded in PEM format.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PrivateKey

The private key.

Source code in coincurve/keys.py
@classmethod
def from_pem(cls, pem: bytes, context: Context = GLOBAL_CONTEXT):
    """
    :param pem: The private key encoded in PEM format.
    :param context:
    :return: The private key.
    :rtype: PrivateKey
    """
    return PrivateKey(
        int_to_bytes_padded(PrivateKeyInfo.load(pem_to_der(pem)).native['private_key']['private_key']), context
    )

multiply(self, scalar, update=False)

Multiply the private key by a scalar.

Parameters:

Name Type Description Default
scalar bytes

The scalar with which to multiply.

required
update bool

Whether or not to update and return the private key in-place.

False

Returns:

Type Description
PrivateKey

The new private key, or the modified private key if update is True.

Source code in coincurve/keys.py
def multiply(self, scalar: bytes, update: bool = False):
    """
    Multiply the private key by a scalar.

    :param scalar: The scalar with which to multiply.
    :param update: Whether or not to update and return the private key in-place.
    :return: The new private key, or the modified private key if `update` is `True`.
    :rtype: PrivateKey
    """
    scalar = validate_secret(scalar)

    secret = ffi.new('unsigned char [32]', self.secret)

    lib.secp256k1_ec_seckey_tweak_mul(self.context.ctx, secret, scalar)

    secret = bytes(ffi.buffer(secret, 32))

    if update:
        self.secret = secret
        self._update_public_key()
        return self

    return PrivateKey(secret, self.context)

sign(self, message, hasher=sha256, custom_nonce=(ffi.NULL, ffi.NULL))

Create an ECDSA signature.

Parameters:

Name Type Description Default
message bytes

The message to sign.

required
hasher Optional[collections.abc.Callable[[bytes], bytes]]

The hash function to use, which must return 32 bytes. By default, the sha256 algorithm is used. If None, no hashing occurs.

sha256
custom_nonce Tuple[_cffi_backend._CDataBase, _cffi_backend._CDataBase]

Custom nonce data in the form (nonce_function, input_data). Refer to secp256k1.h.

(ffi.NULL, ffi.NULL)

Returns:

Type Description
bytes

The ECDSA signature.

Exceptions:

Type Description
ValueError

If the message hash was not 32 bytes long, the nonce generation function failed, or the private key was invalid.

Source code in coincurve/keys.py
def sign(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
    """
    Create an ECDSA signature.

    :param message: The message to sign.
    :param hasher: The hash function to use, which must return 32 bytes. By default,
                   the `sha256` algorithm is used. If `None`, no hashing occurs.
    :param custom_nonce: Custom nonce data in the form `(nonce_function, input_data)`. Refer to
                         [secp256k1.h](https://github.com/bitcoin-core/secp256k1/blob/f8c0b57e6ba202b1ce7c5357688de97c9c067697/include/secp256k1.h#L546-L547).
    :return: The ECDSA signature.
    :raises ValueError: If the message hash was not 32 bytes long, the nonce generation
                        function failed, or the private key was invalid.
    """
    msg_hash = hasher(message) if hasher is not None else message
    if len(msg_hash) != 32:
        raise ValueError('Message hash must be 32 bytes long.')

    signature = ffi.new('secp256k1_ecdsa_signature *')
    nonce_fn, nonce_data = custom_nonce

    signed = lib.secp256k1_ecdsa_sign(self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data)

    if not signed:
        raise ValueError('The nonce generation function failed, or the private key was invalid.')

    return cdata_to_der(signature, self.context)

sign_recoverable(self, message, hasher=sha256, custom_nonce=(ffi.NULL, ffi.NULL))

Create a recoverable ECDSA signature.

Parameters:

Name Type Description Default
message bytes

The message to sign.

required
hasher Optional[collections.abc.Callable[[bytes], bytes]]

The hash function to use, which must return 32 bytes. By default, the sha256 algorithm is used. If None, no hashing occurs.

sha256
custom_nonce Tuple[_cffi_backend._CDataBase, _cffi_backend._CDataBase]

Custom nonce data in the form (nonce_function, input_data). Refer to secp256k1_recovery.h.

(ffi.NULL, ffi.NULL)

Returns:

Type Description
bytes

The recoverable ECDSA signature.

Exceptions:

Type Description
ValueError

If the message hash was not 32 bytes long, the nonce generation function failed, or the private key was invalid.

Source code in coincurve/keys.py
def sign_recoverable(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
    """
    Create a recoverable ECDSA signature.

    :param message: The message to sign.
    :param hasher: The hash function to use, which must return 32 bytes. By default,
                   the `sha256` algorithm is used. If `None`, no hashing occurs.
    :param custom_nonce: Custom nonce data in the form `(nonce_function, input_data)`. Refer to
                         [secp256k1_recovery.h](https://github.com/bitcoin-core/secp256k1/blob/f8c0b57e6ba202b1ce7c5357688de97c9c067697/include/secp256k1_recovery.h#L78-L79).
    :return: The recoverable ECDSA signature.
    :raises ValueError: If the message hash was not 32 bytes long, the nonce generation
                        function failed, or the private key was invalid.
    """
    msg_hash = hasher(message) if hasher is not None else message
    if len(msg_hash) != 32:
        raise ValueError('Message hash must be 32 bytes long.')

    signature = ffi.new('secp256k1_ecdsa_recoverable_signature *')
    nonce_fn, nonce_data = custom_nonce

    signed = lib.secp256k1_ecdsa_sign_recoverable(
        self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data
    )

    if not signed:
        raise ValueError('The nonce generation function failed, or the private key was invalid.')

    return serialize_recoverable(signature, self.context)

sign_schnorr(self, message, aux_randomness=b'')

Create a Schnorr signature.

Parameters:

Name Type Description Default
message bytes

The message to sign.

required
aux_randomness bytes

An optional 32 bytes of fresh randomness. By default (empty bytestring), this will be generated automatically. Set to None to disable this behavior.

b''

Returns:

Type Description
bytes

The Schnorr signature.

Exceptions:

Type Description
ValueError

If the message was not 32 bytes long, the optional auxiliary random data was not 32 bytes long, signing failed, or the signature was invalid.

Source code in coincurve/keys.py
def sign_schnorr(self, message: bytes, aux_randomness: bytes = b'') -> bytes:
    """Create a Schnorr signature.

    :param message: The message to sign.
    :param aux_randomness: An optional 32 bytes of fresh randomness. By default (empty bytestring), this
                           will be generated automatically. Set to `None` to disable this behavior.
    :return: The Schnorr signature.
    :raises ValueError: If the message was not 32 bytes long, the optional auxiliary random data was not
                        32 bytes long, signing failed, or the signature was invalid.
    """
    if len(message) != 32:
        raise ValueError('Message must be 32 bytes long.')
    elif aux_randomness == b'':
        aux_randomness = os.urandom(32)
    elif aux_randomness is None:
        aux_randomness = ffi.NULL
    elif len(aux_randomness) != 32:
        raise ValueError('Auxiliary random data must be 32 bytes long.')

    keypair = ffi.new('secp256k1_keypair *')
    res = lib.secp256k1_keypair_create(self.context.ctx, keypair, self.secret)
    if not res:
        raise ValueError('Secret was invalid')

    signature = ffi.new('unsigned char[64]')
    res = lib.secp256k1_schnorrsig_sign32(self.context.ctx, signature, message, keypair, aux_randomness)
    if not res:
        raise ValueError('Signing failed')

    res = lib.secp256k1_schnorrsig_verify(
        self.context.ctx, signature, message, len(message), self.public_key_xonly.public_key
    )
    if not res:
        raise ValueError('Invalid signature')

    return bytes(ffi.buffer(signature))

to_der(self)

Returns:

Type Description
bytes

The private key encoded in DER format.

Source code in coincurve/keys.py
def to_der(self) -> bytes:
    """
    :return: The private key encoded in DER format.
    """
    pk = ECPrivateKey(
        {
            'version': 'ecPrivkeyVer1',
            'private_key': self.to_int(),
            'public_key': ECPointBitString(self.public_key.format(compressed=False)),
        }
    )

    return PrivateKeyInfo(
        {
            'version': 0,
            'private_key_algorithm': PrivateKeyAlgorithm(
                {
                    'algorithm': 'ec',
                    'parameters': ECDomainParameters(name='named', value='1.3.132.0.10'),
                }
            ),
            'private_key': pk,
        }
    ).dump()

to_hex(self)

Returns:

Type Description
str

The private key encoded as a hex string.

Source code in coincurve/keys.py
def to_hex(self) -> str:
    """
    :return: The private key encoded as a hex string.
    """
    return self.secret.hex()

to_int(self)

Returns:

Type Description
int

The private key as an integer.

Source code in coincurve/keys.py
def to_int(self) -> int:
    """
    :return: The private key as an integer.
    """
    return bytes_to_int(self.secret)

to_pem(self)

Returns:

Type Description
bytes

The private key encoded in PEM format.

Source code in coincurve/keys.py
def to_pem(self) -> bytes:
    """
    :return: The private key encoded in PEM format.
    """
    return der_to_pem(self.to_der())

PublicKey

Source code in coincurve/keys.py
class PublicKey:
    def __init__(self, data, context: Context = GLOBAL_CONTEXT):
        """
        :param data: The formatted public key. This class supports parsing
                     compressed (33 bytes, header byte `0x02` or `0x03`),
                     uncompressed (65 bytes, header byte `0x04`), or
                     hybrid (65 bytes, header byte `0x06` or `0x07`) format public keys.
        :type data: bytes
        :param context:
        :raises ValueError: If the public key could not be parsed or was invalid.
        """
        if not isinstance(data, bytes):
            self.public_key = data
        else:
            public_key = ffi.new('secp256k1_pubkey *')

            parsed = lib.secp256k1_ec_pubkey_parse(context.ctx, public_key, data, len(data))

            if not parsed:
                raise ValueError('The public key could not be parsed or is invalid.')

            self.public_key = public_key

        self.context = context

    @classmethod
    def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
        """
        Derive a public key from a private key secret.

        :param secret: The private key secret.
        :param context:
        :return: The public key.
        :rtype: PublicKey
        """
        public_key = ffi.new('secp256k1_pubkey *')

        created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, validate_secret(secret))

        if not created:  # no cov
            raise ValueError(
                'Somehow an invalid secret was used. Please '
                'submit this as an issue here: '
                'https://github.com/ofek/coincurve/issues/new'
            )

        return PublicKey(public_key, context)

    @classmethod
    def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
        public_key = ffi.new('secp256k1_pubkey *')

        created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, secret)

        if not created:
            raise ValueError('Invalid secret.')

        return PublicKey(public_key, context)

    @classmethod
    def from_point(cls, x: int, y: int, context: Context = GLOBAL_CONTEXT):
        """
        Derive a public key from a coordinate point in the form `(x, y)`.

        :param x:
        :param y:
        :param context:
        :return: The public key.
        :rtype: PublicKey
        """
        return PublicKey(b'\x04' + int_to_bytes_padded(x) + int_to_bytes_padded(y), context)

    @classmethod
    def from_signature_and_message(
        cls, signature: bytes, message: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT
    ):
        """
        Recover an ECDSA public key from a recoverable signature.

        :param signature: The recoverable ECDSA signature.
        :param message: The message that was supposedly signed.
        :param hasher: The hash function to use, which must return 32 bytes. By default,
                       the `sha256` algorithm is used. If `None`, no hashing occurs.
        :param context:
        :return: The public key that signed the message.
        :rtype: PublicKey
        :raises ValueError: If the message hash was not 32 bytes long or recovery of the ECDSA public key failed.
        """
        return PublicKey(
            recover(message, deserialize_recoverable(signature, context=context), hasher=hasher, context=context)
        )

    @classmethod
    def combine_keys(cls, public_keys, context: Context = GLOBAL_CONTEXT):
        """
        Add a number of public keys together.

        :param public_keys: A sequence of public keys.
        :type public_keys: List[PublicKey]
        :param context:
        :return: The combined public key.
        :rtype: PublicKey
        :raises ValueError: If the sum of the public keys was invalid.
        """
        public_key = ffi.new('secp256k1_pubkey *')

        combined = lib.secp256k1_ec_pubkey_combine(
            context.ctx, public_key, [pk.public_key for pk in public_keys], len(public_keys)
        )

        if not combined:
            raise ValueError('The sum of the public keys is invalid.')

        return PublicKey(public_key, context)

    def format(self, compressed: bool = True) -> bytes:
        """
        Format the public key.

        :param compressed: Whether or to use the compressed format.
        :return: The 33 byte formatted public key, or the 65 byte formatted public key if `compressed` is `False`.
        """
        length = 33 if compressed else 65
        serialized = ffi.new('unsigned char [%d]' % length)
        output_len = ffi.new('size_t *', length)

        lib.secp256k1_ec_pubkey_serialize(
            self.context.ctx, serialized, output_len, self.public_key, EC_COMPRESSED if compressed else EC_UNCOMPRESSED
        )

        return bytes(ffi.buffer(serialized, length))

    def point(self) -> Tuple[int, int]:
        """
        :return: The public key as a coordinate point.
        """
        public_key = self.format(compressed=False)
        return bytes_to_int(public_key[1:33]), bytes_to_int(public_key[33:])

    def verify(self, signature: bytes, message: bytes, hasher: Hasher = sha256) -> bool:
        """
        :param signature: The ECDSA signature.
        :param message: The message that was supposedly signed.
        :param hasher: The hash function to use, which must return 32 bytes. By default,
                       the `sha256` algorithm is used. If `None`, no hashing occurs.
        :return: A boolean indicating whether or not the signature is correct.
        :raises ValueError: If the message hash was not 32 bytes long or the DER-encoded signature could not be parsed.
        """
        msg_hash = hasher(message) if hasher is not None else message
        if len(msg_hash) != 32:
            raise ValueError('Message hash must be 32 bytes long.')

        verified = lib.secp256k1_ecdsa_verify(self.context.ctx, der_to_cdata(signature), msg_hash, self.public_key)

        # A performance hack to avoid global bool() lookup.
        return not not verified

    def add(self, scalar: bytes, update: bool = False):
        """
        Add a scalar to the public key.

        :param scalar: The scalar with which to add.
        :param update: Whether or not to update and return the public key in-place.
        :return: The new public key, or the modified public key if `update` is `True`.
        :rtype: PublicKey
        :raises ValueError: If the tweak was out of range or the resulting public key was invalid.
        """
        scalar = pad_scalar(scalar)

        new_key = ffi.new('secp256k1_pubkey *', self.public_key[0])

        success = lib.secp256k1_ec_pubkey_tweak_add(self.context.ctx, new_key, scalar)

        if not success:
            raise ValueError('The tweak was out of range, or the resulting public key is invalid.')

        if update:
            self.public_key = new_key
            return self

        return PublicKey(new_key, self.context)

    def multiply(self, scalar: bytes, update: bool = False):
        """
        Multiply the public key by a scalar.

        :param scalar: The scalar with which to multiply.
        :param update: Whether or not to update and return the public key in-place.
        :return: The new public key, or the modified public key if `update` is `True`.
        :rtype: PublicKey
        """
        scalar = validate_secret(scalar)

        new_key = ffi.new('secp256k1_pubkey *', self.public_key[0])

        lib.secp256k1_ec_pubkey_tweak_mul(self.context.ctx, new_key, scalar)

        if update:
            self.public_key = new_key
            return self

        return PublicKey(new_key, self.context)

    def combine(self, public_keys, update: bool = False):
        """
        Add a number of public keys together.

        :param public_keys: A sequence of public keys.
        :type public_keys: List[PublicKey]
        :param update: Whether or not to update and return the public key in-place.
        :return: The combined public key, or the modified public key if `update` is `True`.
        :rtype: PublicKey
        :raises ValueError: If the sum of the public keys was invalid.
        """
        new_key = ffi.new('secp256k1_pubkey *')

        combined = lib.secp256k1_ec_pubkey_combine(
            self.context.ctx, new_key, [pk.public_key for pk in [self, *public_keys]], len(public_keys) + 1
        )

        if not combined:
            raise ValueError('The sum of the public keys is invalid.')

        if update:
            self.public_key = new_key
            return self

        return PublicKey(new_key, self.context)

    def __eq__(self, other) -> bool:
        return self.format(compressed=False) == other.format(compressed=False)

__init__(self, data, context=GLOBAL_CONTEXT) special

Parameters:

Name Type Description Default
data bytes

The formatted public key. This class supports parsing compressed (33 bytes, header byte 0x02 or 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header byte 0x06 or 0x07) format public keys.

required
context Context GLOBAL_CONTEXT

Exceptions:

Type Description
ValueError

If the public key could not be parsed or was invalid.

Source code in coincurve/keys.py
def __init__(self, data, context: Context = GLOBAL_CONTEXT):
    """
    :param data: The formatted public key. This class supports parsing
                 compressed (33 bytes, header byte `0x02` or `0x03`),
                 uncompressed (65 bytes, header byte `0x04`), or
                 hybrid (65 bytes, header byte `0x06` or `0x07`) format public keys.
    :type data: bytes
    :param context:
    :raises ValueError: If the public key could not be parsed or was invalid.
    """
    if not isinstance(data, bytes):
        self.public_key = data
    else:
        public_key = ffi.new('secp256k1_pubkey *')

        parsed = lib.secp256k1_ec_pubkey_parse(context.ctx, public_key, data, len(data))

        if not parsed:
            raise ValueError('The public key could not be parsed or is invalid.')

        self.public_key = public_key

    self.context = context

add(self, scalar, update=False)

Add a scalar to the public key.

Parameters:

Name Type Description Default
scalar bytes

The scalar with which to add.

required
update bool

Whether or not to update and return the public key in-place.

False

Returns:

Type Description
PublicKey

The new public key, or the modified public key if update is True.

Exceptions:

Type Description
ValueError

If the tweak was out of range or the resulting public key was invalid.

Source code in coincurve/keys.py
def add(self, scalar: bytes, update: bool = False):
    """
    Add a scalar to the public key.

    :param scalar: The scalar with which to add.
    :param update: Whether or not to update and return the public key in-place.
    :return: The new public key, or the modified public key if `update` is `True`.
    :rtype: PublicKey
    :raises ValueError: If the tweak was out of range or the resulting public key was invalid.
    """
    scalar = pad_scalar(scalar)

    new_key = ffi.new('secp256k1_pubkey *', self.public_key[0])

    success = lib.secp256k1_ec_pubkey_tweak_add(self.context.ctx, new_key, scalar)

    if not success:
        raise ValueError('The tweak was out of range, or the resulting public key is invalid.')

    if update:
        self.public_key = new_key
        return self

    return PublicKey(new_key, self.context)

combine(self, public_keys, update=False)

Add a number of public keys together.

Parameters:

Name Type Description Default
public_keys List[PublicKey]

A sequence of public keys.

required
update bool

Whether or not to update and return the public key in-place.

False

Returns:

Type Description
PublicKey

The combined public key, or the modified public key if update is True.

Exceptions:

Type Description
ValueError

If the sum of the public keys was invalid.

Source code in coincurve/keys.py
def combine(self, public_keys, update: bool = False):
    """
    Add a number of public keys together.

    :param public_keys: A sequence of public keys.
    :type public_keys: List[PublicKey]
    :param update: Whether or not to update and return the public key in-place.
    :return: The combined public key, or the modified public key if `update` is `True`.
    :rtype: PublicKey
    :raises ValueError: If the sum of the public keys was invalid.
    """
    new_key = ffi.new('secp256k1_pubkey *')

    combined = lib.secp256k1_ec_pubkey_combine(
        self.context.ctx, new_key, [pk.public_key for pk in [self, *public_keys]], len(public_keys) + 1
    )

    if not combined:
        raise ValueError('The sum of the public keys is invalid.')

    if update:
        self.public_key = new_key
        return self

    return PublicKey(new_key, self.context)

combine_keys(public_keys, context=GLOBAL_CONTEXT) classmethod

Add a number of public keys together.

Parameters:

Name Type Description Default
public_keys List[PublicKey]

A sequence of public keys.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PublicKey

The combined public key.

Exceptions:

Type Description
ValueError

If the sum of the public keys was invalid.

Source code in coincurve/keys.py
@classmethod
def combine_keys(cls, public_keys, context: Context = GLOBAL_CONTEXT):
    """
    Add a number of public keys together.

    :param public_keys: A sequence of public keys.
    :type public_keys: List[PublicKey]
    :param context:
    :return: The combined public key.
    :rtype: PublicKey
    :raises ValueError: If the sum of the public keys was invalid.
    """
    public_key = ffi.new('secp256k1_pubkey *')

    combined = lib.secp256k1_ec_pubkey_combine(
        context.ctx, public_key, [pk.public_key for pk in public_keys], len(public_keys)
    )

    if not combined:
        raise ValueError('The sum of the public keys is invalid.')

    return PublicKey(public_key, context)

format(self, compressed=True)

Format the public key.

Parameters:

Name Type Description Default
compressed bool

Whether or to use the compressed format.

True

Returns:

Type Description
bytes

The 33 byte formatted public key, or the 65 byte formatted public key if compressed is False.

Source code in coincurve/keys.py
def format(self, compressed: bool = True) -> bytes:
    """
    Format the public key.

    :param compressed: Whether or to use the compressed format.
    :return: The 33 byte formatted public key, or the 65 byte formatted public key if `compressed` is `False`.
    """
    length = 33 if compressed else 65
    serialized = ffi.new('unsigned char [%d]' % length)
    output_len = ffi.new('size_t *', length)

    lib.secp256k1_ec_pubkey_serialize(
        self.context.ctx, serialized, output_len, self.public_key, EC_COMPRESSED if compressed else EC_UNCOMPRESSED
    )

    return bytes(ffi.buffer(serialized, length))

from_point(x, y, context=GLOBAL_CONTEXT) classmethod

Derive a public key from a coordinate point in the form (x, y).

Parameters:

Name Type Description Default
x int required
y int required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PublicKey

The public key.

Source code in coincurve/keys.py
@classmethod
def from_point(cls, x: int, y: int, context: Context = GLOBAL_CONTEXT):
    """
    Derive a public key from a coordinate point in the form `(x, y)`.

    :param x:
    :param y:
    :param context:
    :return: The public key.
    :rtype: PublicKey
    """
    return PublicKey(b'\x04' + int_to_bytes_padded(x) + int_to_bytes_padded(y), context)

from_secret(secret, context=GLOBAL_CONTEXT) classmethod

Derive a public key from a private key secret.

Parameters:

Name Type Description Default
secret bytes

The private key secret.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description
PublicKey

The public key.

Source code in coincurve/keys.py
@classmethod
def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
    """
    Derive a public key from a private key secret.

    :param secret: The private key secret.
    :param context:
    :return: The public key.
    :rtype: PublicKey
    """
    public_key = ffi.new('secp256k1_pubkey *')

    created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, validate_secret(secret))

    if not created:  # no cov
        raise ValueError(
            'Somehow an invalid secret was used. Please '
            'submit this as an issue here: '
            'https://github.com/ofek/coincurve/issues/new'
        )

    return PublicKey(public_key, context)

from_signature_and_message(signature, message, hasher=sha256, context=GLOBAL_CONTEXT) classmethod

Recover an ECDSA public key from a recoverable signature.

Parameters:

Name Type Description Default
signature bytes

The recoverable ECDSA signature.

required
message bytes

The message that was supposedly signed.

required
hasher Optional[collections.abc.Callable[[bytes], bytes]]

The hash function to use, which must return 32 bytes. By default, the sha256 algorithm is used. If None, no hashing occurs.

sha256
context Context GLOBAL_CONTEXT

Returns:

Type Description
PublicKey

The public key that signed the message.

Exceptions:

Type Description
ValueError

If the message hash was not 32 bytes long or recovery of the ECDSA public key failed.

Source code in coincurve/keys.py
@classmethod
def from_signature_and_message(
    cls, signature: bytes, message: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT
):
    """
    Recover an ECDSA public key from a recoverable signature.

    :param signature: The recoverable ECDSA signature.
    :param message: The message that was supposedly signed.
    :param hasher: The hash function to use, which must return 32 bytes. By default,
                   the `sha256` algorithm is used. If `None`, no hashing occurs.
    :param context:
    :return: The public key that signed the message.
    :rtype: PublicKey
    :raises ValueError: If the message hash was not 32 bytes long or recovery of the ECDSA public key failed.
    """
    return PublicKey(
        recover(message, deserialize_recoverable(signature, context=context), hasher=hasher, context=context)
    )

multiply(self, scalar, update=False)

Multiply the public key by a scalar.

Parameters:

Name Type Description Default
scalar bytes

The scalar with which to multiply.

required
update bool

Whether or not to update and return the public key in-place.

False

Returns:

Type Description
PublicKey

The new public key, or the modified public key if update is True.

Source code in coincurve/keys.py
def multiply(self, scalar: bytes, update: bool = False):
    """
    Multiply the public key by a scalar.

    :param scalar: The scalar with which to multiply.
    :param update: Whether or not to update and return the public key in-place.
    :return: The new public key, or the modified public key if `update` is `True`.
    :rtype: PublicKey
    """
    scalar = validate_secret(scalar)

    new_key = ffi.new('secp256k1_pubkey *', self.public_key[0])

    lib.secp256k1_ec_pubkey_tweak_mul(self.context.ctx, new_key, scalar)

    if update:
        self.public_key = new_key
        return self

    return PublicKey(new_key, self.context)

point(self)

Returns:

Type Description
Tuple[int, int]

The public key as a coordinate point.

Source code in coincurve/keys.py
def point(self) -> Tuple[int, int]:
    """
    :return: The public key as a coordinate point.
    """
    public_key = self.format(compressed=False)
    return bytes_to_int(public_key[1:33]), bytes_to_int(public_key[33:])

verify(self, signature, message, hasher=sha256)

Parameters:

Name Type Description Default
signature bytes

The ECDSA signature.

required
message bytes

The message that was supposedly signed.

required
hasher Optional[collections.abc.Callable[[bytes], bytes]]

The hash function to use, which must return 32 bytes. By default, the sha256 algorithm is used. If None, no hashing occurs.

sha256

Returns:

Type Description
bool

A boolean indicating whether or not the signature is correct.

Exceptions:

Type Description
ValueError

If the message hash was not 32 bytes long or the DER-encoded signature could not be parsed.

Source code in coincurve/keys.py
def verify(self, signature: bytes, message: bytes, hasher: Hasher = sha256) -> bool:
    """
    :param signature: The ECDSA signature.
    :param message: The message that was supposedly signed.
    :param hasher: The hash function to use, which must return 32 bytes. By default,
                   the `sha256` algorithm is used. If `None`, no hashing occurs.
    :return: A boolean indicating whether or not the signature is correct.
    :raises ValueError: If the message hash was not 32 bytes long or the DER-encoded signature could not be parsed.
    """
    msg_hash = hasher(message) if hasher is not None else message
    if len(msg_hash) != 32:
        raise ValueError('Message hash must be 32 bytes long.')

    verified = lib.secp256k1_ecdsa_verify(self.context.ctx, der_to_cdata(signature), msg_hash, self.public_key)

    # A performance hack to avoid global bool() lookup.
    return not not verified

PublicKeyXOnly

Source code in coincurve/keys.py
class PublicKeyXOnly:
    def __init__(self, data, parity: bool = False, context: Context = GLOBAL_CONTEXT):
        """A BIP340 `x-only` public key.

        :param data: The formatted public key.
        :type data: bytes
        :param parity: Whether the encoded point is the negation of the public key.
        :param context:
        """
        if not isinstance(data, bytes):
            self.public_key = data
        else:
            public_key = ffi.new('secp256k1_xonly_pubkey *')
            parsed = lib.secp256k1_xonly_pubkey_parse(context.ctx, public_key, data)
            if not parsed:
                raise ValueError('The public key could not be parsed or is invalid.')

            self.public_key = public_key

        self.parity = parity
        self.context = context

    @classmethod
    def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
        """Derive an x-only public key from a private key secret.

        :param secret: The private key secret.
        :param context:
        :return: The x-only public key.
        """
        keypair = ffi.new('secp256k1_keypair *')
        res = lib.secp256k1_keypair_create(context.ctx, keypair, validate_secret(secret))
        if not res:
            raise ValueError('Secret was invalid')

        xonly_pubkey = ffi.new('secp256k1_xonly_pubkey *')
        pk_parity = ffi.new('int *')
        res = lib.secp256k1_keypair_xonly_pub(context.ctx, xonly_pubkey, pk_parity, keypair)

        return cls(xonly_pubkey, parity=not not pk_parity[0], context=context)

    @classmethod
    def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
        keypair = ffi.new('secp256k1_keypair *')
        res = lib.secp256k1_keypair_create(context.ctx, keypair, secret)
        if not res:
            raise ValueError('Secret was invalid')

        xonly_pubkey = ffi.new('secp256k1_xonly_pubkey *')
        pk_parity = ffi.new('int *')
        res = lib.secp256k1_keypair_xonly_pub(context.ctx, xonly_pubkey, pk_parity, keypair)

        return cls(xonly_pubkey, parity=not not pk_parity[0], context=context)

    def format(self) -> bytes:
        """Serialize the public key.

        :return: The public key serialized as 32 bytes.
        """
        output32 = ffi.new('unsigned char [32]')

        res = lib.secp256k1_xonly_pubkey_serialize(self.context.ctx, output32, self.public_key)
        if not res:
            raise ValueError('Public key in self.public_key must be valid')

        return bytes(ffi.buffer(output32, 32))

    def verify(self, signature: bytes, message: bytes) -> bool:
        """Verify a Schnorr signature over a given message.

        :param signature: The 64-byte Schnorr signature to verify.
        :param message: The message to be verified.
        :return: A boolean indicating whether or not the signature is correct.
        """
        if len(signature) != 64:
            raise ValueError('Signature must be 32 bytes long.')

        return not not lib.secp256k1_schnorrsig_verify(
            self.context.ctx, signature, message, len(message), self.public_key
        )

    def tweak_add(self, scalar: bytes):
        """Add a scalar to the public key.

        :param scalar: The scalar with which to add.
        :return: The modified public key.
        :rtype: PublicKeyXOnly
        :raises ValueError: If the tweak was out of range or the resulting public key was invalid.
        """
        scalar = pad_scalar(scalar)

        out_pubkey = ffi.new('secp256k1_pubkey *')
        res = lib.secp256k1_xonly_pubkey_tweak_add(self.context.ctx, out_pubkey, self.public_key, scalar)
        if not res:
            raise ValueError('The tweak was out of range, or the resulting public key would be invalid')

        pk_parity = ffi.new('int *')
        lib.secp256k1_xonly_pubkey_from_pubkey(self.context.ctx, self.public_key, pk_parity, out_pubkey)
        self.parity = not not pk_parity[0]

    def __eq__(self, other) -> bool:
        res = lib.secp256k1_xonly_pubkey_cmp(self.context.ctx, self.public_key, other.public_key)
        return res == 0

__init__(self, data, parity=False, context=GLOBAL_CONTEXT) special

A BIP340 x-only public key.

Parameters:

Name Type Description Default
data bytes

The formatted public key.

required
parity bool

Whether the encoded point is the negation of the public key.

False
context Context GLOBAL_CONTEXT
Source code in coincurve/keys.py
def __init__(self, data, parity: bool = False, context: Context = GLOBAL_CONTEXT):
    """A BIP340 `x-only` public key.

    :param data: The formatted public key.
    :type data: bytes
    :param parity: Whether the encoded point is the negation of the public key.
    :param context:
    """
    if not isinstance(data, bytes):
        self.public_key = data
    else:
        public_key = ffi.new('secp256k1_xonly_pubkey *')
        parsed = lib.secp256k1_xonly_pubkey_parse(context.ctx, public_key, data)
        if not parsed:
            raise ValueError('The public key could not be parsed or is invalid.')

        self.public_key = public_key

    self.parity = parity
    self.context = context

format(self)

Serialize the public key.

Returns:

Type Description
bytes

The public key serialized as 32 bytes.

Source code in coincurve/keys.py
def format(self) -> bytes:
    """Serialize the public key.

    :return: The public key serialized as 32 bytes.
    """
    output32 = ffi.new('unsigned char [32]')

    res = lib.secp256k1_xonly_pubkey_serialize(self.context.ctx, output32, self.public_key)
    if not res:
        raise ValueError('Public key in self.public_key must be valid')

    return bytes(ffi.buffer(output32, 32))

from_secret(secret, context=GLOBAL_CONTEXT) classmethod

Derive an x-only public key from a private key secret.

Parameters:

Name Type Description Default
secret bytes

The private key secret.

required
context Context GLOBAL_CONTEXT

Returns:

Type Description

The x-only public key.

Source code in coincurve/keys.py
@classmethod
def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT):
    """Derive an x-only public key from a private key secret.

    :param secret: The private key secret.
    :param context:
    :return: The x-only public key.
    """
    keypair = ffi.new('secp256k1_keypair *')
    res = lib.secp256k1_keypair_create(context.ctx, keypair, validate_secret(secret))
    if not res:
        raise ValueError('Secret was invalid')

    xonly_pubkey = ffi.new('secp256k1_xonly_pubkey *')
    pk_parity = ffi.new('int *')
    res = lib.secp256k1_keypair_xonly_pub(context.ctx, xonly_pubkey, pk_parity, keypair)

    return cls(xonly_pubkey, parity=not not pk_parity[0], context=context)

tweak_add(self, scalar)

Add a scalar to the public key.

Parameters:

Name Type Description Default
scalar bytes

The scalar with which to add.

required

Returns:

Type Description
PublicKeyXOnly

The modified public key.

Exceptions:

Type Description
ValueError

If the tweak was out of range or the resulting public key was invalid.

Source code in coincurve/keys.py
def tweak_add(self, scalar: bytes):
    """Add a scalar to the public key.

    :param scalar: The scalar with which to add.
    :return: The modified public key.
    :rtype: PublicKeyXOnly
    :raises ValueError: If the tweak was out of range or the resulting public key was invalid.
    """
    scalar = pad_scalar(scalar)

    out_pubkey = ffi.new('secp256k1_pubkey *')
    res = lib.secp256k1_xonly_pubkey_tweak_add(self.context.ctx, out_pubkey, self.public_key, scalar)
    if not res:
        raise ValueError('The tweak was out of range, or the resulting public key would be invalid')

    pk_parity = ffi.new('int *')
    lib.secp256k1_xonly_pubkey_from_pubkey(self.context.ctx, self.public_key, pk_parity, out_pubkey)
    self.parity = not not pk_parity[0]

verify(self, signature, message)

Verify a Schnorr signature over a given message.

Parameters:

Name Type Description Default
signature bytes

The 64-byte Schnorr signature to verify.

required
message bytes

The message to be verified.

required

Returns:

Type Description
bool

A boolean indicating whether or not the signature is correct.

Source code in coincurve/keys.py
def verify(self, signature: bytes, message: bytes) -> bool:
    """Verify a Schnorr signature over a given message.

    :param signature: The 64-byte Schnorr signature to verify.
    :param message: The message to be verified.
    :return: A boolean indicating whether or not the signature is correct.
    """
    if len(signature) != 64:
        raise ValueError('Signature must be 32 bytes long.')

    return not not lib.secp256k1_schnorrsig_verify(
        self.context.ctx, signature, message, len(message), self.public_key
    )

Last update: November 15, 2022