-
Notifications
You must be signed in to change notification settings - Fork 215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add P2SH-Multisig support #12
Conversation
Adds support for Pay-To-Script-Hash (P2SH) multi-signature contracts. The multi-signature scheme is compatible with Bitcoin Core, and therefore partially-signed transactions can be co-signed between both implementations. New classes: MultiSig and MultiSigTestnet. Example usage: from bit import * key1 = PrivateKeyTestnet("cNZYKrewErp2wG9LXiAU4H6dzgtyYsSm78tSU14QST9Hu57CMRaJ") key2 = PrivateKeyTestnet("cMgHThJYoUYc19UjMWn1xabRJLh3PCKyKfiTM1ssV2ZPe7V6eaBL") key3 = PrivateKeyTestnet("cR1C9ZB7qoVktmQDQsUP8KcTM37G1PYN3oR2B6ua27EycGDVN6fE") key1p = key1.pub_to_hex() key2p = key2.pub_to_hex() key3p = key3.pub_to_hex() plist = [key1p, key2p, key3p] from the total of three keys: multi = MultiSigTestnet(key1, plist, 2) multi.address tx = multi.create_transaction([], fee=21, leftover='mpDJqjKMyQaFjEMwYHKJBP3mM4rM2ejJdk', unspents=unspent) multi2 = MultiSigTestnet(key2, plist, 2) tx_signed = multi2.sign_transaction(tx) spending from the multi-signature contract. This is only thought of as a first start to implement P2SH and therefore bugs are still expected and more rigorous testing and optimization may be necessary.
You have done a lot of work here, thanks! I'll look soon. |
Update to latest version 0.4.1 of bit
Update multisig branch to version 0.4.1 of Bit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a bug where string is compared with bytes when constructing outputs and checking the cases of the output-types (P2PKH/P2SH)
bit/transaction.py
Outdated
# Real recipient | ||
if amount: | ||
# P2SH | ||
if amount and (b58decode_check(dest)[0] == MAIN_SCRIPT_HASH or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I produced a bug here. b58decode_check(dest)[0]
casts to a string which is then compared to MAIN_SCRIPT_HASH
which is bytes. This is serious, because outputs to P2SH will not be catched and instead be interpreted as P2PKH outputs...
bit/transaction.py
Outdated
if amount: | ||
# P2SH | ||
if amount and (b58decode_check(dest)[0] == MAIN_SCRIPT_HASH or | ||
b58decode_check(dest)[0] == TEST_SCRIPT_HASH): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same problem as above.
In #23 I talked about throwing an exception for mainnet addresses that do not begin with a 1. Am I correct in understanding that there are no tests in place for that currently? |
@teran-mckinney Yes, not yet. |
Any chance this will be finished anytime soon? I need to be able to send transactions to P2SH addresses. |
@dspechnikov: I hope to be able to continue working on it. But you can maybe try to test this branch a little, because it actually does allow you to send to P2SH addresses, see here: I just highly recommend not to use it in production, as it has not yet been tested thoroughly. |
@bjarnemagnussen Any progress on this one? |
Just to make sure: the master branch of my fork (https://github.com/bjarnemagnussen/bit) already has the ability to make P2SH-Multisig and also supports sending to Segwit addresses (including bech32) and receiving to P2SH-nested Segwit addresses. I have been fixing a few bugs I found regarding the interoperability with Bitcoin Core, so that e.g. a partially signed transaction from either Bit or Bitcoin Core can now be exchanged and fully signed. But since only I have been testing it, I am not confident in suggesting to use it in production. However, all the functionality is implemented and I did not encount any problems. So maybe just check it out with testnet coins or small amounts and let me know if there are any problems. I have not yet written any documentation for the Segwit features. But you can see the segwit address by calling
Bit will then by itself take care of creating/signing transactions using segwit inputs if there are any (make sure to call Some parts of the code are still a little cluttered and I want to clean it up a bit. Of course, that should not influence the operation of it. |
@bjarnemagnussen could you please give me little guide how to use it with multisig? |
@impowski: Have a look at my first message in this pull request. Let me know if you need some clarification on the example. |
@bjarnemagnussen when I did |
@bjarnemagnussen Yeah, it doesn't work I guess, can you check it on your machine? |
@impowski Can you show me how you initiate your keys and call the function so that I can check it? |
from bit import PrivateKeyTestnet, MultiSigTestnet
private_keys_hex = ['b4741e2e87c19256c81a0662480b75f15831c7017d28b4586882c52b89f91bcf', '3c212d5724a2a6bd5004c502a4be7ba55a1cf4a0923ffd1a9bb3cb6fd7201bed']
private_keys = [
PrivateKeyTestnet.from_hex(private_keys_hex[0]),
PrivateKeyTestnet.from_hex(private_keys_hex[1])
]
private_keys[0].instance
public_keys = [
private_keys[0].pub_to_hex(),
private_keys[1].pub_to_hex()
]
multisig = MultiSigTestnet(private_keys[0], public_keys, 2)
unspents = multisig.get_unspents()
tx = multisig.create_transaction([('2Mv7MJVHeqjyLVFUg7hc1oJoWedYySeGBsh', 100, 'usd')], unspents=unspents)
multisig2 = MultiSigTestnet(private_keys[1], public_keys, 2)
tx_signed = multisig2.sign_transaction(tx)
print(tx_signed) |
@bjarnemagnussen And when I take for example partially signed transaction and signing it with second key myself I can't broadcast it to the network. Because for the first input |
@impowski Thanks for your feedback! I have found the bug and fixed it with a new commit to my master branch. The problem was in the way signatures from partially signed transactions were extracted, since coincidently the redeemscript was exactly of the same length as typically a signature is. It is on my todo-list to improve it further. Furthermore, since you are using the segwit functionality you must make sure that also the instance |
@bjarnemagnussen It signs transaction right now, but can't broadcast it:
The output address is different, input address is basically SegWit address and in the output it's just basic address and the witness script is kinda messy I guess. |
@impowski I think this is unfortunately a problem with blockcypher using testnet. Can you try to push the transaction on testnet using blockchain.info (https://testnet.blockchain.info/pushtx) or your own node and let me know if it worked? Edit: Regarding the output address. For now it simply uses the original implemention to determine a return address, which therefore is the legacy address of the multisig. I will later at a later point make a check so that if it spends from at least one segwit input it returns to the segwit address. |
@bjarnemagnussen How do you recommend sending it? Via |
@bjarnemagnussen And the address is still different. If I write |
@bjarnemagnussen I don't know what to do now with my faucet funds they've gone to the beyond world I guess 😨 |
@impowski It should work to use a direct call to In the future I will make the send-function allow to finally sign and then directly broadcast a transaction. But until now this is not possible. I am not sure I understand your last question correctly. You can still spend the new testnet coins you received to the legacy multisig ( You are very welcome to open an issue on my fork if you find any problems! |
@bjarnemagnussen But why it sent on legacy address when I needed it on segwit address? That a kinda problem I guess. Oh I see... unspents, outputs = sanitize_tx_data(
unspents or self.unspents,
outputs,
fee or get_fee_cached(),
leftover or self.address,
combine=combine,
message=message,
compressed=self._pk.is_compressed(),
version='test'
) |
@impowski You can always manually set the return address to the segwit-address like this Edit: But this is getting a little off-topic for the multisig-implementation, so maybe open a new issue on my fork if there should be something else. |
@bjarnemagnussen Oh thanks, totally forgot about that. We will use it in production some time and I'll let you know if anything new will get in the way. Big thanks for help and for that fix 💯 |
Make some better checks to validate signatures extracted from partially signed transactions.
Used this PR in production, everything seems fine so far. Can't wait till it be finally merged in master and published. Amazing work @bjarnemagnussen 🎉 |
🎉 Works on mainnet! Moved some coins to a multisig wallet on Coinbase from a wallet generated by this library |
@ofek, what do you think about merging this? |
@teran-mckinney Go ahead 🙂 |
Here goes nothing... might need some cleanup but seems like it's time. |
woot! 👏 Thanks for your great work here! |
Updates branch to current master including MultiSig
Adds support for Pay-To-Script-Hash (P2SH) multi-signature contracts.
The multi-signature scheme is compatible with Bitcoin Core, and therefore partially-signed transactions can be co-signed between both implementations.
New classes:
MultiSig and MultiSigTestnet.
Example usage:
This is only thought of as a first start to implement P2SH and therefore bugs are still expected and more rigorous testing and optimization may be necessary.