Package: python-secp256k1
python-secp256k1WARNING: In python you do not control memory. Regardless of how secrets are passed to the underlying lib, it still was an object in python before. It will linger in the heap for some time even after going out of scope. It is also impossible to mlock() secrets, your private keys may end up on disk in swap. Use with caution!!!Python FFI bindings forlibsecp256k1(an experimental and optimized C library for EC operations on curve secp256k1) usingctypes.
Alternative implementation that usescffiinstead of ctypes issecp256k1-py.
CFFI is heavier, needs compiler for API mode (parses C headers) while ctypes does not need dependencies at all.Rationale and goalThis library aims to provide a standard way to wraplibsecp256k1usingctypes.Implementation DetailsScratch spaces are not implemented.methods fromsecp256k1_preallocated.hare not implementedThis library creates default contexts (sign/verify) at the initialization phase, randomizes them
and uses them the whole time, you do not need to worry about contexts. In case you need to randomize more often (to protect against side-channel leakage)
usepysecp256k1.context_randomize.way to provide own hash functions is not implemented - default hash functions are usedschnorrsig_sign_customdoes not accept extraparams argument, instead acceptsaux_rand32asschnorrsig_sign32- same as passingextraparams.ndataDefault illegal callback function (that is added to default contexts) logs to stderr.Method names are the same as inlibsecp256k1but without 'secp256k1_' prefix (i.e.secp256k1_ec_pubkey_serialize->ec_pubkey_serialize)Modules are structured same as in secp256k1include/directory but without 'secp256k1_' prefix.secp256k1 modulespysecp256k1 modulesimportingsecp256k1.hpysecp256k1.__init__.pyfrom pysecp256k1 import *secp256k1_ecdh.hpysecp256k1.ecdh.pyfrom pysecp256k1.ecdh import *secp256k1_extrakeys.hpysecp256k1.extrakeys.pyfrom pysecp256k1.extrakeys import *secp256k1_recovery.hpysecp256k1.recovery.pyfrom pysecp256k1.recovery import *secp256k1_schnorrsig.hpysecp256k1.schnorrsig.pyfrom pysecp256k1.schnorrsig import *Validation and data typesThis library tries to supplementlibsecp256k1with valid data ONLY, therefore heavy input type validation is in place.
Validation is implemented viaenforce_type((can be found inpysecp256k1.low_level.util)) which check for correct type (based on type hints) and correct length if possible.Internal (opaque) secp256k1 data structures are represented asctypes.c_char_Arrayto get bytes fromc_char_Arrayuse.raw(see examples).pysecp256k1 classtypeSecp256k1Pubkeyc_char_Array_64Secp256k1ECDSASignaturec_char_Array_64Secp256k1XonlyPubkeyc_char_Array_64Secp256k1Keypairc_char_Array_96Secp256k1ECDSARecoverableSignaturec_char_Array_65Secp256k1Contextc_void_pApart fromctypes.c_char_Arrayandctypes.c_void_pthis library uses a limited number of standard python types.python typeusageboolresult of signature verification functionsecdsa_verifyandschnorrsig_verifyintrecovery id, pubkey parity, result ofec_pubkey_cmpandxonly_pubkey_cmpbytestags, tweaks, messages, message hashes, serialized pubkeys, serialized signatures, seckeysList[Secp256k1Pubkey]list of initialized pubkeys forec_pubkey_combineTuple[Secp256k1XonlyPubkey, int]initialized xonly public key and its parityTuple[bytes, int]serialized recoverable signature and its recovery idOptional[bytes]optional random data forschnorrsig_sign{32,_custom}Installation and dependenciesOnly dependency ofpysecp256k1ispython3.6+andlibsecp256k1itself.
To use full feature set build secp256k1 this way:gitclonehttps://github.com/bitcoin-core/secp256k1.gitcdsecp256k1/
gitcheckoutv0.2.0
./autogen.sh
./configure--enable-module-recovery
make
makecheck
sudomakeinstallif one builds secp256k1 without schnorrsig for example and then tries to import from itfrom pysecp256k1.schnorrsig import schnorrsig_sign32RuntimeErroris raised hinting thatlibsecp256k1is built without shnorrsig support. Same applies for all optional modules.If one needs to have older version of libsecp256k1 installed in standard path and recent one for this library,
do NOT do last step from above (sudo make install) and instead export absolute path to desired.sofile in environment variable.exportPYSECP_SO=/home/johndoe/secp256k1/.libs/libsecp256k1.so.0.0.0Installpython-secp256k1python package from pypi:python3-mpipinstall-Upipwheel
python3-mpipinstallpython-secp256k1Examplesimportosfrompysecp256k1import*seckey=tagged_sha256(b"seckey",os.urandom(32))print("seckey:",seckey.hex())ec_seckey_verify(seckey)pubkey=ec_pubkey_create(seckey)print("Compressed pubkey:",ec_pubkey_serialize(pubkey).hex())msg=b"message to be signed"print("msg:",msg.decode())msg_hash=tagged_sha256(b"message",msg)print("msg hash:",msg_hash.hex())sig=ecdsa_sign(seckey,msg_hash)print("DER signature:",ecdsa_signature_serialize_der(sig).hex())print("compact signature:",ecdsa_signature_serialize_compact(sig).hex())print("Correct signature for pubkey and msg hash:",ecdsa_verify(sig,pubkey,msg_hash))Schnorrsigimportosfrompysecp256k1importtagged_sha256frompysecp256k1.extrakeysimport*frompysecp256k1.schnorrsigimport*seckey=tagged_sha256(b"seckey",os.urandom(32))print("seckey:",seckey.hex())keypair=keypair_create(seckey)xonly_pubkey,pk_parity=keypair_xonly_pub(keypair)print("xonly pubkey:",xonly_pubkey_serialize(xonly_pubkey).hex())msg=b"message to be signed"print("msg:",msg.decode())msg_hash=tagged_sha256(b"message",msg)print("msg hash:",msg_hash.hex())rand_32=os.urandom(32)sig=schnorrsig_sign32(keypair,msg_hash,aux_rand32=rand_32)print("schnorr signature:",sig.hex())print("Correct signature for xonly pubkey and msg hash:",schnorrsig_verify(sig,msg_hash,xonly_pubkey))# you can also sign variable length messages# instead of passing extraparams pointer as in secp256k1 custom takes aux_rand (equivalent of extraparams.ndata)sig0=schnorrsig_sign_custom(keypair,msg,aux_rand32=rand_32)print("schnorr signature:",sig0.hex())print("Correct signature for xonly pubkey and msg hash:",schnorrsig_verify(sig0,msg,xonly_pubkey))Recoveryimportosfrompysecp256k1importtagged_sha256frompysecp256k1importec_pubkey_createfrompysecp256k1.recoveryimport*msg=b"message to be signed"print("msg:",msg.decode())msg_hash=tagged_sha256(b"message",msg)print("msg hash:",msg_hash.hex())seckey=tagged_sha256(b"seckey",os.urandom(32))pubkey=ec_pubkey_create(seckey)rec_sig=ecdsa_sign_recoverable(seckey,msg_hash)compact_rec_sig_ser,recid=ecdsa_recoverable_signature_serialize_compact(rec_sig)print("compact signature:",compact_rec_sig_ser.hex(),"recovery id:",recid)rec_sig_parsed=ecdsa_recoverable_signature_parse_compact(compact_rec_sig_ser,recid)assertrec_sig_parsed.raw,rec_sig.rawrec_pubkey=ecdsa_recover(rec_sig,msg_hash)print("recovered pubkey is the same as original:",pubkey.raw==rec_pubkey.raw)ECDHimportosfrompysecp256k1importtagged_sha256frompysecp256k1importec_pubkey_createfrompysecp256k1.ecdhimportecdhbob_seckey=tagged_sha256(b"seckey",os.urandom(32))bob_pubkey=ec_pubkey_create(bob_seckey)alice_seckey=tagged_sha256(b"seckey",os.urandom(32))alice_pubkey=ec_pubkey_create(alice_seckey)shared_secret_bob=ecdh(bob_seckey,alice_pubkey)shared_secret_alice=ecdh(alice_seckey,bob_pubkey)print("bob and alice shared secret equals:",shared_secret_bob==shared_secret_alice)Tweakingimportosfrompysecp256k1import(ec_pubkey_create,ec_seckey_tweak_add,ec_seckey_negate,ec_seckey_verify,tagged_sha256)frompysecp256k1.extrakeysimport(keypair_create,keypair_sec,keypair_xonly_pub,xonly_pubkey_from_pubkey,xonly_pubkey_serialize,xonly_pubkey_tweak_add_check,xonly_pubkey_parse,xonly_pubkey_tweak_add,keypair_xonly_tweak_add)seckey=tagged_sha256(b"seckey",os.urandom(32))raw_pubkey=ec_pubkey_create(seckey)keypair=keypair_create(seckey)xonly_pub,parity=xonly_pubkey_from_pubkey(raw_pubkey)xonly_pub1,parity1=keypair_xonly_pub(keypair)assertxonly_pub.raw==xonly_pub1.rawassertparity==parity1ser_xonly_pub=xonly_pubkey_serialize(xonly_pub)assertxonly_pubkey_parse(ser_xonly_pub).raw==xonly_pub.rawvalid_tweak=tagged_sha256(b"tweak",seckey)# this is randomassertec_seckey_verify(valid_tweak)isNone# tweak keypairtweaked_keypair=keypair_xonly_tweak_add(keypair,valid_tweak)# below returns standard pubkey (not xonly)tweaked_pubkey=xonly_pubkey_tweak_add(xonly_pub,valid_tweak)tweaked_xonly_pub,parity2=xonly_pubkey_from_pubkey(tweaked_pubkey)tweaked_xonly_pub1,parity3=keypair_xonly_pub(tweaked_keypair)asserttweaked_xonly_pub.raw==tweaked_xonly_pub1.rawassertparity2==parity3ser_tweaked_xonly_pub=xonly_pubkey_serialize(tweaked_xonly_pub)assertxonly_pubkey_tweak_add_check(ser_tweaked_xonly_pub,parity2,xonly_pub,valid_tweak)isTrue# https://github.com/bitcoin-core/secp256k1/issues/1021ifparity==0:tweaked_seckey=ec_seckey_tweak_add(seckey,valid_tweak)else:tweaked_seckey=ec_seckey_tweak_add(ec_seckey_negate(seckey),valid_tweak)asserttweaked_seckey==keypair_sec(tweaked_keypair)Negationsimportosfrompysecp256k1importec_pubkey_create,ec_pubkey_negate,ec_seckey_negate,tagged_sha256seckey=tagged_sha256(b"seckey",os.urandom(32))pubkey=ec_pubkey_create(seckey)# double negation - result is the same seckeyassertseckey==ec_seckey_negate(ec_seckey_negate(seckey))# double negation - result is the same pubkeyassertpubkey.raw==ec_pubkey_negate(ec_pubkey_negate(pubkey)).rawTestingcdpython-secp256k1
python3-munittest-vvvor with tox against multiple python interpreterscdpython-secp256k1
tox