Hello all!
I have been asked to recover some tether that have been sent to the wrong address... Sitting on the blockchain in a multisig copay account.
I have written small php command line tools to reproduce the case and learn more about how Bitcoin works. I am a software developer working mostly with NEM where the blockchain is very different. Ok, so all that gives me is enthusiasm and passion when it comes to blockchains, lots of hours passed... and I have learned a lot about multisig on Bitcoin, OP_RETURNs used in omni (usdt being a smartproperty on the omni layer), and many more awesome things about hierarchical key generation, key derivation functions, and many more subjects.
But I struggle getting back those tether to another address
Of course, I own the 3 cosigners configured with copay, but I am too much of a noob to get those tether back. Fact is, the NEM blockchain is very different when it comes to multisig and custom assets. So I went through the learning process for HD keys and XPRV and XPUBs. Which I also built a command for. A command that, provided with the right derivation path and xpubs or mnemonics,
will generate the same cosigners xpubs as copay displays in the multisig wallet information page.
I also built a command that, out of XPUBs, will display a *redeemScript* for the given derivation path as well as the scriptPubKey of the derived account (copay uses m/44'/0'/0' for your main accounts (the cosigners) but uses m/0/x where x is the addresses counter, for generating payment addresses.
I am able to reproduce exactly the same OP_HASH160 THE_HASH OP_EQUAL. And i am referring to a valid bitcoin transaction's output in my transactions inputs.
I have 2 inputs (both multisig output scripts generated with the bitcoin php lib from Bit-Wasp), the first input contains the tether OP_RETURN along with a OP_HASH160 multisig verification. The second input contains enough BTC to pay the miner fee (0.002 BTC for 487 Bytes transaction) and also has a OP_HASH160 multisig verification.
So both
my generated inputs are equal to the scriptPubKey that I am able to generate with my HD derivation tool. The redeemScript generated by this tool tells me the order of cosignatories (lexicographically ordeded public keys) and the path for copay multisig addresses are
xpub/0/1 xpub/0/2, etc. where xpub is the xpubs of cosigners which are generated with the derivation path
m/44'/0'/0'. So the absolute path to the generated addresses are:
m/44'/0'/0'/0/1, next address would be
m/44'/0'/0'/0/2, etc.
Great, I have learned so much about key derivation and how multisig is represented on Bitcoin!!!
Yet now the real fun happens.. How to get this OP_RETURN statement to be reproduced on a tether address? Turns out they are always bitcoin addresses.
So I thought I need 2 outputs, same as what happened to the tether originally, I would reproduce by sending the bundle to a omniwallet address.
Here comes my problem.. I don't really understand the omni transaction fully. I have converted the hash into a suit of big endian numbers (also working for little endian and machine byte order code so.. maybe i pick the wrong).
But from my understanding of the redeemScript and scriptPubKey, I have to reproduce the OP_RETURN hash with in my output, or
is it right to only copy it into a new sequence of opcodes?. Thats what I did now, so one of my output specifies the exact same OP_RETURN xxxx as the Bitcoin input transaction I am referring to and a value of 0. In the second output which is a payToAddress with the omni address, I put a 0.0005 btc amount which will leave 0.0015 btc for the fee. Transaction is 487 bytes big.
So I am unsure here.. I have gone through a few errors coming from a pushtx API, the error i am stuck on now is
16: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)Ok, this error is really not the worse I have seen, its very explicit.. and thats what I need help with. I need to know how to read the omniwallet Op_Return data correctly so that the script is evaluated correctly, or is it something else that I am missing?
I am able to provide with screenshots, and unsigned transaction data and will do so, but I need a little input first, maybe my mistake is obvious to experts of the Bitcoin network.
I have reproduced this case on an address with not a lot of money and will provide with screenshots and source code in the morning.
Please people, I love blockchains and I have yet to learn about cryptography. I have coded a lot around the NEM blockchain for my personal progress and professional abilities and I really enjoy the Bitcoin scene as a software developer. It is a very entertaining community, we have to say too
Thank you for reading and I hope someone can help me out!
Sources Codes & Screenshots 1)
BIP32+44 HD Addresses Generation Tool on Github 2)
BIP39 Seed Derivation (get Private Keys) on Github 3)
My P2SH Colored Transaction creation on Github 4)
SCREENSHOT OF THE ERROR 5)
OP_RETURN hexadecimal payload reading (All it does it read Integer values, please tell me there is more behind)
With those source codes, passing the right parameters as explained below, I am able to produce the transaction that I will add to a screenshot below. Sadly the Transaction is rejected with the message specified above and also displayed in the screenshot. I don't think the problem lies in the smartbit API I am using, this is only a quick broadcast tool I integrated to my script.
$ php application wallet:p2sh-colored \
--colored-tx "9141346500a45fb588e2ee2908583d9d2b0484b1941dcb0e50fbf9bf1e4e5b51" \
--btc-input-tx "4ef5d6c42ceec8d53a896f1bc6b15fa07c52b9f29db0d8e3ce6a4aa5fb9e0ecb" \
--path="m/44'/0'/0'" \
--min 1 --cosig1="this is not the right mnemonic" \
--cosig2="this one isn't either" \
--cosig3="and of course, nor is this one" \
--destination "1PFSCWbPdfQfwtRidBxj5x2HxigG7JGFNb" \
--change "143f5QPkc5mJurEr2kGPPoecJqkhvaQ2u2" \
--bitcoin 200000
Another *insurances* I get from calling the following command:
$ php application wallet:hd-from-xpub --xpubs="my_cosigner_lists_xpubs,separated_by_comma,very_basic" --path="m/0"
This command just above will return the following
scriptPubKey:
OP_HASH160 d7fb889271798a9244f47320aec28b926a2bb7a4 OP_EQUAL - as well as a
redeemScript containing the lexicographically ordered Public Keys of the cosigners of the copay account. So, as you can see when checking the screenshot from the transaction creation, this
d7fb8... is exactly what I am able to reproduce when I have signed the inputs. Now I am asking myself where the problem could be and hope all those details can help you people more for helping me!
Here is the Transaction bytes data to be copy/pasted into coin.bin for example:
0100000002515b4e1ebff9fb500ecb1d94b184042b9d3d580829eee288b55fa4006534419101000000b4004730440220533476590d6f978491499559c34916a4318c36ff6656f00449ba8bc41c4c1b47022007e8fc60706abcc0d1523f3bc0e5768cc4a32fd3774eb69d8621527ad15d340e014c6951210247bcd2c9e1bf8bfc9e567c8f9c81ef5f2c5356e544ab7b9141b7ef93f272da9f2102771dbfd6b9e6183e893c07ea49cfc781d1836e42b6425ff2a59100cc2e9b11222102ab0456bde3a2a8f5066fa4827c8a00c9388e6da1fdc22f90c406d89112c8bacb53aeffffffffcb0e9efba54a6acee3d8b09df2b9527ca05fb1c61b6f893ad5c8ee2cc4d6f54e00000000b4004730440220787016c5ad971514fd925b7407230430d365242a679e7237d492101a7b1c31d5022055098842dc35764b498ab4633fad4b9da929f00a34a07aa5e8dfd3b9e5b760d8014c6951210247bcd2c9e1bf8bfc9e567c8f9c81ef5f2c5356e544ab7b9141b7ef93f272da9f2102771dbfd6b9e6183e893c07ea49cfc781d1836e42b6425ff2a59100cc2e9b11222102ab0456bde3a2a8f5066fa4827c8a00c9388e6da1fdc22f90c406d89112c8bacb53aeffffffff030000000000000000166a146f6d6e69000000000000001f000000002faf0800c4090000000000001976a914f40da014c137ad88006608834887556944e2dfeb88acf0490200000000001976a9142168fe52d8c1cbc656a544859c4193dd4b92e23788ac00000000
I think my problem lies in the
OP_RETURN output because all I did is copy it from the input transaction and I am unsure If I have to solve the script or whatever. I would appreciate any help from people knowing our OP_RETURN could work in my case of a Copay Multisig (which I seem to be reproducing fine)
For people who didn't click the screenshot, here is the *var_dump()* output of my transaction content after signing, this is similar to what *coin.bin* will display for the transaction bytes data above.
array (
'version' => 1,
'inputs' =>
array (
0 => 'OP_HASH160 d7fb889271798a9244f47320aec28b926a2bb7a4 OP_EQUAL',
1 => 'OP_HASH160 d7fb889271798a9244f47320aec28b926a2bb7a4 OP_EQUAL',
),
'outputs' =>
array (
0 =>
array (
'value' => 0,
'script' => 'OP_RETURN 6f6d6e69000000000000001f000000002faf0800',
),
1 =>
array (
'value' => 2500,
'script' => 'OP_DUP OP_HASH160 f40da014c137ad88006608834887556944e2dfeb OP_EQUALVERIFY OP_CHECKSIG',
),
2 =>
array (
'value' => 150000,
'script' => 'OP_DUP OP_HASH160 2168fe52d8c1cbc656a544859c4193dd4b92e237 OP_EQUALVERIFY OP_CHECKSIG',
),
),
)
And the error when I broadcast this transaction:
array (
'success' => false,
'error' =>
array (
'code' => 'REQ_ERROR',
'message' => '16: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)',
),
)
This API response is from the Smartbit API at:
https://www.smartbit.com.au/api which lets you push transactions quickly with POST requests. This message seems to be a bitcoin scripting error and that's why I am pretty sure I missed something!
Have a nice day.
Greg
[EDIT 1]: Added some phpbb formatting to replace MD.
[EDIT 2]: Changed title to reflect content.
[EDIT 3]: Add Transaction creation Screenshots + Source codes
[EDIT 4]: Added `script:op-return` github source link