Manage watch only wallets with bcoin
- Node.js
Signer
Class with Ledger and Trezor support. - CLI tooling for end to end work with
bcoin
. - Pull extended public keys, create watch only wallets/accounts.
- Sign transactions, broadcast to the network.
- Manage multisignature wallets.
bsigner
helps to manage hardware signing using bcoin
.
A class to manage multiple devices/vendors and do signing.
const {Signer, Path} = require('bsigner');
(async () => {
// create bip44 xpub path
const path = Path.fromList([44,0,0], true);
const manager = Signer.fromOptions({
vendor: 'ledger', // enabled vendors, supports ledger and trezor
network: 'regtest' // main, testnet, regtest, or simnet
});
await manager.open();
// select device with vendor.
await manager.selectDevice('ledger');
const hdpubkey = await manager.getPublicKey(path);
})().catch(e => {
console.log(e.stack);
process.exit(1);
});
The Signer
class is an eventemitter
and emits on 4 topics.
connect
, disconnect
, select
, deselect
, a device
object is passed along.
Use in conjunction with bcoin to sign transactions using the hardware wallet device.
const {WalletClient} = require('bclient');
const {Newtork} = require('bcoin');
const {Path, prepareSign, Signer} = require('bsigner');
const network = Network.get('regtest');
const client = new WalletClient({
port: network.walletPort,
network: network.type
});
const wallet = client.wallet('mywallet');
const manager = Signer.fromOptions({
vendor: 'ledger',
network: 'regtest'
});
const wallet = client.wallet('primary');
const path = Path.fromList([44,0,0], true);
const tx = await wallet.createTX({
account: 'default',
rate: 1e3,
outputs: [{ value: 1e4, address: 'REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR' }],
sign: false
});
const {mtx, inputData} = await prepareSign({
tx: tx,
wallet: walletClient.wallet(walletId),
path: path.clone(),
network: network
});
const signed = await manager.signTransaction(mtx, inputData);
console.log(signed.verify());
// true
Also use in conjunction with bmultisig to manage signing multisignature transactions
const {WalletClient} = require('bclient');
const {Newtork} = require('bcoin');
const {Path, prepareSignMultisig, Signer} = require('bsigner');
const network = Network.get('regtest');
const client = new WalletClient({
port: network.walletPort,
network: network.type
});
const wallet = client.wallet('primary');
const proposalId = 0;
const path = Path.fromList([44,0,0], true);
const {mtx, inputData} = prepareSignMultisig({
proposalId,
path: path.clone(),
wallet: wallet,
network: network
});
const signatures = await manager.getSignature(mtx, inputData);
const approval = await wallet.approveProposal(proposalId, signatures);
A class to manage bip44
wallets. This class can be used in conjunction with the Hardware
class
to make deriving keys on the device more simple.
- Create abstractions over hardened indices (no more manual bitwise or)
- Represent as string or list of uint256
- Throw errors in "strict" mode, when path depth exceeds 5
- Infer path from extended public key
Commonly seen notation for a hardened index includes 0'
or 0h
. Under the hood,
the hardened index is not 0
, its representations are shown below:
0 | 0x80000000
1 << 31
2147483648
0b10000000000000000000000000000000
Different paths correspond to different coins. See slip44 to learn the mapping between coin types and coins.
Create a Path
that represents the path to the
keypair that locks a particular utxo
const {Path} = require('bsigner');
// create a Path instance for bitcoin mainnet
const path = Path.fromList([44,0,0], true);
console.log(path.toString());
// 'm\'/44\'/0\'/0\''
console.log(path.toList());
// [ 2147483692, 2147483648, 2147483648 ]
// clone path to reuse the same
// account depth path for another tx
// from same account
let myTXPath = path.clone();
// fetch branch and index from someplace
const branch = 0;
const index = 0;
myTXPath = myTXPath.push(branch).push(index);
console.log(myTXPath.toString());
// 'm\'/44\'/0\'/0\'/0/0'
console.log(myTXPath.toList());
// [ 2147483692, 2147483648, 2147483648, 0, 0 ]
Quickly pull bip 32 extended public keys from your hardware devices
Note that [email protected] uses the extended key prefix rpub
for regtest
extended account public keys (m/44'/1'/{i}'
), the 2.x.x release
will use tpub
and be compatible with bitcoind.
$ ./bin/pubkeys.js -v ledger -n regtest -i 0h
{
"network": "regtest",
"vendor": "ledger",
"path": "m'/44'/1'/0'",
"xkey": "rpubKBA5VcKMuu9dL73dKJSAMNx8FkPAURAsJUr2mTTJWgAGQxuwB9Nj3VHCzwEtqUQhUWnEJSHFzGGxrzTgdYQL46fekWEbFeSsruRQn3wDujaC",
"publicKey": "028b42cd4776376c82791b494155151f56c2d7b471e0c7a526a7ce60dd872e3867",
"receive": {
"legacy": [
"REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR",
"RUaqJ3PnCZPFRcdV4PBYvgmbLjWhqrBKX3",
"RGDzkAB4UkUnATjpP2d9AKBxyFGMimaC1r"
],
"segwit": [
"rb1q8gk5z3dy7zv9ywe7synlrk58elz4hrnearrkd8",
"rb1q60qgwr5wzw57cvs0z2qg3yf84g26fs9pjcjacf",
"rb1qfshzhu5qdyz94r4kylyrnlerq6mnhw3s9y9e85"
]
},
"change": {
"legacy": [
"RBu2VTMz4MnCziyo9tccAkjjFVWkEeMwV1",
"RTuXStuGuqQURBGiMGq5zN75W1fg3exBTc",
"RGXwdbMLoaVaXFzLifkZLJFND14KcvdyVA"
],
"segwit": [
"rb1qrjmnjg944su0gc8r3h4ksjuuffqglu7q3yrsyp",
"rb1qe3gksfnc76gujkqsxqxh79t9nqe4ervwhgqu2h",
"rb1qf7fs76w04gdtwrg6cktw52agqzvdttvzv8k2g5"
]
}
}
Keep private keys on a hardware security module instead of any old machine. Sign transactions that the bcoin wallet assmebles for watch only wallets. Then broadcast them to the network.
Use the --help
flag to see in depth details.
-i
- bip44 account index, must specify hardened with eitherh
or'
-n
- bitcoin network, one of main, testnet, regtest, simnet-v
- signing vendor:ledger
ortrezor
Lets start by verifying. Grab the first receive address of the first account.
This address corresponds to m/44'/1'/0'/0/0
. Using jq
, it is possible to
index into the returned list of addresses and they are indexed in ascending order.
$ receive=$(./bin/pubkeys.js -v ledger -i 0h -n regtest | jq -r .receive.legacy[0])
Now we can create a wallet using the extended public key 44h/0h/0h
.
$ ./bin/pubkeys.js -v ledger -i 0h -n regtest -w foo --create-wallet
Now lets compare the receive address that bcoin created against the one that we derived locally
$ bwallet-cli --id foo account get default | jq -r .receiveAddress
REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR
Sanity check to make sure they match
$ echo $receive
REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR
If you don't already have a ton of BTC at that address, mine some real quick
$ bcoin-cli rpc generatetoaddress 300 REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR
Now create a transaction and sign it. It will broadcast it to the network automatically.
$ ./bin/sign.js -v ledger -w foo -n regtest --value 10000 --recipient REaoV1gcgqDSQCkdZpjFZptGnutGEat4DR
{
"vendor": "ledger",
"network": "regtest",
"wallet": "foo",
"account": "default",
"valid": true,
"broadcast": true,
"hex": "010000000174fa5c5c4d870b04c47dea06f97422962d80be048413d5e787bfdc6e12c07bd0000000006b483045022100a16f35b7e7a414e5c100f362bcdc02e526ac6a962a03b955098ca5949caddd4a0220256d1d3aef9e7ea89402e3519d97d7e99c0a38022288e66363ab8a4715089c5e012102a7451395735369f2ecdfc829c0f774e88ef1303dfe5b2f04dbaab30a535dfdd6ffffffff022d260000000000001976a9143a2d4145a4f098523b3e8127f1da87cfc55b8e7988ace2de2901000000001976a914033e299551bd538711fb536beb7f99a726f24cb988ac00000000",
"response": {
"success": true
}
}
Docs coming soon
- Separate tests so that they can more easily run
This is experimental software for an experimental protocol. Please do your own research and understand the code if you decide to use it with real money.