|
@ -5,8 +5,9 @@ from asn1crypto.keys import ECDomainParameters, ECPointBitString, ECPrivateKey, |
|
|
from coincurve.context import GLOBAL_CONTEXT, Context |
|
|
from coincurve.context import GLOBAL_CONTEXT, Context |
|
|
from coincurve.ecdsa import cdata_to_der, der_to_cdata, deserialize_recoverable, recover, serialize_recoverable |
|
|
from coincurve.ecdsa import cdata_to_der, der_to_cdata, deserialize_recoverable, recover, serialize_recoverable |
|
|
from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED |
|
|
from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED |
|
|
from coincurve.types import Hasher |
|
|
from coincurve.types import Hasher, Nonce |
|
|
from coincurve.utils import ( |
|
|
from coincurve.utils import ( |
|
|
|
|
|
DEFAULT_NONCE, |
|
|
bytes_to_int, |
|
|
bytes_to_int, |
|
|
der_to_pem, |
|
|
der_to_pem, |
|
|
get_valid_secret, |
|
|
get_valid_secret, |
|
@ -20,22 +21,35 @@ from coincurve.utils import ( |
|
|
|
|
|
|
|
|
from ._libsecp256k1 import ffi, lib |
|
|
from ._libsecp256k1 import ffi, lib |
|
|
|
|
|
|
|
|
DEFAULT_NONCE = (ffi.NULL, ffi.NULL) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PrivateKey: |
|
|
class PrivateKey: |
|
|
def __init__(self, secret: bytes = None, context: Context = GLOBAL_CONTEXT): |
|
|
def __init__(self, secret: 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.secret: bytes = validate_secret(secret) if secret is not None else get_valid_secret() |
|
|
self.context = context |
|
|
self.context = context |
|
|
self.public_key: PublicKey = PublicKey.from_valid_secret(self.secret, self.context) |
|
|
self.public_key: PublicKey = PublicKey.from_valid_secret(self.secret, self.context) |
|
|
|
|
|
|
|
|
def sign(self, message: bytes, hasher: Hasher = sha256, custom_nonce=None) -> bytes: |
|
|
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)`. |
|
|
|
|
|
: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 |
|
|
msg_hash = hasher(message) if hasher is not None else message |
|
|
if len(msg_hash) != 32: |
|
|
if len(msg_hash) != 32: |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
|
|
|
|
|
|
signature = ffi.new('secp256k1_ecdsa_signature *') |
|
|
signature = ffi.new('secp256k1_ecdsa_signature *') |
|
|
nonce_fn, nonce_data = custom_nonce or DEFAULT_NONCE |
|
|
nonce_fn, nonce_data = custom_nonce |
|
|
|
|
|
|
|
|
signed = lib.secp256k1_ecdsa_sign(self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data) |
|
|
signed = lib.secp256k1_ecdsa_sign(self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data) |
|
|
|
|
|
|
|
@ -44,15 +58,27 @@ class PrivateKey: |
|
|
|
|
|
|
|
|
return cdata_to_der(signature, self.context) |
|
|
return cdata_to_der(signature, self.context) |
|
|
|
|
|
|
|
|
def sign_recoverable(self, message, hasher: Hasher = sha256): |
|
|
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)`. |
|
|
|
|
|
: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 |
|
|
msg_hash = hasher(message) if hasher is not None else message |
|
|
if len(msg_hash) != 32: |
|
|
if len(msg_hash) != 32: |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
|
|
|
|
|
|
signature = ffi.new('secp256k1_ecdsa_recoverable_signature *') |
|
|
signature = ffi.new('secp256k1_ecdsa_recoverable_signature *') |
|
|
|
|
|
nonce_fn, nonce_data = custom_nonce |
|
|
|
|
|
|
|
|
signed = lib.secp256k1_ecdsa_sign_recoverable( |
|
|
signed = lib.secp256k1_ecdsa_sign_recoverable( |
|
|
self.context.ctx, signature, msg_hash, self.secret, ffi.NULL, ffi.NULL |
|
|
self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
if not signed: |
|
|
if not signed: |
|
@ -61,13 +87,29 @@ class PrivateKey: |
|
|
return serialize_recoverable(signature, self.context) |
|
|
return serialize_recoverable(signature, self.context) |
|
|
|
|
|
|
|
|
def ecdh(self, public_key: bytes) -> bytes: |
|
|
def ecdh(self, public_key: bytes) -> bytes: |
|
|
|
|
|
""" |
|
|
|
|
|
Compute an EC Diffie-Hellman secret in constant time. |
|
|
|
|
|
|
|
|
|
|
|
: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]') |
|
|
secret = ffi.new('unsigned char [32]') |
|
|
|
|
|
|
|
|
lib.secp256k1_ecdh(self.context.ctx, secret, PublicKey(public_key).public_key, self.secret, ffi.NULL, ffi.NULL) |
|
|
lib.secp256k1_ecdh(self.context.ctx, secret, PublicKey(public_key).public_key, self.secret, ffi.NULL, ffi.NULL) |
|
|
|
|
|
|
|
|
return bytes(ffi.buffer(secret, 32)) |
|
|
return bytes(ffi.buffer(secret, 32)) |
|
|
|
|
|
|
|
|
def add(self, scalar: bytes, update=False): |
|
|
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) |
|
|
scalar = pad_scalar(scalar) |
|
|
|
|
|
|
|
|
secret = ffi.new('unsigned char [32]', self.secret) |
|
|
secret = ffi.new('unsigned char [32]', self.secret) |
|
@ -86,7 +128,15 @@ class PrivateKey: |
|
|
|
|
|
|
|
|
return PrivateKey(secret, self.context) |
|
|
return PrivateKey(secret, self.context) |
|
|
|
|
|
|
|
|
def multiply(self, scalar: bytes, update=False): |
|
|
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) |
|
|
scalar = validate_secret(scalar) |
|
|
|
|
|
|
|
|
secret = ffi.new('unsigned char [32]', self.secret) |
|
|
secret = ffi.new('unsigned char [32]', self.secret) |
|
@ -103,15 +153,27 @@ class PrivateKey: |
|
|
return PrivateKey(secret, self.context) |
|
|
return PrivateKey(secret, self.context) |
|
|
|
|
|
|
|
|
def to_hex(self) -> str: |
|
|
def to_hex(self) -> str: |
|
|
|
|
|
""" |
|
|
|
|
|
:return: The private key encoded as a hex string. |
|
|
|
|
|
""" |
|
|
return self.secret.hex() |
|
|
return self.secret.hex() |
|
|
|
|
|
|
|
|
def to_int(self) -> int: |
|
|
def to_int(self) -> int: |
|
|
|
|
|
""" |
|
|
|
|
|
:return: The private key as an integer. |
|
|
|
|
|
""" |
|
|
return bytes_to_int(self.secret) |
|
|
return bytes_to_int(self.secret) |
|
|
|
|
|
|
|
|
def to_pem(self) -> bytes: |
|
|
def to_pem(self) -> bytes: |
|
|
|
|
|
""" |
|
|
|
|
|
:return: The private key encoded in PEM format. |
|
|
|
|
|
""" |
|
|
return der_to_pem(self.to_der()) |
|
|
return der_to_pem(self.to_der()) |
|
|
|
|
|
|
|
|
def to_der(self) -> bytes: |
|
|
def to_der(self) -> bytes: |
|
|
|
|
|
""" |
|
|
|
|
|
:return: The private key encoded in DER format. |
|
|
|
|
|
""" |
|
|
pk = ECPrivateKey( |
|
|
pk = ECPrivateKey( |
|
|
{ |
|
|
{ |
|
|
'version': 'ecPrivkeyVer1', |
|
|
'version': 'ecPrivkeyVer1', |
|
@ -135,20 +197,44 @@ class PrivateKey: |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_hex(cls, hexed: str, context: Context = GLOBAL_CONTEXT): |
|
|
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) |
|
|
return PrivateKey(hex_to_bytes(hexed), context) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_int(cls, num: int, context: Context = GLOBAL_CONTEXT): |
|
|
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) |
|
|
return PrivateKey(int_to_bytes_padded(num), context) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_pem(cls, pem: bytes, context: Context = GLOBAL_CONTEXT): |
|
|
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( |
|
|
return PrivateKey( |
|
|
int_to_bytes_padded(PrivateKeyInfo.load(pem_to_der(pem)).native['private_key']['private_key']), context |
|
|
int_to_bytes_padded(PrivateKeyInfo.load(pem_to_der(pem)).native['private_key']['private_key']), context |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_der(cls, der: bytes, context: Context = GLOBAL_CONTEXT): |
|
|
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) |
|
|
return PrivateKey(int_to_bytes_padded(PrivateKeyInfo.load(der).native['private_key']['private_key']), context) |
|
|
|
|
|
|
|
|
def _update_public_key(self): |
|
|
def _update_public_key(self): |
|
@ -163,6 +249,15 @@ class PrivateKey: |
|
|
|
|
|
|
|
|
class PublicKey: |
|
|
class PublicKey: |
|
|
def __init__(self, data, context: Context = GLOBAL_CONTEXT): |
|
|
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): |
|
|
if not isinstance(data, bytes): |
|
|
self.public_key = data |
|
|
self.public_key = data |
|
|
else: |
|
|
else: |
|
@ -179,6 +274,14 @@ class PublicKey: |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT): |
|
|
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 *') |
|
|
public_key = ffi.new('secp256k1_pubkey *') |
|
|
|
|
|
|
|
|
created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, validate_secret(secret)) |
|
|
created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, validate_secret(secret)) |
|
@ -205,18 +308,49 @@ class PublicKey: |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_point(cls, x: int, y: int, context: Context = GLOBAL_CONTEXT): |
|
|
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) |
|
|
return PublicKey(b'\x04' + int_to_bytes_padded(x) + int_to_bytes_padded(y), context) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def from_signature_and_message( |
|
|
def from_signature_and_message( |
|
|
cls, serialized_sig: bytes, message: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT |
|
|
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( |
|
|
return PublicKey( |
|
|
recover(message, deserialize_recoverable(serialized_sig, context=context), hasher=hasher, context=context) |
|
|
recover(message, deserialize_recoverable(signature, context=context), hasher=hasher, context=context) |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
@classmethod |
|
|
@classmethod |
|
|
def combine_keys(cls, public_keys, context: Context = GLOBAL_CONTEXT): |
|
|
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 *') |
|
|
public_key = ffi.new('secp256k1_pubkey *') |
|
|
|
|
|
|
|
|
combined = lib.secp256k1_ec_pubkey_combine( |
|
|
combined = lib.secp256k1_ec_pubkey_combine( |
|
@ -228,7 +362,13 @@ class PublicKey: |
|
|
|
|
|
|
|
|
return PublicKey(public_key, context) |
|
|
return PublicKey(public_key, context) |
|
|
|
|
|
|
|
|
def format(self, compressed=True) -> bytes: |
|
|
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 |
|
|
length = 33 if compressed else 65 |
|
|
serialized = ffi.new('unsigned char [%d]' % length) |
|
|
serialized = ffi.new('unsigned char [%d]' % length) |
|
|
output_len = ffi.new('size_t *', length) |
|
|
output_len = ffi.new('size_t *', length) |
|
@ -240,10 +380,21 @@ class PublicKey: |
|
|
return bytes(ffi.buffer(serialized, length)) |
|
|
return bytes(ffi.buffer(serialized, length)) |
|
|
|
|
|
|
|
|
def point(self) -> Tuple[int, int]: |
|
|
def point(self) -> Tuple[int, int]: |
|
|
|
|
|
""" |
|
|
|
|
|
:return: The public key as a coordinate point. |
|
|
|
|
|
""" |
|
|
public_key = self.format(compressed=False) |
|
|
public_key = self.format(compressed=False) |
|
|
return bytes_to_int(public_key[1:33]), bytes_to_int(public_key[33:]) |
|
|
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: |
|
|
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 |
|
|
msg_hash = hasher(message) if hasher is not None else message |
|
|
if len(msg_hash) != 32: |
|
|
if len(msg_hash) != 32: |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
|
raise ValueError('Message hash must be 32 bytes long.') |
|
@ -253,7 +404,16 @@ class PublicKey: |
|
|
# A performance hack to avoid global bool() lookup. |
|
|
# A performance hack to avoid global bool() lookup. |
|
|
return not not verified |
|
|
return not not verified |
|
|
|
|
|
|
|
|
def add(self, scalar: bytes, update=False): |
|
|
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) |
|
|
scalar = pad_scalar(scalar) |
|
|
|
|
|
|
|
|
new_key = ffi.new('secp256k1_pubkey *', self.public_key[0]) |
|
|
new_key = ffi.new('secp256k1_pubkey *', self.public_key[0]) |
|
@ -269,7 +429,15 @@ class PublicKey: |
|
|
|
|
|
|
|
|
return PublicKey(new_key, self.context) |
|
|
return PublicKey(new_key, self.context) |
|
|
|
|
|
|
|
|
def multiply(self, scalar: bytes, update=False): |
|
|
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) |
|
|
scalar = validate_secret(scalar) |
|
|
|
|
|
|
|
|
new_key = ffi.new('secp256k1_pubkey *', self.public_key[0]) |
|
|
new_key = ffi.new('secp256k1_pubkey *', self.public_key[0]) |
|
@ -282,7 +450,17 @@ class PublicKey: |
|
|
|
|
|
|
|
|
return PublicKey(new_key, self.context) |
|
|
return PublicKey(new_key, self.context) |
|
|
|
|
|
|
|
|
def combine(self, public_keys, update=False): |
|
|
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 *') |
|
|
new_key = ffi.new('secp256k1_pubkey *') |
|
|
|
|
|
|
|
|
combined = lib.secp256k1_ec_pubkey_combine( |
|
|
combined = lib.secp256k1_ec_pubkey_combine( |
|
|