Skip to content

Commit c1a131c

Browse files
committed
Merge #29: Async and support TAPSIGNER backup and change commands, sign PSBT
3d7c1ec Make uniffi dep version less restrictive (Praveen Perera) 6609c34 Update README.md to show that we've added support for `change` and `backup` commands (Praveen Perera) 1f95e27 Update tap_signer.rs (Praveen Perera) 3ac5f95 Add note about toggling the hardened bit (Praveen Perera) 9cf9fbd Get the correct path for deriving (Praveen Perera) 09c2f57 Derive pubkey from TAPSIGNER if we get a pubkey mismatch (Praveen Perera) 30bfddb Improve comments on the PSBT signing code (Praveen Perera) 7db62ee Fix clippy warnings (Praveen Perera) fe190c4 Add docs and remove finalizing PSBT, leave it up to the user (Praveen Perera) 2871698 Get the subpath correctly from the PSBT (Praveen Perera) 37f7fc6 Use byte arrays for `DeriveResponse` (Praveen Perera) c71ba3f Add function to finalize PSBT (Praveen Perera) bbcb1d2 Use secp from `bitcoin`, verify pubkey matches (Praveen Perera) ad81034 Retry `UnluckyNumber` errors during `sign` command (Praveen Perera) 0b98baa Add `reason` to `InvalidScript` error (Praveen Perera) ab0ae62 Make `bitcoin` a regular dependency (Praveen Perera) 2097a84 Use a `vec` for subpath because it can be 0,1,2 in length (Praveen Perera) 898f7be Add `sign` command to cli (Praveen Perera) ae2fb07 Use byte arrays whenever possible (Praveen Perera) c907f6f Add `psbt` signing to lib (Praveen Perera) 4856cc0 Fix typo in comments (Praveen Perera) 54bd325 Make all commands take a &str instead of a String for cvc and fix tests (Praveen Perera) f93d247 Fix errors in examples (Praveen Perera) b787752 Update tap_signer.rs (Praveen Perera) 98c21e0 Add `StatusCommand` to TapSigner (Praveen Perera) b020c4d Update tap_signer.rs (Praveen Perera) f6841a3 Revert changes, SATSCARD won't have pubkey set (Praveen Perera) 3dfec7e Added note about signature verification for derive command (Praveen Perera) 37721f4 Fix signature verification for `derive` command (Praveen Perera) cf7f6e2 Adjust wording on `BackupFirst` error (Praveen Perera) dd01257 Create `CkTapError` enum to represent errors returned by the card (Praveen Perera) 22515cc Use slice instead of Vec for master pubkey (Praveen Perera) c754df5 Make `derive` signature verification work with a derivation path (Praveen Perera) 2f8d83e Make `chain_code` a byte array instead of a Vec (Praveen Perera) a74be41 Rework CvcChangeError (Praveen Perera) fe3033e Fix imports (Praveen Perera) bf32158 Improve error handling (Praveen Perera) d36a8f0 Complete `change` command (Praveen Perera) a81474f Add `backup` and `change` commands to CLI (Praveen Perera) d1a7bc6 Implement `change` and `backup` commands and validation logic (Praveen Perera) 115374d Set card nonce as 16 byte array (Praveen Perera) 2756692 Create new `TapSignerError` (Praveen Perera) 591f92a Change all `epubkey` to be 33 bytes (Praveen Perera) 0440250 Change all `cmd` from `String` to `&'static str` (Praveen Perera) b51dd5b Separate apdu commands to modules (Praveen Perera) 5dc76a5 Separate out functions for `TAPSIGNER` & `SATSCARD` (Praveen Perera) 42a04fa Allow transmit to be used in async contexts (Praveen Perera) e6fa7ab Fix CLI (Praveen Perera) 0ef500f Make the `CkTransport` trait `async` (Praveen Perera) 0450bc6 Update deps and fix breaking changes (Praveen Perera) Pull request description: ACKs for top commit: notmandatory: ACK 3d7c1ec Tree-SHA512: 176362a894575cad82491cefafa79c625f55909dc8287192aa1dcd27c789dd4bf6266407a8aaf3cffcba7cb9ca3873a62bf73f4f20a6e8cbf4cb88ec8e0a16af
2 parents d825822 + 3d7c1ec commit c1a131c

