Skip to content

External signer support - Wallet Box edition#16546

Merged
laanwj merged 15 commits into
bitcoin:masterfrom
Sjors:2019/08/hww-box2
Feb 23, 2021
Merged

External signer support - Wallet Box edition#16546
laanwj merged 15 commits into
bitcoin:masterfrom
Sjors:2019/08/hww-box2

Conversation

@Sjors

@Sjors Sjors commented Aug 4, 2019

Copy link
Copy Markdown
Member

Big picture overview in this gist.

This PR lets bitcoind call an arbitrary command -signer=<cmd>, e.g. a hardware wallet driver, where it can fetch public keys, ask to display an address, and sign a transaction (using PSBT under the hood).

It's design to work with https://github.com/bitcoin-core/HWI, which supports multiple hardware wallets. Any command with the same arguments and return values will work. It simplifies the manual procedure described here.

Usage is documented in doc/external-signer.md, which also describes what protocol a different signer binary should conform to.

Use --enable-external-signer to opt in, requires Boost::Process:

Options used to compile and link:
  with wallet     = yes
  with gui / qt   = no
  external signer = yes

It adds the following RPC methods:

  • enumeratesigners: asks for a list of signers (e.g. devices) and their master key fingerprint
  • signerdisplayaddress <address>: asks to display an address

It enhances the following RPC methods:

  • createwallet: takes an additional external_signer argument and fetches keys from device
  • send: automatically sends transaction to device and waits

Usage TL&DR:

  • clone HWI repo somewhere and launch bitcoind -signer=../HWI/hwi.py
  • check if you can see your hardware device: bitcoin-cli enumeratesigners
  • create wallet and auto import keys bitcoin-cli createwallet "hww" true true "" true true true
  • display address on device: bitcoin-cli signerdisplayaddress ...
  • to spend, use send RPC and approve transaction on device

Prerequisites:

Potentially useful followups:

@DrahtBot

DrahtBot commented Aug 4, 2019

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Comment thread src/wallet/rpcwallet.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This list-of-bools thing seems like a terrible idea...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Indeed, I was complaining about that when blank was added :-)

Comment thread src/wallet/rpcwallet.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What if there are multiple wallets, with different signers?

IMO -signer needs to be replaced with either a wallet-stored path, or a path provided when the wallet is loaded...

@Sjors Sjors Aug 5, 2019

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Storing signer in the wallet makes sense, but I also think it can wait for a later PR. In practice afaik the only tool that currently works is HWI and it can handle multiple wallets. In the long run however I do hope that wallet manufactures provide their own software that just uses the same commands / responses.

@Relaxo143

Copy link
Copy Markdown

Can we expect this to be merged and included in 0.19? It's a really useful and requested feature! Thanks to everyone who is working on it.

@Sjors

Sjors commented Sep 16, 2019

Copy link
Copy Markdown
Member Author

@Relaxo143 not a chance; this is still work in progress and there's several pull requests that need to be reviewed and merged first. There's also a feature freeze on 0.19.

@Sjors

Sjors commented Sep 16, 2019

Copy link
Copy Markdown
Member Author

I dropped the dependency on my new send RPC proposal #16378, in favor of just tweaking sendmany and sendtoaddress. It's less powerful, but should reduce the review burden once native descriptors are merged.

@Sjors Sjors mentioned this pull request Sep 17, 2019
2 tasks
@achow101

Copy link
Copy Markdown
Member

re-ACK 96f991a11a871502d1a39f684994c1e2664c3af7

@fanquake fanquake left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In 903f277f982ed390dc68114c9c1eb70b4a59bb19 is where we should be able to drop HAVE_BOOST_PROCESS entirely. I realise that boost process could potentially be used for other things in future, but currently, it's not, and as this is implemented, there's a bit of an awkward split between HAVE_BOOST_PROCESS & ENABLE_EXTERNAL_SIGNER for code which, as I understand it, is all essentially the same feature. So I think for now we should just replace the few usages of HAVE_BOOST_PROCESS with ENABLE_EXTERNAL_SIGNER.

Comment thread src/wallet/external_signer_scriptpubkeyman.cpp Outdated
Comment thread src/wallet/wallet.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const std::string command = gArgs.GetArg("-signer", ""); // DEFAULT_EXTERNAL_SIGNER);
const std::string command = gArgs.GetArg("-signer", DEFAULT_EXTERNAL_SIGNER);

