Skip to content

Add P2SH-P2WSH support to signrawtransaction and listunspent RPC#11708

Closed
meshcollider wants to merge 5 commits intobitcoin:masterfrom
meshcollider:201711_signrawtransaction_wsh
Closed

Add P2SH-P2WSH support to signrawtransaction and listunspent RPC#11708
meshcollider wants to merge 5 commits intobitcoin:masterfrom
meshcollider:201711_signrawtransaction_wsh

Conversation

@meshcollider
Copy link
Contributor

@meshcollider meshcollider commented Nov 17, 2017

Currently signrawtransaction works with P2SH-P2WSH which are already in wallet (e.g. addmultisigaddress -> addwitnessaddress). But when using signrawtransaction with keys which aren't in the wallet, there is currently only a redeemScript key so you cannot enter both the P2SH redeemScript and the witness script. There is an undocumented workaround by including the same input twice (suggested on StackExchange here), once with each script, but that is unnecessary and hacky.

This simply allows the optional inclusion of a witnessScript key in the JSON input to signrawtransaction. Because it uses JSON, this is a non-breaking change.

Also, as discussed on IRC (see here), we add a witnessScript output to the listunspent RPC for P2SH-P2WSH addresses because gmaxwell pointed out signrawtransaction should be able to get most of the needed info from listunspent.

Closes #11693

TODO:

  • Needs tests + release notes

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Nov 17, 2017

NACK, redeemScript + scriptPubKey are sufficient information to know whether you are talking about P2SH-P2WSH, P2SH, P2WSH. Having one more "witness script" is just redundant information which can go wrong.

If you enter the witness script in "redeemScript" field, you can easily find out in code if it mean P2SH or Witness by comparing with the scriptPubKey, there is no ambiguity.

@sipa
Copy link
Member

sipa commented Nov 17, 2017

@NicolasDorier How so? The redeemscript only contains a hash of the witness script in the case of P2SH-P2WSH, which is not sufficient to sign with.

It would be possible to require passing only the witness script, and automatically also add the v0 P2WSH redeemscript (and any future versions?). But just the redeemscript is not enough.

@meshcollider
Copy link
Contributor Author

meshcollider commented Nov 17, 2017

Indeed, IMO its much cleaner implementation and user experience to have the witnessScript provided in addition to the redeemScript, not only because that avoids the ambiguous witness version issue in a nice way but also because then it is clear to the user which script the redeemScript actually refers to (because the redeemScript output of other RPCs in general would refer to the wrapper P2SH redeemScript not the witnessScript which would introduce confusion).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Watch out with the indentation,... the \" result in one char. It may look unaligned in the source code but is aligned in the help output.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: replace 32 with sizeof()?

@jonasschnelli
Copy link
Contributor

Concept ACK. Can you add a test?

@meshcollider
Copy link
Contributor Author

@jonasschnelli yep will do, see TODO section :) Just trying to work out the cleanest way to test it wrt all the existing tests in segwit.py

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the whash.size() should also go here... :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh missed that, thanks :P

@meshcollider
Copy link
Contributor Author

meshcollider commented Nov 18, 2017

Test and release notes added, @jonasschnelli's nits addresses

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Nov 20, 2017

This is how I am doing with NBitcoin for long.

@sipa if you have redeemscript and scriptPubKey you can know exactly wether redeemScript is a Segwit or P2SH redeem.

If scriptPubKey is P2WSH, then you know redeemScript is a segwit redeem.

If scriptPubKey is P2SH, you know redeemScript is either a P2SH redeem or a Segwit Redeem.

If Hash(redeemScript) is contained inside scriptPubKey, then it is a P2SH redeem else it is a segwit redeem. If it is a P2SH redeem, and inside is a segwit program, you must return error to the user.

You can then Hash(Hash(redeemScript)) and verify that it is indeed into ScriptPubKey to be sure the user did not messed up.

You can derive the P2SH redeem from the Segwit redeem, so no need to specify both.

Again, strong NACK on this one. This is not needed.

@sipa
Copy link
Member

sipa commented Nov 20, 2017

@NicolasDorier Ah, I'm using redeemscript exclusively for P2SH.

Yes, if you have the witness script, and can correctly guess it is a V0 P2WSH embedded in P2SH, you can derive the redeemscript.

I'm not convinced guessing is the best approach.

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Nov 20, 2017

Well the thing is that this field is called redeemScript which does not suppose it is P2SH or Segwit redeem, and it does not have to.

