I have recently changed verification code in python-bitcointx to use libsecp256k1 library.
In the python-bitcoinlib code (that python-bitcointx is a derivative of), openssl functions were used for signature verification.
I want to be able to remove dependency on openssl in the future, and use only libsecp256k1 in python-bitcointx, but there is a problem that I noticed when I re-wrote CECKey.verify() method:
libsecp256k1 function secp256k1_ecdsa_signature_parse_der() only allows strictly valid DER-encoded signatures.
bitcoind uses ecdsa_signature_parse_der_lax() function, copied from libsecp256k1's contrib/ directory. This function is not compiled in a normal compilation cycle of libsecp256k1.
It is expected for bitcoin-focused library to be able to verify signatures that bitcoind will also verify.
To be able to verify non-strict-encoded signatures with libsecp256k1, I see 3 options:
EDIT: I might just implement two verification methods, strict and non-strict, and then use them in tests. openssl dependency will stay because of verify_nonstrict().
- get ecdsa_signature_parse_der_lax() function from libsecp256k1/contrib, add it to python-bitcointx repository, compile at python package build time, install with a package.
This is not desireable, because it adds requirement to have C compiler to build the python package. In some environments this may be inconvenient.
- re-implement ecdsa_signature_parse_der_lax() in python
I would like to avoid this, to reduce the size of the code (reduce the surface where bug may be introduced). Porting this function correctly will require to invest some time.
It will also make verification slower to some degree, because more operations will be done in python, vs calling C functions.
- do a cycle of decode-with-openssl / encode-with-openssl / decode-with-libsecp256k1
This way all encoding violations that openssl allows are also allowed by first decode-with-openssl. encode-with-openssl then creates valid encoding, and decode-with-libsecp256k1 is happy.
The problem with this approach (in addition to this decode/encode cycle) is that it will allow some format violations that bitcoind won't allow, because ecdsa_signature_parse_der_lax() only allows an arbitrary subset of violations allowed by openssl.
python-bitcoinlib was using openssl to decode, too, so I went with the later approach - decode/encode/decode. This means I cannot drop openssl dependency.
My question is: what may be the implications of this choice ?
What I can see is that when someone uses python-bitcointx or python-bitcoinlib to verify inputs in some transaction, and these inputs contain signatures that is accepted by openssl, but not by bitcoind, they might be convinced that tx is valid, but will have unpleasant surprise when they try to broadcast it.
With some protocols that require parties to store signed transactions, that might be relevant.
As this is historical behavior of python-bitcoinlib, I am inclined to just document this in the README.md as a 'gotcha', and leave it as it is now.
But maybe there is some other potential problems that could arise out of this issue, that may be more serious, and the first or second options described above should be considered ?