Skip to content

Commit

Permalink
Version 0.12, new CLI commands
Browse files Browse the repository at this point in the history
* new CLI command: `install-udev-rules`

* CLI command `upgrade` now has auto download mode

* `Coldcard::upload` now has a progress hook

* More flexible DFU firmware parsing
  • Loading branch information
alfred-hodler committed Jan 31, 2024
1 parent d9dd415 commit ed2d99c
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 94 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
[![License](https://img.shields.io/crates/l/coldcard.svg)](https://github.com/alfred-hodler/rust-coldcard/blob/master/coldcard/LICENSE)
[![Test Status](https://github.com/alfred-hodler/rust-coldcard/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/alfred-hodler/rust-coldcard/actions)

This is a workspace that contains crates used for interfacing with the [Coldcard](https://coldcard.com/) hardware wallet.
This is a workspace that contains crates used for interfacing with the [Coldcard](https://coldcard.com/) hardware wallet over USB.

![Project Logo](logo.png)

The crates are as follows:

`coldcard` - the library for integration with Rust projects

`coldcard-cli` - the CLI tool for interfacing with Coldcard devices
`coldcard-cli` - the CLI tool for upgrading and interfacing with Coldcard devices

See each crate's `README.md` file for detailed information.

Expand Down
9 changes: 7 additions & 2 deletions coldcard-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "coldcard-cli"
version = "0.11.2"
version = "0.12.0"
edition = "2021"
authors = ["Alfred Hodler <alfred_hodler@protonmail.com>"]
license = "MIT"
Expand All @@ -17,11 +17,16 @@ name = "coldcard"
path = "src/main.rs"

[dependencies]
coldcard = { version = "0.11.1", path = "../coldcard" }
coldcard = { version = "0.12.0", path = "../coldcard" }
base64 = "0.13.0"
clap = { version = "3.1.6", features = ["derive"] }
hex = "0.4.3"
hmac-sha256 = "1.1.7"
indicatif = "0.17.7"
json = "0.12.4"
rpassword = "7.3.1"
env_logger = "0.11.0"
regex = "1.10.3"
ureq = "2.9.1"
semver = "1.0.21"
console = "0.15.8"
74 changes: 35 additions & 39 deletions coldcard-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Coldcard CLI

`coldcard-cli` is a CLI tool for interfacing with the [Coldcard](https://coldcard.com/) hardware wallet.
`coldcard-cli` is a CLI tool for interfacing with the [Coldcard](https://coldcard.com/) hardware wallet over USB.


Install it with:
Expand All @@ -9,59 +9,55 @@ Install it with:
$ cargo install coldcard-cli
```

![Demo](upgrade.gif)

Usage:
```bash
$ coldcard --help

coldcard-cli 0.7.0
coldcard-cli 0.12.0
Coldcard Wallet CLI Tool

USAGE:
coldcard [OPTIONS] <SUBCOMMAND>

OPTIONS:
-h, --help
Print help information

--serial <SERIAL>
The Coldcard serial number to operate on (default: first one found)

-V, --version
Print version information

--xpub <XPUB>
Perform a MITM check against an xpub
-h, --help Print help information
--serial <SERIAL> The Coldcard serial number to operate on (default: first one found)
-V, --version Print version information
--xpub <XPUB> Perform a MITM check against an xpub

SUBCOMMANDS:
address Show the address for a derivation path
auth-token Authenticate a specific user using a 6-digit token (for HSM)
backup Initiate the backup process and create an encrypted 7z file
bag Show the bag number the Coldcard arrived in
chain Show the configured blockchain
delete-user Delete a specific HSM user
help Print this message or the help of the given subcommand(s)
hsm Show the current HSM policy
hsm-start Starts the HSM mode (with a specific policy)
list List the serial numbers of connected Coldcards
local-conf Generate a 6-digit code for PSBT signing in HSM mode
locker Get the hex contents of the storage locker (HSM mode only)
logout Securely log out of the Coldcard
message Sign a text message with a specific derivation path
passphrase Set a BIP39 passphrase
pubkey Show the pubkey for a derivation path
reboot Reboot the Coldcard
sign Sign a spending PSBT transaction
test Test USB connection
upgrade Upgrade the firmware
user Create a new HSM user. The secret is generated on the device
version Show the version information of this Coldcard
xfp Show the master fingerprint for this wallet
xpub Show the xpub (default: master)
address Show the address for a derivation path
auth-token Authenticate a specific user using a 6-digit token (for HSM)
backup Initiate the backup process and create an encrypted 7z file
bag Show the bag number the Coldcard arrived in
chain Show the configured blockchain
delete-user Delete a specific HSM user
help Print this message or the help of the given subcommand(s)
hsm Show the current HSM policy
hsm-start Starts the HSM mode (with a specific policy)
install-udev-rules Installs the udev file required to detect Coldcards on Linux
list List the serial numbers of connected Coldcards
local-conf Generate a 6-digit code for PSBT signing in HSM mode
locker Get the hex contents of the storage locker (HSM mode only)
logout Securely log out of the Coldcard
message Sign a text message with a specific derivation path
passphrase Set a BIP39 passphrase
pubkey Show the pubkey for a derivation path
reboot Reboot the Coldcard
sign Sign a spending PSBT transaction
test Test USB connection
upgrade Download and upgrade to the latest firmware, or upgrade from file
user Create a new HSM user. The secret is generated on the device
version Show the version information of this Coldcard
xfp Show the master fingerprint for this wallet
xpub Show the xpub (default: master)
```
## Linux Specific Instructions
In order to be able to detect a Coldcard device on a Linux system, [51-coinkite.rules](../51-coinkite.rules) must be placed in `/etc/udev/rules.d/`.
In order to be able to detect a Coldcard device on a Linux system, [51-coinkite.rules](../51-coinkite.rules) must be placed in `/etc/udev/rules.d/`. This can also be achieved using the `install-udev-rules` command.
Two mutually exclusive HID backends are supported and can be turned on using the following features:
Expand All @@ -74,7 +70,7 @@ To see log output, run the program with the `RUST_LOG=$level` environment variab
## Library
This project also offers a library for Rust integration. See the `coldcard` crate for more information.
This project also offers a Rust library. See the `coldcard` crate for more information.
## Contributing
Expand Down
103 changes: 103 additions & 0 deletions coldcard-cli/src/fw_upgrade.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use regex::Regex;
use semver::Version;

pub const DOWNLOADS: &str = "https://www.coldcard.com/downloads";

/// A single firmware release.
#[derive(Debug)]
pub struct Release {
/// The raw name of the firmware file as defined by the vendor.
pub name: String,
/// The version of the release in the semver format.
pub version: Version,
/// Whether the release is "edge" (experimental).
pub is_edge: bool,
}

impl Release {
/// Attempts to find a list of firmware releases on the official website.
pub fn find() -> Result<Vec<Self>, ureq::Error> {
let page = fetch_download_page()?;

let mut found: Vec<_> = firmware_regex()
.find_iter(&page)
.map(|m| {
let version = version_regex().find(m.as_str()).unwrap();
let edge_marker = version.as_str().find('X');
let (version, is_edge) = match edge_marker {
Some(pos) => (Version::parse(&version.as_str()[1..pos]), true),
None => (Version::parse(&version.as_str()[1..]), false),
};

Release {
name: m.as_str().to_owned(),
version: version.unwrap(),
is_edge,
}
})
.collect();

found.sort_by(|a, b| a.version.cmp(&b.version));
found.reverse();

Ok(found)
}

/// Downloads a firmware release.
pub fn download<F: FnMut(usize, usize)>(
&self,
mut progress: F,
) -> Result<Vec<u8>, ureq::Error> {
let url = format!("{DOWNLOADS}/{}", self.name);
let response = ureq::get(&url).call()?;

let size: usize = response
.header("Content-Length")
.and_then(|h| h.parse().ok())
.unwrap_or_default();

let mut reader = response.into_reader();
let mut downloaded = 0;
let mut bytes = Vec::with_capacity(size);
let mut buffer = [0_u8; 4096];

while downloaded < 20 * 1024 * 1024 {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
downloaded += read;
bytes.extend_from_slice(&buffer[..read]);
progress(downloaded, size);
}

Ok(bytes)
}
}

pub fn best_match<'a>(releases: &'a [Release], our_model: Option<&str>) -> Option<&'a Release> {
releases
.iter()
.filter(|r| {
let mk2_or_mk3 = r.version.major == 4 && matches!(our_model, Some("mk2" | "mk3"));
let mk4 = r.version.major == 5 && matches!(our_model, Some("mk4"));

!r.is_edge && (mk2_or_mk3 || mk4)
})
.max_by(|a, b| a.version.cmp(&b.version))
}

fn fetch_download_page() -> Result<String, ureq::Error> {
ureq::get(DOWNLOADS)
.call()
.map(|r| r.into_string().expect("bad utf-8"))
}

fn firmware_regex() -> Regex {
Regex::new(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]+-v[0-9]+\.[0-9]+\.[0-9]+X?(-mk.)?-coldcard.dfu")
.unwrap()
}

fn version_regex() -> Regex {
Regex::new(r"v[0-9]+\.[0-9]+\.[0-9]+X?").unwrap()
}
Loading

0 comments on commit ed2d99c

Please sign in to comment.