Sjors / libwally-swift

Swift wrapper for LibWally, a collection of useful primitives for cryptocurrency wallets
MIT License
40 stars 18 forks source link

Witness input amount type UInt64 is causing a rounding bug #34

Closed Fonta1n3 closed 4 years ago

Fonta1n3 commented 4 years ago

I have a testnet witness input:

"witness_utxo" =                 {
                    amount = "0.009506239999999999";
                    scriptPubKey =                     {
                        address = tb1qtrj390qt7xn3vkl64e4ce6mzkyqtky5njqrumc;
                        asm = "0 58e512bc0bf1a7165bfaae6b8ceb62b100bb1293";
                        hex = 001458e512bc0bf1a7165bfaae6b8ceb62b100bb1293;
                        type = "witness_v0_keyhash";
                    };
                };

In my code I convert the input amount to satoshis with:

let amount = UInt64((witness_utxo["amount"] as! Double) * 100000000)

Which of course converts 0.009506239999999999 to 950624. I then use LibWally-Swift to sign the input with (summarizing the code for brevity):

let witness = Witness(.payToWitnessPubKeyHash(key!.pubKey))
let input = TxInput(Transaction(txid)!, vout, amount, nil, witness, scriptPubKey)!
var transaction = Transaction(inputsToSign, outputsToSend)
let signedTx = transaction.sign(privKeys)

The error I receive when trying to broadcast is:

error =     {
        code = "-26";
        message = "non-mandatory-script-verify-flag (Signature must be zero for failed CHECK(MULTI)SIG operation) (code 64)";
    }

Which apparently is caused by input amounts not matching precisely as per this bitcoin stackexchange question.

Any ideas how I can prevent this? Is this a testnet issue? I was not even aware you can get input amounts sub satoshi like this. Is it possible to supply LibWally with a double instead of UInt64 so the input amount matches?

Fonta1n3 commented 4 years ago

I was able to spend the input by converting the input amount number to a float before converting to a UInt64 like so:

let amount = UInt64(Float((witness_utxo["amount"] as! Double) * 100000000.0))

Sjors commented 4 years ago

I would be worried about the source of data for your input amount "0.009506239999999999". Did you check with e.g. a full node if the amount was really 0.00950624?

Fonta1n3 commented 4 years ago

This output is directly from a full node 0.19.0.1 using bitcoin-cli decodepsbt:

"witness_utxo" =                 {
                    amount = "0.009506239999999999";
                    scriptPubKey =                     {
                        address = tb1qtrj390qt7xn3vkl64e4ce6mzkyqtky5njqrumc;
                        asm = "0 58e512bc0bf1a7165bfaae6b8ceb62b100bb1293";
                        hex = 001458e512bc0bf1a7165bfaae6b8ceb62b100bb1293;
                        type = "witness_v0_keyhash";
                    };
                };

It would be nice to understand why this is happening but it is not a LibWally issue, it is fixed and working fine now after I convert the amount to a float and then a UInt64. Strangely I even tried hard coding the amount to 950623 and 950624 but both resulted in the same error.

Fonta1n3 commented 4 years ago

Closing this as not related to LibWally-Swift

Sjors commented 4 years ago

That could be Bitcoin Core bug. Would you mind opening a Github issue on https://github.com/bitcoin/bitcoin/issues with that PSBT and the output from decodepsbt? Feel free to tag me.

Fonta1n3 commented 4 years ago

Of course! Will do.

Fonta1n3 commented 4 years ago

This seems to be an issue with how json is parsed in swift. When I make the command directly with my node via terminal the amount comes out exactly as it should, when the json dictionary is parsed programmatically via iOS it seems to convert NSNumber in its own way which looks to be as a float?

In short it does not seem to be a Bitcoin Core bug but just something to be careful of when parsing result types from your node over json-rpc.

Sjors commented 4 years ago

Thanks for investigating. That can probably be fixed by customizig the JSON deserialization, making sure if parses as Decimal: https://forums.swift.org/t/parsing-decimal-values-from-json/6906/3