Or if we are going to use "", we should remove the commented code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not sure what was going on there, but I put DEFAULT_EXTERNAL_SIGNER back.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now I remember, I didn't want a circular inclusion. I just switched to "".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

From what I can see, GetExternalSigner() is only called from code that is inside ENABLE_EXTERNAL_SIGNER #idfefs. So why can't we wrap this whole function in an #ifdef, and drop the need for the throw()? I think the same goes for most of this file, and some other functions in this PR. It seems weird that when external signing is not enabled, we'd still be compiling functions that aren't called from anywhere, and who's purpose is to throw a runtime error to tell you about external signing and it's need of Boost Process.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If turns out I can put the entire ExternalSignerScriptPubKeyMan inside #ifdef. The most important throw()s are already in wallet.cpp.

This option replaces --with-boost-process

This prepares external signer support to be disabled by default.
It adds a configure option to enable this feature and to check
if Boost::Process is present.

This also exposes ENABLE_EXTERNAL_SIGNER to the test suite via test/config.ini
@Sjors

Sjors commented Feb 21, 2021

Copy link
Copy Markdown
Member Author

Rebased onto CI fix. @fanquake I replaced the remaining instances of HAVE_BOOST_PROCESS with ENABLE_EXTERNAL_SIGNER. In the process I squashed the MSVC and doc commit, since they're now just a simple rename.

@laanwj

laanwj commented Feb 22, 2021

Copy link
Copy Markdown
Member

Please squash the move-only: add underscore to externalsigner.h commit as well. It's introduced in this PR, so let's introduce it with the eventual name directly.

@Sjors

Sjors commented Feb 22, 2021

Copy link
Copy Markdown
Member Author

That may be a rather big rebase hell though, because the commits make incremental changes to these files so I'd have to divide this commit in 10 pieces, squash them in the right place and then deal with rebase conflicts. Unless there's an intelligent Git incantation to rename a file across all commits?

@ryanofsky

Copy link
Copy Markdown
Contributor

Unless there's an intelligent Git incantation to rename a file across all commits?

Something like this will rename the one file. You can extend it to rename other files or do more replacements across commits:

end=f1824e7af7a8d53f9be50a6c6354511190dcb774
start=b9a262ea0a86f498959e81113522ce26299f1984

git checkout $end
git filter-branch --tree-filter '
  git mv src/wallet/externalsigner.h src/wallet/external_signer.h
  git grep -l externalsigner | xargs sed -i s:wallet/externalsigner.h:wallet/external_signer.h:
' --force $start^..HEAD

@laanwj

laanwj commented Feb 23, 2021

Copy link
Copy Markdown
Member

Also git tends to be smart enough to take into account renamed files when rebasing. If you go to the first commit with git rebase -i, amend it for editing, git mv src/wallet/externalsigner.h src/wallet/external_signer.h, then git rebase --continue, that should not cause any merge conflicts.

@Sjors

Sjors commented Feb 23, 2021

Copy link
Copy Markdown
Member Author

@ryanofsky's thanks! That worked (with some tweaks), and despite warnings from the git, it still compiles and history looks sane. @laanwj I have indeed seen Git behave sanely when it comes to renames, so maybe my worries were not necessary.

@laanwj

laanwj commented Feb 23, 2021

Copy link
Copy Markdown
Member

re-ACK f75e0c1

Comment thread doc/external-signer.md
Create a wallet, this automatically imports the public keys:

```sh
$ bitcoin-cli createwallet "hww" true true "" true true true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Would be nice to use named args

Comment thread src/wallet/rpcsigner.cpp
},
RPCExamples{""},
[](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why does this rpc require a wallet?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I might get rid of that requirement in a followup.

@hebasto

hebasto commented Mar 2, 2021

Copy link
Copy Markdown
Member

There is a buggy execution path in the configure script. Fixed in #21339.

Comment thread test/config.ini.in
@BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true
@ENABLE_FUZZ_TRUE@ENABLE_FUZZ=true
@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true
@ENABLE_EXTERNAL_SIGNER_TRUE@ENABLE_EXTERNAL_SIGNER=true

@hebasto hebasto Jun 11, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems the @ENABLE_EXTERNAL_SIGNER_TRUE@ substitution does not work for some reasons.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nm, forgot to run ./autogen.sh

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.