hal 0.7.0

hal - the Bitcoin companion
Documentation
use std::str::FromStr;

use bitcoin::secp256k1;
use bitcoin::util::bip32;
use clap;

use cmd;

pub fn subcommand<'a>() -> clap::App<'a, 'a> {
	cmd::subcommand_group("bip32", "BIP-32 key derivation")
		.subcommand(cmd_derive())
		.subcommand(cmd_inspect())
}

pub fn execute<'a>(matches: &clap::ArgMatches<'a>) {
	match matches.subcommand() {
		("derive", Some(ref m)) => exec_derive(&m),
		("inspect", Some(ref m)) => exec_inspect(&m),
		(_, _) => unreachable!("clap prints help"),
	};
}

fn cmd_derive<'a>() -> clap::App<'a, 'a> {
	cmd::subcommand("derive", "derive keys from an extended key").arg(cmd::opt_yaml()).args(&[
		cmd::arg("ext-key", "extended public or private key").required(true),
		cmd::arg("derivation-path", "the derivation path").required(true),
	])
}

fn exec_derive<'a>(matches: &clap::ArgMatches<'a>) {
	let path_str = matches.value_of("derivation-path").unwrap();
	let path: bip32::DerivationPath = path_str.parse().expect("error parsing derivation path");
	let key_str = matches.value_of("ext-key").unwrap();

	let secp = secp256k1::Secp256k1::new();

	let master_fingerprint;
	let mut derived_xpriv = None;
	let derived_xpub = match bip32::ExtendedPrivKey::from_str(&key_str) {
		Ok(ext_priv) => {
			derived_xpriv = Some(ext_priv.derive_priv(&secp, &path).expect("derivation error"));
			master_fingerprint = ext_priv.fingerprint(&secp);
			bip32::ExtendedPubKey::from_private(&secp, derived_xpriv.as_ref().unwrap())
		}
		Err(_) => {
			let ext_pub: bip32::ExtendedPubKey = key_str.parse().expect("invalid extended key");
			master_fingerprint = ext_pub.fingerprint();
			ext_pub.derive_pub(&secp, &path).expect("derivation error")
		}
	};

	let info = hal::bip32::DerivationInfo {
		network: derived_xpub.network,
		master_fingerprint: Some(master_fingerprint),
		path: Some(path),
		xpriv: derived_xpriv,
		xpub: derived_xpub,
		chain_code: derived_xpub.chain_code,
		identifier: derived_xpub.identifier(),
		fingerprint: derived_xpub.fingerprint(),
		public_key: derived_xpub.public_key,
		private_key: derived_xpriv.map(|x| x.private_key),
		addresses: hal::address::Addresses::from_pubkey(
			&derived_xpub.public_key, derived_xpub.network,
		),
	};

	cmd::print_output(matches, &info)
}

fn cmd_inspect<'a>() -> clap::App<'a, 'a> {
	cmd::subcommand("inspect", "inspect a BIP-32 xpub or xpriv").args(&cmd::opts_networks()).args(
		&[cmd::opt_yaml(), cmd::arg("ext-key", "extended public or private key").required(true)],
	)
}

fn exec_inspect<'a>(matches: &clap::ArgMatches<'a>) {
	let key_str = matches.value_of("ext-key").unwrap();

	let secp = bitcoin::secp256k1::Secp256k1::signing_only();

	let mut xpriv = None;

	let xpub = match bip32::ExtendedPrivKey::from_str(&key_str) {
		Ok(ext_priv) => {
			xpriv = Some(ext_priv);
			bip32::ExtendedPubKey::from_private(&secp, xpriv.as_ref().unwrap())
		}
		Err(_) => key_str.parse().expect("invalid extended key"),
	};

	let info = hal::bip32::DerivationInfo {
		network: xpub.network,
		master_fingerprint: None,
		path: None,
		xpriv: xpriv,
		xpub: xpub,
		chain_code: xpub.chain_code,
		identifier: xpub.identifier(),
		fingerprint: xpub.fingerprint(),
		public_key: xpub.public_key,
		private_key: xpriv.map(|x| x.private_key),
		addresses: hal::address::Addresses::from_pubkey(
			&xpub.public_key, xpub.network,
		),
	};

	cmd::print_output(matches, &info)
}