Skip to content

Commit

Permalink
CLI improvement
Browse files Browse the repository at this point in the history
* CLI version 0.12.3

* CLI can now convert between different xpub versions
  using the xpub command
  • Loading branch information
alfred-hodler committed Sep 2, 2024
1 parent 8d99a93 commit e39634e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 3 deletions.
3 changes: 2 additions & 1 deletion coldcard-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "coldcard-cli"
version = "0.12.2"
version = "0.12.3"
edition = "2021"
authors = ["Alfred Hodler <[email protected]>"]
license = "MIT"
Expand All @@ -18,6 +18,7 @@ path = "src/main.rs"

[dependencies]
coldcard = { version = "0.12.2", path = "../coldcard" }
base58 = "0.2.0"
base64 = "0.21.7"
clap = { version = "3.2.22", features = ["derive"] }
hex = "0.4.3"
Expand Down
42 changes: 40 additions & 2 deletions coldcard-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use coldcard::{util, XpubInfo};
use clap::Parser;

mod fw_upgrade;
mod xpub_version;

#[derive(clap::Parser)]
#[clap(author, version, about)]
Expand Down Expand Up @@ -172,6 +173,10 @@ enum Command {
/// The optional derivation path
path: Option<String>,

#[clap(arg_enum)]
/// The extended key version to optionally convert to.
version: Option<XpubVersion>,

/// Include the fingerprint. The output will be two lines.
#[clap(long)]
xfp: bool,
Expand Down Expand Up @@ -233,6 +238,29 @@ impl From<&SignMode> for coldcard::SignMode {
}
}

#[derive(Clone, clap::ArgEnum)]
enum XpubVersion {
Xpub,
Ypub,
Zpub,
Tpub,
Upub,
Vpub,
}

impl From<XpubVersion> for xpub_version::Version {
fn from(value: XpubVersion) -> Self {
match value {
XpubVersion::Xpub => Self::Xpub,
XpubVersion::Ypub => Self::Ypub,
XpubVersion::Zpub => Self::Zpub,
XpubVersion::Tpub => Self::Tpub,
XpubVersion::Upub => Self::Upub,
XpubVersion::Vpub => Self::Vpub,
}
}
}

fn main() -> Result<(), Error> {
env_logger::init();

Expand Down Expand Up @@ -736,11 +764,11 @@ fn handle(cli: Cli) -> Result<(), Error> {
}
}

Command::Xpub { path, xfp } => {
Command::Xpub { path, version, xfp } => {
let path = path
.map(|p| protocol::DerivationPath::new(&p))
.transpose()?;
let xpub = cc.xpub(path)?;
let mut xpub = cc.xpub(path)?;

if xfp {
let pk = util::decode_xpub(&xpub).expect("Unable to decode xpub; Coldcard error");
Expand All @@ -749,6 +777,9 @@ fn handle(cli: Cli) -> Result<(), Error> {
println!("{}", hex);
}

if let Some(version) = version {
xpub = xpub_version::convert_bytes(&xpub, version.into())?;
}
println!("{}", xpub);
}
}
Expand Down Expand Up @@ -859,6 +890,7 @@ enum Error {
NotAuthToken,
InvalidPSBT,
NoColdcardDetected,
VersionConvert(xpub_version::Error),
}

impl From<coldcard::Error> for Error {
Expand Down Expand Up @@ -897,6 +929,12 @@ impl From<std::io::Error> for Error {
}
}

impl From<xpub_version::Error> for Error {
fn from(error: xpub_version::Error) -> Self {
Self::VersionConvert(error)
}
}

fn prompt<'a>(text: &'static str, choices: &'a [&str]) -> &'a str {
let bold = console::Style::new().bold();

Expand Down
81 changes: 81 additions & 0 deletions coldcard-cli/src/xpub_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use base58::FromBase58;
use base58::ToBase58;
use coldcard::util::sha256;

/// Some of the possible extended key version bytes.
#[derive(Debug, Clone, Copy)]
pub enum Version {
Xpub,
Ypub,
Zpub,
Tpub,
Upub,
Vpub,
}

impl Version {
/// Returns the version bytes for a particular exended key version.
fn bytes(&self) -> [u8; 4] {
match self {
Version::Xpub => XPUB,
Version::Ypub => YPUB,
Version::Zpub => ZPUB,
Version::Tpub => TPUB,
Version::Upub => UPUB,
Version::Vpub => VPUB,
}
}
}

const XPUB: [u8; 4] = [0x04, 0x88, 0xB2, 0x1E];
const YPUB: [u8; 4] = [0x04, 0x9D, 0x7C, 0xB2];
const ZPUB: [u8; 4] = [0x04, 0xB2, 0x47, 0x46];
const TPUB: [u8; 4] = [0x04, 0x35, 0x87, 0xCF];
const UPUB: [u8; 4] = [0x04, 0x4A, 0x52, 0x62];
const VPUB: [u8; 4] = [0x04, 0x5F, 0x1C, 0xF6];

/// Converts an extended key to a different version.
pub fn convert_bytes(s: &str, to: Version) -> Result<String, Error> {
let mut decoded = s.from_base58().map_err(|_| Error::InvalidBase58)?;
if decoded.len() != 82 {
return Err(Error::InvalidLength);
}

decoded[0..4].copy_from_slice(&to.bytes());
let checksum = sha256(&sha256(&decoded[0..78]));
decoded[78..82].copy_from_slice(&checksum[0..4]);
Ok(decoded.to_base58())
}

#[derive(Debug, Clone, Copy)]
pub enum Error {
InvalidBase58,
InvalidLength,
}

#[cfg(test)]
mod test {
use crate::xpub_version::Version;

use super::convert_bytes;

#[test]
fn version_conversion() {
let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";

let zpub = convert_bytes(xpub, Version::Zpub).unwrap();
assert_eq!(zpub, "zpub6jftahH18ngZxUuv6oSniLNrBCSSE1B4EEU59bwTCEt8x6aS6b2mdfLxbS4QS53g85SWWP6wexqeer516433gYpZQoJie2tcMYdJ1SYYYAL");

let ypub = convert_bytes(xpub, Version::Ypub).unwrap();
assert_eq!(ypub, "ypub6QqdH2c5z7967BioGSfAWFHM1EHzHPBZK7wrND3ZpEWFtzmCqvsD1bgpaE6pSAPkiSKhkuWPCJV6mZTSNMd2tK8xYTcJ48585pZecmSUzWp");

let tpub = convert_bytes(xpub, Version::Tpub).unwrap();
assert_eq!(tpub, "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp");

let vpub = convert_bytes(xpub, Version::Vpub).unwrap();
assert_eq!(vpub, "vpub5SLqN2bLY4WeZJ9SmNJHsyzqVKreTXD4ZnPC22MugDNcjhKX5xNX9QiQWcE4SSRzVWyHWUihpKRT7hckDGNzVc69wSX2JPcfGeNiT5c2XZy");

let orig_xpub = convert_bytes(&zpub, Version::Xpub).unwrap();
assert_eq!(xpub, orig_xpub);
}
}

0 comments on commit e39634e

Please sign in to comment.