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
Implement BIP 174 Partially Signed Bitcoin Transactions serialization and RPCs #12136
Conversation
src/script/sign.cpp
Outdated
@@ -302,6 +381,218 @@ struct Stacks | |||
}; | |||
} | |||
|
|||
// Iterates through all inputs of the partially signed transaction and just produces signatures for what it can and adds them to the psbt partial sigs | |||
bool SignPartialTransaction(PartiallySignedTransaction& psbt, const CKeyStore* keystore, int nHashType) |
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.
SignPartialTransaction
sounds after we have partial transactions. Suggest SignPartialSignedTransaction
.
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.
Changed to SignPartiallySignedTransaction
Great work! |
I'm not sure what's causing the travis failure. |
Concept ACK! |
Concept ACK |
12a88c4
to
55a7223
Compare
Neat! Concept ACK. |
src/script/sign.cpp
Outdated
switch (whichType) | ||
{ | ||
case TX_PUBKEY: | ||
script_ret.push_back(psbt.inputs[i].partial_sigs.find(CPubKey(vSolutions[0]))->second); |
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.
this may segfault when there is no corresponding partial_sig
, I believe this is the cause of your test failure.
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.
Fixed
src/wallet/rpcwallet.cpp
Outdated
} | ||
// Get public keys if hd is enabled | ||
if (pwallet->IsHDEnabled()) { | ||
if (type == TX_PUBKEYHASH) { |
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.
should be:
if (type == TX_PUBKEYHASH || type == TX_WITNESS_V0_KEYHASH) {
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.
Done.
// Add to inputs | ||
psbtx.inputs.push_back(input); | ||
} | ||
} |
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.
this isn't mentioned in the spec anywhere, but passing along the the change output's pubkey(s)/hdpath(s)/redeemscript allows hww to understand change outputs.
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.
Done
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 decided to make this an option instead of the default. I'll add tests for it later.
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.
Should that be mentioned in the spec, then?
Rebased and squashed fixup commits. |
e5009df
to
0582662
Compare
src/wallet/rpcwallet.cpp
Outdated
@@ -4082,6 +4163,8 @@ UniValue walletcreatepsbt(const JSONRPCRequest& request) | |||
"using fundrawtransaction.\n" | |||
"\nArguments:\n" | |||
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n" | |||
"2. \"include_output_info\" (boolean, optional, default=false) If true, returns the PSBT with the redeem scripts, witness\n" |
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.
you need to edit number of allowed args, and include the optional one in parens above.
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.
you also need to add it to the list of arguments that will be parsed as json rather than strings in rpc/client.cpp
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.
Dang, I knew I forgot something. Fixed.
src/wallet/rpcwallet.cpp
Outdated
if (type == TX_PUBKEYHASH || type == TX_WITNESS_V0_KEYHASH) { | ||
uint160 hash(solns[0]); | ||
CKeyID keyID(hash); | ||
if (!pwallet->HaveKey(keyID)) { |
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.
What we really care about is that we have the hdkeypath, not particularly that the key is present or not. I don't think these HaveKey
checks are necessary since add_keypath_to_map
will deal with that case specifically.
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.
Right, fixed.
4881490
to
f259cd5
Compare
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.
"ERROR: walletcreatepsbt argument 2 (named include_output_info in vRPCConvertParams) is not defined in dispatch table
ERROR: walletupdatepsbt argument 3 (named include_output_info in vRPCConvertParams) is not defined in dispatch table"
You need to add the new named args to the dispatch table
src/rpc/client.cpp
Outdated
@@ -102,6 +102,8 @@ static const CRPCConvertParam vRPCConvertParams[] = | |||
{ "fundrawtransaction", 1, "options" }, | |||
{ "fundrawtransaction", 2, "iswitness" }, | |||
{ "walletupdatepsbt", 2, "psbtformat"}, | |||
{ "walletupdatepsbt", 3, "include_output_info"}, | |||
{ "walletcreatepsbt", 2, "include_output_info"}, |
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.
should be 1
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.
fixed
light tACK I have rebased the externalhd branch onto this PR, with minor modifications, for ledger support, combined with @achow101 's HWI repo for signing. https://github.com/instagibbs/bitcoin/tree/external_psbt |
Rebased on top of #13425 which simplifies the signer logic. |
src/script/sign.cpp
Outdated
CPubKey pubkey(vSolutions[0]); | ||
keyID = pubkey.GetID(); | ||
if (!creator.CreateSig(provider, sig, keyID, scriptPubKey, sigversion)) return false; | ||
sigdata.signatures.emplace(keyID, SigPair(pubkey, sig)); |
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.
Why is this needed? Signing a TX_PUBKEY should always result in a full signature.
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.
A PSBT does not have to create the final scriptSig even when a complete set of signatures is available. The user has the option to not finalize the scriptSigs even when they can be.
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.
In that case you should also be able to deal with unfinalized signatures already present, and turn them into scriptSigs. In other words, you need to test whether sigdata.signatures contains a matching signature and reuse it. If that's the case, we're probably better off reintroducing SignatureDataSignatureCreator and SignatureDataSigningProvider, so these keys/script/signatures are automatically available everywhere.
Another interpretation is that TX_PUBKEY and TX_PUBKEYHASH etc just don't have "partial" forms - they're either fully signed or not, and as a result there never is anything to finalize.
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.
We should discuss this on the mailing list as it requires changing the BIP.
For now, I have added a helper function for createSig which fetches and places signatures to and from the SignatureData.
src/script/sign.cpp
Outdated
CPubKey pubkey; | ||
GetPubKey(&provider, &sigdata, keyID, pubkey); | ||
sigdata.signatures.emplace(keyID, SigPair(pubkey, sig)); |
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 here.
src/script/sign.h
Outdated
if (in_globals) { | ||
num_ins = ReadCompactSize(s); | ||
} else { | ||
// Make sure that we are using inout indexes or this is the first input |
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.
small typo: Make sure that we are using input indexes or this is the first input
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.
Fixed
Sign1 and SignN are kind of redundant so remove them and inline their behavior into SignStep
Updated the title to indicate that this is primarily serializations and RPCs. |
In addition to having the scriptSig and scriptWitness, have SignatureData also be able to store just the signatures (pubkeys mapped to sigs) and scripts (script ids mapped to scripts). Also have DataFromTransaction be able to extract signatures and scripts from the scriptSig and scriptWitness of an input to put them in SignatureData. Adds a new SignatureChecker which takes a SignatureData and puts pubkeys and signatures into it when it successfully verifies a signature. Adds a new field in SignatureData which stores whether the SignatureData was complete. This allows us to also update the scriptSig and scriptWitness to the final one when updating a SignatureData with another one.
Instead of using CombineSignatures to create the final scriptSig or scriptWitness of an input, use ProduceSignature itself. To allow for ProduceSignature to place signatures, pubkeys, and scripts that it does not know about, we pass down the SignatureData to SignStep which pulls out the information that it needs from the SignatureData.
Removes CombineSignatures and replaces its use in tests with ProduceSignature to test the same behavior for ProduceSignature.
walletupdatepsbt takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, and signs the transaction walletcreatepsbt takes a raw network serialized transaction like what createrawtransaction and fundrawtransaction output and converts it to a PSBT using whatever information about the inputs it knows from the wallet. decodepsbt takes a PSBT and decodes it to JSON combinepsbt takes multiple PSBTs for the same tx and combines them.
If the only reason not to use protobufs is "another dependency", that's a bad reason to avoid it. Dependencies are better than reinventing the same thing... no reason to avoid them. (not to mention that protobuf is already a dependency) |
@luke-jr They also have a non-unique encoding, and are too complicated for what we need. Protobuf is also not a dependency already of all (or even most) software (including hardware devices) we expect to implement this, while all of those already need to implement Bitcoin P2P-like serialization, which this extends. The discussion for that probably belongs on the ML. |
@sipa My goal was not to argue for a change in BIP 174, only to get a better explanation/rationale for why protobufs aren't used. :) |
Since BIP 174 is changing soon, I'm closing this for now. |
020628e Tests for PSBT (Andrew Chow) a4b06fb Create wallet RPCs for PSBT (Andrew Chow) c27fe41 Create utility RPCs for PSBT (Andrew Chow) 8b5ef27 SignPSBTInput wrapper function (Andrew Chow) 58a8e28 Refactor transaction creation and transaction funding logic (Andrew Chow) e9d86a4 Methods for interacting with PSBT structs (Andrew Chow) 12bcc64 Add pubkeys and whether input was witness to SignatureData (Andrew Chow) 41c607f Implement PSBT Structures and un/serialization methods per BIP 174 (Andrew Chow) Pull request description: This Pull Request fully implements the [updated](bitcoin/bips#694) BIP 174 specification. It is based upon #13425 which implements the majority of the signing logic. BIP 174 specifies a binary transaction format which contains the information necessary for a signer to produce signatures for the transaction and holds the signatures for an input while the input does not have a complete set of signatures. This PR contains structs for PSBT, serialization, and deserialzation code. Some changes to `SignatureData` have been made to support detection of UTXO type and storing public keys. *** Many RPCs have been added to handle PSBTs. `walletprocesspsbt` takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, signs, and finalizes the transaction. There is also an option to not sign and just update. `walletcreatefundedpsbt` creates a PSBT from user provided data in the same form as createrawtransaction. It also funds the transaction and takes an options argument in the same form as `fundrawtransaction`. The resulting PSBT is blank with no input or output data filled in. It is analogous to a combination of `createrawtransaction` and `fundrawtransaction` `decodepsbt` takes a PSBT and decodes it to JSON. It is analogous to `decoderawtransaction` `combinepsbt` takes multiple PSBTs for the same tx and combines them. It is analogous to `combinerawtransaction` `finalizepsbt` takes a PSBT and finalizes the inputs. If all inputs are final, it extracts the network serialized transaction and returns that instead of a PSBT unless instructed otherwise. `createpsbt` is like `createrawtransaction` but for PSBTs instead of raw transactions. `convertpsbt` takes a network serialized transaction and converts it into a psbt. The resulting psbt will lose all signature data and an explicit flag must be set to allow transactions with signature data to be converted. *** This supersedes #12136 Tree-SHA512: 1ac7a79e5bc669933f0a6fcc93ded55263fdde9e8c144a30266b13ef9f62aacf43edd4cbca1ffbe003090b067e9643c9298c79be69d7c1b10231b32acafb6338
BIP 174 specifies a binary transaction format which contains the information necessary for a signer to produce signatures for the transaction and holds the signatures for an input while the input does not have a complete set of signatures.
BIP 174 is fully implemented in this pull request. It contains a struct for the a PSBT, serialization and deserialization functions, and a PSBT specific versions of
ProduceSignature
(SignPartialTransaction
),SignStep
(SignSigsOnly
) andCombineSignatures
(FinalizePartialTransaction`).Currently PSBT functionality is limited to 4 new RPC calls,
walletupdatepsbt
,walletcreatepsbt
,combinepsbt
, anddecodepsbt
.walletupdatepsbt
updates a given PSBT with data from the wallet. For each input, it will attempt to add the proper UTXO, sign for the input, and finalize the input. It will also add any known redeem scripts, witness scripts, and public key derivation paths if they are known to the wallet.walletcreatepsbt
takes a network serialized raw transaction as produced by the*rawtransaction
commands and converts it to a PSBT. Inputs are filled using information known to the wallet.combinepsbt
combines multiple PSBTs and finalizes them if possible.decodepsbt
decodes a PSBT and into a human readable format in order to more easily examine them. It is analogous todecoderawtransaction
.All of the test vectors currently in BIP 174 are also implemented.