17 files changed

Lines changed: 1250 additions & 720 deletions

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# rust-cktap
22

3-
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/notmandatory/rust-cktap/blob/master/LICENSE)
3+
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/notmandatory/rust-cktap/blob/master/LICENSE)
44
[![CI](https://github.com/notmandatory/rust-cktap/actions/workflows/test.yml/badge.svg)](https://github.com/notmandatory/rust-cktap/actions/workflows/test.yml)
55
[![rustc](https://img.shields.io/badge/rustc-1.57.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html)
66

77
A Rust implementation of the [Coinkite Tap Protocol](https://github.com/coinkite/coinkite-tap-proto) (cktap)
88
for use with [SATSCARD], [TAPSIGNER], and [SATSCHIP] products.
99

10-
This project provides PC/SC APDU message encoding and decoding, cvc authentication, certificate chain verification, and card response verification.
10+
This project provides PC/SC APDU message encoding and decoding, cvc authentication, certificate chain verification, and card response verification.
1111

1212
It is up to the crate user to send and receive the raw cktap APDU messages via NFC to the card by implementing the `CkTransport` trait. An example implementation is provided using the optional rust `pcsc` crate. Mobile users are expected to implement `CkTransport` using the iOS or Android provided libraries.
1313

@@ -37,9 +37,9 @@ It is up to the crate user to send and receive the raw cktap APDU messages via N
3737

3838
#### TAPSIGNER-Only Commands
3939

40-
- [ ] [change](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#change)
40+
- [x] [change](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#change)
4141
- [x] [xpub](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#xpub)
42-
- [ ] [backup](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#backup)
42+
- [x] [backup](https://github.com/coinkite/coinkite-tap-proto/blob/master/docs/protocol.md#backup)
4343

4444
### Automated Testing with Emulator
4545

@@ -52,21 +52,21 @@ It is up to the crate user to send and receive the raw cktap APDU messages via N
5252

5353
#### Prerequisites
5454

55-
1. USB PCSC NFC card reader, for example:
56-
* [OMNIKEY 5022 CL](https://www.hidglobal.com/products/omnikey-5022-reader)
55+
1. USB PCSC NFC card reader, for example:
56+
- [OMNIKEY 5022 CL](https://www.hidglobal.com/products/omnikey-5022-reader)
5757
2. Coinkite SATSCARD, TAPSIGNER, or SATSCHIP cards
58-
Install vendor PCSC driver
58+
Install vendor PCSC driver
5959
3. Connect NFC reader to desktop system
6060
4. Place SATSCARD, TAPSIGNER, or SATSCHIP on reader
61-
61+
6262
#### Run CLI
6363

64-
```
65-
cargo run -p cktap-cli -- --help
66-
cargo run -p cktap-cli -- certs
67-
cargo run -p cktap-cli -- read
68-
```
64+
```
65+
cargo run -p cktap-cli -- --help
66+
cargo run -p cktap-cli -- certs
67+
cargo run -p cktap-cli -- read
68+
```
6969

7070
[SATSCARD]: https://satscard.com/
7171
[TAPSIGNER]: https://tapsigner.com/
72-
[SATSCHIP]: https://satschip.com/
72+
[SATSCHIP]: https://satschip.com/

cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ rust-cktap = { path = "../lib", features = ["pcsc"] }
1010
pcsc = { version = "2" }
1111
clap = { version = "4.3.1", features = ["derive"] }
1212
rpassword = { version = "7.2" }
13+
tokio = { version = "1", features = ["full"] }
1314

1415
[features]
1516
emulator = ["rust-cktap/emulator"]

cli/src/main.rs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rust_cktap::commands::{CkTransport, Read};
66
use rust_cktap::emulator;
77
#[cfg(not(feature = "emulator"))]
88
use rust_cktap::pcsc;
9+
use rust_cktap::secp256k1::hashes::Hash as _;
910
use rust_cktap::secp256k1::rand;
1011
use rust_cktap::{apdu::Error, commands::Certificate, rand_chaincode, CkTapCard};
1112
use std::io;
@@ -65,16 +66,23 @@ enum TapSignerCommand {
6566
#[clap(short, long, value_delimiter = ',', num_args = 1..)]
6667
path: Vec<u32>,
6768
},
69+
/// Get an encrypted backup of the card's private key
70+
Backup,
71+
/// Change the PIN (CVC) used for card authentication to a new user provided one
72+
Change { new_cvc: String },
73+
/// Sign a digest
74+
Sign { to_sign: String },
6875
}
6976

70-
fn main() -> Result<(), Error> {
77+
#[tokio::main]
78+
async fn main() -> Result<(), Error> {
7179
// figure out what type of card we have before parsing cli args
7280
#[cfg(not(feature = "emulator"))]
73-
let mut card = pcsc::find_first()?;
81+
let mut card = pcsc::find_first().await?;
7482

7583
// if emulator feature enabled override pcsc card
7684
#[cfg(feature = "emulator")]
77-
let mut card = emulator::find_emulator()?;
85+
let mut card = emulator::find_emulator().await?;
7886

7987
let rng = &mut rand::thread_rng();
8088

@@ -86,21 +94,21 @@ fn main() -> Result<(), Error> {
8694
dbg!(&sc);
8795
}
8896
SatsCardCommand::Address => println!("Address: {}", sc.address().unwrap()),
89-
SatsCardCommand::Certs => check_cert(sc),
90-
SatsCardCommand::Read => read(sc, None),
97+
SatsCardCommand::Certs => check_cert(sc).await,
98+
SatsCardCommand::Read => read(sc, None).await,
9199
SatsCardCommand::New => {
92100
let slot = sc.slot().expect("current slot number");
93-
let chain_code = Some(rand_chaincode(rng).to_vec());
94-
let response = &sc.new_slot(slot, chain_code, cvc()).unwrap();
101+
let chain_code = Some(rand_chaincode(rng));
102+
let response = &sc.new_slot(slot, chain_code, &cvc()).await.unwrap();
95103
println!("{}", response)
96104
}
97105
SatsCardCommand::Unseal => {
98106
let slot = sc.slot().expect("current slot number");
99-
let response = &sc.unseal(slot, cvc()).unwrap();
107+
let response = &sc.unseal(slot, &cvc()).await.unwrap();
100108
println!("{}", response)
101109
}
102110
SatsCardCommand::Derive => {
103-
dbg!(&sc.derive());
111+
dbg!(&sc.derive().await);
104112
}
105113
}
106114
}
@@ -110,15 +118,33 @@ fn main() -> Result<(), Error> {
110118
TapSignerCommand::Debug => {
111119
dbg!(&ts);
112120
}
113-
TapSignerCommand::Certs => check_cert(ts),
114-
TapSignerCommand::Read => read(ts, Some(cvc())),
121+
TapSignerCommand::Certs => check_cert(ts).await,
122+
TapSignerCommand::Read => read(ts, Some(cvc())).await,
115123
TapSignerCommand::Init => {
116-
let chain_code = rand_chaincode(rng).to_vec();
117-
let response = &ts.init(chain_code, cvc());
124+
let chain_code = rand_chaincode(rng);
125+
let response = &ts.init(chain_code, &cvc()).await;
118126
dbg!(response);
119127
}
120128
TapSignerCommand::Derive { path } => {
121-
dbg!(&ts.derive(path, cvc()));
129+
dbg!(&ts.derive(&path, &cvc()).await);
130+
}
131+
132+
TapSignerCommand::Backup => {
133+
let response = &ts.backup(&cvc()).await;
134+
println!("{:?}", response);
135+
}
136+
137+
TapSignerCommand::Change { new_cvc } => {
138+
let response = &ts.change(&new_cvc, &cvc()).await;
139+
println!("{:?}", response);
140+
}
141+
TapSignerCommand::Sign { to_sign } => {
142+
let digest: [u8; 32] =
143+
rust_cktap::secp256k1::hashes::sha256::Hash::hash(to_sign.as_bytes())
144+
.to_byte_array();
145+
146+
let response = &ts.sign(digest, vec![], &cvc()).await;
147+
println!("{:?}", response);
122148
}
123149
}
124150
}
@@ -129,8 +155,11 @@ fn main() -> Result<(), Error> {
129155

130156
// handler functions for each command
131157

132-
fn check_cert<T: CkTransport>(card: &mut dyn Certificate<T>) {
133-
if let Ok(k) = card.check_certificate() {
158+
async fn check_cert<C, T: CkTransport>(card: &mut C)
159+
where
160+
C: Certificate<T>,
161+
{
162+
if let Ok(k) = card.check_certificate().await {
134163
println!(
135164
"Genuine card from Coinkite.\nHas cert signed by: {}",
136165
k.name()
@@ -140,8 +169,11 @@ fn check_cert<T: CkTransport>(card: &mut dyn Certificate<T>) {
140169
}
141170
}
142171

143-
fn read<T: CkTransport>(card: &mut dyn Read<T>, cvc: Option<String>) {
144-
match card.read(cvc) {
172+
async fn read<C, T: CkTransport>(card: &mut C, cvc: Option<String>)
173+
where
174+
C: Read<T>,
175+
{
176+
match card.read(cvc).await {
145177
Ok(resp) => println!("{}", resp),
146178
Err(e) => {
147179
dbg!(&e);

lib/Cargo.toml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ version = "0.1.0"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7-
87
[lib]
98
crate-type = ["lib", "staticlib", "cdylib"]
109
name = "rust_cktap"
@@ -13,15 +12,26 @@ name = "rust_cktap"
1312
ciborium = "0.2.0"
1413
serde = "1"
1514
serde_bytes = "0.11"
16-
secp256k1 = { version = "0.26.0", features = ["rand-std", "bitcoin-hashes-std", "recovery"] }
1715

18-
# optional dependencies
16+
# async
17+
tokio = { version = "1.44", features = ["macros"] }
18+
19+
# error handling
20+
thiserror = "2.0"
21+
22+
# bitcoin
23+
bitcoin = { version = "0.32", features = ["rand-std"] }
24+
25+
# logging
26+
log = "0.4"
27+
28+
# uniffi todo: make this optional
29+
uniffi = { version = "0.29.1", features = ["cli"] }
30+
1931
pcsc = { version = "2", optional = true }
20-
uniffi = { version = "=0.28.0", features = ["cli"] }
21-
thiserror = "1.0.58"
2232

2333
[build-dependencies]
24-
uniffi = { version = "=0.28.0", features = ["build"] }
34+
uniffi = { version = "0.29.1", features = ["build"] }
2535

2636
[features]
2737
default = []

lib/build.rs

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/examples/pcsc.rs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rust_cktap::apdu::Error;
44
use rust_cktap::commands::{Certificate, Wait};
55
use rust_cktap::{pcsc, rand_chaincode, CkTapCard};
66

7-
use secp256k1::rand;
7+
use bitcoin::secp256k1::rand;
88
use std::io;
99
use std::io::Write;
1010

@@ -17,8 +17,9 @@ fn get_cvc() -> String {
1717
}
1818

1919
// Example using pcsc crate
20-
fn main() -> Result<(), Error> {
21-
let card = pcsc::find_first()?;
20+
#[tokio::main]
21+
async fn main() -> Result<(), Error> {
22+
let card = pcsc::find_first().await?;
2223
dbg!(&card);
2324

2425
let rng = &mut rand::thread_rng();
@@ -30,25 +31,25 @@ fn main() -> Result<(), Error> {
3031
// if auth delay call wait
3132
while ts.auth_delay.is_some() {
3233
dbg!(ts.auth_delay.unwrap());
33-
ts.wait(None)?;
34+
ts.wait(None).await?;
3435
}
3536

3637
// only do this once per card!
3738
if ts.path.is_none() {
38-
let chain_code = rand_chaincode(rng).to_vec();
39-
let new_result = ts.init(chain_code, cvc)?;
39+
let chain_code = rand_chaincode(rng);
40+
let new_result = ts.init(chain_code, &cvc).await.unwrap();
4041
dbg!(new_result);
4142
}
4243

43-
// let read_result = card.read(cvc.clone())?;
44+
// let read_result = ts.read(Some(cvc.clone())).await?;
4445
// dbg!(read_result);
4546

46-
dbg!(ts.check_certificate().unwrap().name());
47+
dbg!(ts.check_certificate().await.unwrap().name());
4748

4849
//let dump_result = card.dump();
4950

5051
// let path = vec![2147483732, 2147483648, 2147483648];
51-
// let derive_result = card.derive(path, cvc.clone())?;
52+
// let derive_result = ts.derive(path, cvc.clone()).await?;
5253
// dbg!(&derive_result);
5354

5455
// let nfc_result = card.nfc()?;
@@ -60,17 +61,17 @@ fn main() -> Result<(), Error> {
6061
// if auth delay call wait
6162
while chip.auth_delay.is_some() {
6263
dbg!(chip.auth_delay.unwrap());
63-
chip.wait(None)?;
64+
chip.wait(None).await?;
6465
}
6566

6667
// only do this once per card!
6768
if chip.path.is_none() {
68-
let chain_code = rand_chaincode(rng).to_vec();
69-
let new_result = chip.init(chain_code, get_cvc())?;
69+
let chain_code = rand_chaincode(rng);
70+
let new_result = chip.init(chain_code, &get_cvc()).await.unwrap();
7071
dbg!(new_result);
7172
}
7273

73-
// let read_result = card.read(cvc)?;
74+
// let read_result = chip.read(Some(cvc.clone())).await?;
7475
// dbg!(read_result);
7576

7677
// let nfc_result = card.nfc()?;
@@ -80,17 +81,17 @@ fn main() -> Result<(), Error> {
8081
// if auth delay call wait
8182
while sc.auth_delay.is_some() {
8283
dbg!(sc.auth_delay.unwrap());
83-
let wait_response = sc.wait(None)?;
84+
let wait_response = sc.wait(None).await?;
8485
dbg!(wait_response);
8586
}
8687

87-
// let read_result = sc.read(None)?;
88+
// let read_result = sc.read(None).await?;
8889
// dbg!(read_result);
8990

90-
// let derive_result = card.derive()?;
91+
// let derive_result = sc.derive().await?;
9192
// dbg!(&derive_result);
9293

93-
dbg!(sc.check_certificate().unwrap().name());
94+
dbg!(sc.check_certificate().await.unwrap().name());
9495

9596
// let nfc_result = card.nfc()?;
9697
// dbg!(nfc_result);
@@ -99,20 +100,20 @@ fn main() -> Result<(), Error> {
99100
// if slot == &0 {
100101
// // TODO must unseal first
101102
// let chain_code = rand_chaincode(rng).to_vec();
102-
// let new_result = card.new_slot(0, chain_code, get_cvc())?;
103+
// let new_result = sc.new_slot(0, chain_code, get_cvc()).await?;
103104
// }
104105
// }
105106

106107
// let certs_result = card.certs()?;
107108
// dbg!(certs_result);
108109

109-
// let unseal_result = card.unseal(0, get_cvc())?;
110+
// let unseal_result = sc.unseal(0, get_cvc()).await?;
110111
// dbg!(unseal_result);
111112

112-
// let dump_result = card.dump(0, None)?;
113+
// let dump_result = sc.dump(0, None).await?;
113114
// dbg!(dump_result);
114115

115-
// let dump_result = card.dump(0, Some(get_cvc()))?;
116+
// let dump_result = sc.dump(0, Some(get_cvc())).await?;
116117
// dbg!(dump_result);
117118
}
118119
}

0 commit comments

Comments
 (0)