Skip to content
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

Closed
wants to merge 8 commits into from

Conversation

achow101
Copy link
Member

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) and CombineSignatures(FinalizePartialTransaction`).

Currently PSBT functionality is limited to 4 new RPC calls, walletupdatepsbt, walletcreatepsbt, combinepsbt, and decodepsbt.

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 to decoderawtransaction.


All of the test vectors currently in BIP 174 are also implemented.

@@ -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)
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to SignPartiallySignedTransaction

@jonasschnelli
Copy link
Contributor

Great work!
General concept ack, though the PR is large and maybe there is a way to make smaller steps towards BIP174 (reduce of risks).

@achow101
Copy link
Member Author

I'm not sure what's causing the travis failure.

@gmaxwell
Copy link
Contributor

Concept ACK!

@dcousens
Copy link
Contributor

Concept ACK

@laanwj
Copy link
Member

laanwj commented Feb 14, 2018

Neat! Concept ACK.

switch (whichType)
{
case TX_PUBKEY:
script_ret.push_back(psbt.inputs[i].partial_sigs.find(CPubKey(vSolutions[0]))->second);
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

}
// Get public keys if hd is enabled
if (pwallet->IsHDEnabled()) {
if (type == TX_PUBKEYHASH) {
Copy link
Member

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) {

Copy link
Member Author

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);
}
}
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Copy link
Member Author

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.

Copy link
Member

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?

@achow101
Copy link
Member Author

Rebased and squashed fixup commits.

@@ -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"
Copy link
Member

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.

Copy link
Member

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

Copy link
Member Author

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.

if (type == TX_PUBKEYHASH || type == TX_WITNESS_V0_KEYHASH) {
uint160 hash(solns[0]);
CKeyID keyID(hash);
if (!pwallet->HaveKey(keyID)) {
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, fixed.

Copy link
Member

@instagibbs instagibbs left a 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

@@ -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"},
Copy link
Member

Choose a reason for hiding this comment

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

should be 1

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

@instagibbs
Copy link
Member

instagibbs commented Mar 6, 2018

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

@achow101
Copy link
Member Author

Rebased on top of #13425 which simplifies the signer logic.

CPubKey pubkey(vSolutions[0]);
keyID = pubkey.GetID();
if (!creator.CreateSig(provider, sig, keyID, scriptPubKey, sigversion)) return false;
sigdata.signatures.emplace(keyID, SigPair(pubkey, sig));
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

CPubKey pubkey;
GetPubKey(&provider, &sigdata, keyID, pubkey);
sigdata.signatures.emplace(keyID, SigPair(pubkey, sig));
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

if (in_globals) {
num_ins = ReadCompactSize(s);
} else {
// Make sure that we are using inout indexes or this is the first input

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

Copy link
Member Author

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
@achow101 achow101 changed the title Implement BIP 174 Partially Signed Bitcoin Transactions Implement BIP 174 Partially Signed Bitcoin Transactions serialization and RPCs Jun 12, 2018
@achow101
Copy link
Member Author

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.
@luke-jr
Copy link
Member

luke-jr commented Jun 16, 2018

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)

@sipa
Copy link
Member

sipa commented Jun 16, 2018

@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.

@luke-jr
Copy link
Member

luke-jr commented Jun 17, 2018

@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. :)

@achow101
Copy link
Member Author

Since BIP 174 is changing soon, I'm closing this for now.

@achow101 achow101 closed this Jun 24, 2018
laanwj added a commit that referenced this pull request Jul 18, 2018
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
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 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.

None yet