If you insist having both redeem, then I would suggest to rename redeemScript as p2shRedeem.

But then, the logic of this RPC method will need to ensure that all, witnessScript, p2shRedeem and scriptPubKey are coherent.

I am convinced specifying both is just wrong as there is no ambiguity, no way to guess incorrectly. Just more way for the user to get wrong and confused, and more code to ensure everything is coherent on both side.

@sipa
Copy link
Member

sipa commented Nov 20, 2017

@NicolasDorier That's the fair point, the naming would be inconsistent in that case.

We also don't actually need to distinguish for our implementation. @meshcollider perhaps we can just permit redeemScript to be a list, in which case multiple scripts gets loaded with AddCScript?

@meshcollider
Copy link
Contributor Author

meshcollider commented Nov 20, 2017

Yep I'm happy with that approach too, I just definitely want to avoid guessing the witness version as mentioned. Although re: terminology, I've only ever seen redeemScript used to refer exclusively to P2SH, even the bitcoincore.org segwit wallet dev guide refers to the witness redeem script as witnessScript only.

@NicolasDorier
Copy link
Contributor

NicolasDorier commented Nov 21, 2017

The fact that "redeemScript" can refer to all type of PS2SH*-P2WSH* redeems makes migration from P2SH to Segwit easier, as there is no additional fields to pass around that did not existed before.

In NBitcoin, there is a type called ScriptCoin which was used to represent a P2SH + Redeem. When I updated the library for Segwit, I only needed to instruct my users that if they want to migrate to P2SH-P2WSH they just need a different way of calculating the ScriptPubKey when generating a new address. The majority of their code did not changed. (No additional field, old non-segwit UTXO were still working fine, no additional type to know about, no if/else logic for handling both way in their code)

That said I would be fine with redeemScript being an array as well.

@meshcollider
Copy link
Contributor Author

I've made the modification to include the witnessScript in a redeem script array in the case of P2SH-P2WSH, will squash those modification commits if that approach is acceptable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are not using that anymore

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are not using that anymore

@meshcollider
Copy link
Contributor Author

Fixed, thanks @NicolasDorier
Squashed fixups too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: check if the redeemScript[0] is the p2sh redeem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@NicolasDorier
Copy link
Contributor

Code review ACK

@meshcollider
Copy link
Contributor Author

Rebased and added the extra check as suggested by @NicolasDorier. That also fixed the unused OP_0 warning.

@NicolasDorier
Copy link
Contributor

reACK

@ryanofsky
Copy link
Contributor

Should passing a redeem script array to signrawtransaction be possible with bitcoin-cli? It looks this might not work currently because the param does not have an entry in vRPCConvertParams. You might be able to work around this with something like:

if (redeem_script.isStr() && !IsHex(redeem_script.get_str())) {
    redeem_script.read(redeem_script.get_str().substr());
    RPCTypeCheckArgument(redeem_script, UniValue::VARR);
}

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK a1dec739839053ce095f84ea1d281744124896e6

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but seems like it might good to return an error if this condition is false instead of skipping validation of the redeemscript parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe drop this line. It prevents get_str() below from throwing an exception if the wrong type is passed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to explicitly say this will be an array for p2wsh and nested p2wsh outputs, a string for traditional p2sh outputs, and unset in other cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this should never be false? Would be helpful to have a comment saying this, or else mentioning when it would be expected.

@ryanofsky
Copy link
Contributor

This PR has two code review acks (from me and NicolasDorier) so maybe it is close to being merged. The remaining comments that haven't been responded to are minor and shouldn't hold this up.

@meshcollider
Copy link
Contributor Author

Addressed @ryanofsky feedback, thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error when not string or array? Suggestion (scripts is a vector of strings):

if (rs.getType() == UniValue::VARR) {
    for (auto r : rs.getValues()) {
         scripts.push_back(r.get_str());
    }
} else {
    scripts.push_back(rs.get_str());
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea, done, thanks :)

@meshcollider
Copy link
Contributor Author

Feedback addressed, fixup commits squashed into one cleanup commit

Copy link
Contributor Author

@meshcollider meshcollider Dec 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This redeemScript parameter was being silently ignored until now, because scripts are not used by signrawtransaction if no keys are passed in (the internal wallet is used instead). New code returns an error instead of silently ignoring it, so it was removed.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 50074643f20cc36e6ffbbec22f1c850c6e9a3207. Only change since last review is the new cleanup commit. Feel free to ignore very minor review suggestions below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use const reference to avoid string copy here (though there are more copies below anyway).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use const auto& to avoid univalue copies

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe s/should not accept/don't currently support accepting/. This seems more like a reasonable limitation of the current implementation than a desirable feature of it.

