Open massmux opened 2 years ago
It may be due to the input type of the wif
argument. If it was created from some other software, it could be using a missing compression hint flag that determines if the wif starts with a K
/L
, or a 5
.
This is from bip-178. Although it's not formally accepted yet, it is referenced in the code base of this project.
The flag b'\x01'
is the only option for using a compressed public key with a wif. If it's missing, then that private key will eventually reference its uncompressed public key when it's creating scripts to sign. You should get your address from the private key yourself. Avoid using an address manually as your second argument, it makes this edge case harder to spot.
def spend(private_key_bytes, recipient):
private = PrivateKey.from_bytes(private_key_bytes)
address = private.to_public().to_address("P2WPKH")
# ... the rest is the same
If you only have wifs from some other system, then pull the private key bytes out of it, and ignore the compression flags entirely.
from cryptotools.HD.bip32 import base53
def spend(wif, recipient):
private = PrivateKey.from_bytes(base53.decode(wif)[1:33])
address = private.to_public().to_address("P2WPKH")
# ...
hey @massmux I think this might be related with a recent change to pull utxo data from blockstream.info instead of blockchain.info and the utxo endpoint does not contain output scripts. Could you try setting the environmental variable CRYPTOTOOLS_BACKEND
to blockchaininfo
to confirm if that's the issue ?
@mcdallas my wif controls a 3xx address in which funds are. now i am using same code to spend to a bc1 address. I set the environment var as you suggested, the situation actually changed, but i got a different error now the error is the following:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/massmux/.local/lib/python3.8/site-packages/cryptotools/BTC/address.py", line 159, in send
inp.sign(private)
File "/home/massmux/.local/lib/python3.8/site-packages/cryptotools/BTC/transaction.py", line 169, in sign
raise SigningError('Cannot sign P2SH or P2WSH outputs.')
cryptotools.BTC.error.SigningError: Cannot sign P2SH or P2WSH outputs.
can i spend only to legacy?
@yurisich my wif is created externally but it seems ok, it begins with K
@massmux You can send to segwit but the problem is where you are spending from. P2SH addresses (starting with 3) have no direct correlation to a single private key, for example they can be 2-of-3 multisig addresses that require multiple keys to sign or they can require no signature at all. As such there is no standard method on how to spend from them.
In the special case that the P2SH address is a segwit-wrapped address (P2SH-P2WPKH) then there is a 1-to-1 relation with a key and perhaps your address is such but I have not yet implemented this functionality. I'll look into adding it.
Finally I would caution against using the send/spend mechanisms in this library with mainnet addresses containing any significant amount because as I mentioned in the disclaimer this library is mostly for educational purposes and not as well battle tested as other wallets like for example electrum.
@mcdallas ok i understand. i try then to spend from bc1 to bc1. this should work, correct?
about last point, dont worry i understand. your library so far is very good, clear and well done
@mcdallas
now i am in the condition suggested. from bc1 towards bc1 address. wif starting with L. But i get again an error, different, but another one. What is it? where am i wrong?
Traceback (most recent call last):
File "
@massmux I have been spending some time attempting to implement a valid utxo spend that includes witness data. In my case, it was a P2WSH-P2SH
, like the one found in the readme of the project.
There's an issue where the sighash uses a tx version number from the default value of b'\x00\x00\x00\x01'
, when it needs to be set to b'\x00\x00\x00\x02'
. You also need to set your signature separately when creating the transaction.
private = cryptotools.PrivateKey.from_hex("...")
public = private.to_public()
script = cryptotools.push(public.encode(compressed=True)) + cryptotools.OP.CHECKSIG.byte
p2sh_address = cryptotools.script_to_address(script, "P2WSH-P2SH")
p2sh_txid = "..."
p2sh_tx = cryptotools.Transaction.get(p2sh_txid)
p2sh_output = p2sh_tx.outputs[0]
send_to = Address("...")
send_amount = 50_000
fee = p2sh_output.value - send_amount
input_tx = p2sh_output.spend()
output_tx = send_to._receive(send_amount)
tx = cryptotools.Transaction(
inputs=[input_tx],
outputs=[output_tx]
)
p2sh_bytes = cryptotools.BTC.base58.decode(p2sh_address)
payload = p2sh_bytes[1:-4]
redeem_script = cryptotools.push(cryptotools.sha256(script))
# just to demonstrate....
witness_byte = b'\x00'
assert cryptotools.BTC.script.witness_byte(witver=0) == witness_byte
assert payload == cryptotools.hash160(
witness_byte + redeem_script
)
Ok, that's the setup. Here's where things can get tricky if you're looking to build your own transaction for signing:
assert input_tx.is_nested() == False
assert input_tx.segwit == False
input_tx.script = (
# push the witness byte b'\x00' and the 0x21 byte redeem_script
b'\x22' + (b'\x00' + redeem_script)
)
assert input_tx.segwit == False
# the tx is now "nested", though
assert input_tx.is_nested() == cryptotools.TX.P2WSH
The witness data should be populated with an empty signature at first:
empty_sig = b''
input_tx.witness = [empty_sig, script]
assert input_tx.segwit == True
And then, creating a signature will fail to verify:
utxo_input_to_sign = 0
sig = private.sign_hash(tx.sighash(i=utxo_input_to_sign))
# for clarity's sake
sig_verification_strictness = b'\x01'
assert sig_verification_strictness == cryptotools.SIGHASH.ALL.byte
tx.inputs[0].witness[0] = sig.encode() + b'\x01'
# this isn't working...
assert tx.verify() == False
Fixing the verify step:
assert tx.version == 1
assert tx._version == b'\x00\x00\x00\x01'
tx._version = b'\x00\x00\x00\x02'
tx.version = 2
assert tx.version == 2
assert tx._version == b'\x00\x00\x00\x02'
assert tx.verify() == True
in this code
i run with spend("wifxxx","bc1source","bc1qdest")
i get this error
but it seems that bech32 (bc1q) is supported by the lib. where am i wrong?