@meshcollider
Copy link
Contributor Author

meshcollider commented Dec 24, 2017

Fixed @ryanofsky's comments, thanks :) Haven't tested CLI yet though

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 22397a43a6601e76582053dc95381196f520a610. Sorry for the delay, only changes since last review were the suggested comment & for loop tweaks.

@meshcollider
Copy link
Contributor Author

meshcollider commented Jan 17, 2018

Rebased to fix merge conflict in rawtransactions.py

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 73b582dc6624ffc27dc65cf7f60e6ff8829b6691. No changes since last review except for rebase and simple conflict resolution in rawtransactions.py.

@sipa
Copy link
Member

sipa commented Feb 12, 2018

Concept ACK. Rebase/squash last commit?

@meshcollider
Copy link
Contributor Author

@sipa done :)

laanwj added a commit that referenced this pull request Feb 15, 2018
5f605e1 Make signrawtransaction accept P2SH-P2WSH redeemscripts (Pieter Wuille)

Pull request description:

  This is a quick fix for #12418, which is a regression in 0.16.

  It permits specifying just the inner redeemscript to let `signrawtransaction` succeed. This inner redeemscript is already reported by `addmultisigaddress` & co.

  #11708 uses a different approach, where `listunspent` reports both inner & outer redeemscript, but requires both to be provided to `signrawtransaction`. Part of #11708 is still needed even in combination with this PR however, as currently the inner redeemscript isn't reported by `listunspent`.

Tree-SHA512: a6fa2b2661ce04db25cf029dd31da39c0b4811d43692f816dfe0f77b4159b5e2952051664356a579f690ccd58a626e0975708afcd7ad5919366c490944e3a9a5
if (pwallet->GetCScript(id, witnessScript)) {
scripts.push_back(HexStr(witnessScript.begin(), witnessScript.end()));
}
entry.push_back(Pair("redeemScript", scripts));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, Pair push_backs were just cleaned up... :/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this needs a large rebase and cleanup anyway now signrawtransaction was split up, things like this will be done at the same time when I get onto this


- The `listunspent` output will now return an array of scripts as the `redeemScript` in the case of P2SH-P2WSH,
for compatibility with the `signrawtransaction` change above. In other cases, `listunspent` will continue to return
the only `redeemScript` as a string, as before.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to break the API here? Why not add a new key for the witnessScript?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API only breaks in the case of P2SH-P2WSH which was not supported at all before anyway. Adding a new key for witnessScript to listunspent would make its output incompatible with the signrawtransaction input which expects a list of scripts in this case

@maflcko
Copy link
Member

maflcko commented Aug 25, 2018

Needs rebase

UniValue rs = find_value(prevOut, "redeemScript");
if (rs.getType() != UniValue::VNULL) {
if (!(scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
// if this isnt P2SH or P2WSH, scripts should not be provided
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo found by codespell: “isnt” should be “isn’t” or “is not” :-)

@meshcollider
Copy link
Contributor Author

Closing this, replaced by #14481

@Rspigler
Copy link
Contributor

Rspigler commented Nov 6, 2018

@fanquake This should be removed from 'In Progress' in 'Segwit' and #14481 perhaps added?

laanwj added a commit that referenced this pull request Feb 14, 2019
6ca836a Add release note for listunspent P2WSH change (MeshCollider)
928beae Add test for P2SH-P2WSH in signrawtransactionwithkey and listunspent (MeshCollider)
314784a Make listunspent and signrawtransaction RPCs support witnessScript (MeshCollider)

Pull request description:

  This is a reworked version of #11708 after #12427 and the `signrawtransaction` split.

  For a P2WSH address, listunspent should return the witness script, and for a P2SH-P2WSH address, it should also return the inner witness script (because SignTransaction will automatically wrap it in P2SH if required).

  Includes a test which also tests the behaviour of #12427, and release note.

Tree-SHA512: a8e72cf16930312bf48ec47e44a68f8d7e26664043c1b4cc0983eb25aec4087e511188ff9a0f181cd7b8a0c068c60d7f1e7e3f226b79e8c48890039dcf57f7b7
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Dec 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signing raw transaction that has p2sh-p2wsh input