Skip to content

Commit

Permalink
Tapsigner derive sig verification
Browse files Browse the repository at this point in the history
small changes (rebase)

Clean branch
  • Loading branch information
Ben Hindman committed Jun 19, 2023
1 parent ac00280 commit 18d3070
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 99 deletions.
15 changes: 12 additions & 3 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ enum TapSignerCommand {
Read,
/// This command is used once to initialize a new card.
Init,
/// Derive a public key at the given hardened path
Derive {
/// path, eg. for 84'/0'/0'/* use 84,0,0
#[clap(short, long, value_delimiter = ',', num_args = 1..)]
path: Vec<u32>,
},
}

fn main() -> Result<(), Error> {
Expand Down Expand Up @@ -93,9 +99,9 @@ fn main() -> Result<(), Error> {
let response = &sc.unseal(slot, cvc());
dbg!(response);
}
SatsCardCommand::Derive => {
dbg!(sc.derive());
},
SatsCardCommand::Derive => {
dbg!(&sc.derive());
}
}
}
CkTapCard::TapSigner(ts) | CkTapCard::SatsChip(ts) => {
Expand All @@ -111,6 +117,9 @@ fn main() -> Result<(), Error> {
let response = &ts.init(chain_code, cvc());
dbg!(response);
}
TapSignerCommand::Derive { path } => {
dbg!(&ts.derive(path, cvc()));
}
}
}
}
Expand Down
51 changes: 45 additions & 6 deletions lib/src/apdu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
use ciborium::de::from_reader;
use ciborium::ser::into_writer;
use ciborium::value::Value;
use secp256k1::ecdh::SharedSecret;
use secp256k1::hashes::hex::ToHex;
use secp256k1::PublicKey;
use serde;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fmt::{Debug, Formatter};

use secp256k1::ecdsa::Signature;

pub const APP_ID: [u8; 15] = *b"\xf0CoinkiteCARDv1";
pub const SELECT_CLA_INS_P1P2: [u8; 4] = [0x00, 0xA4, 0x04, 0x00];
pub const CBOR_CLA_INS_P1P2: [u8; 4] = [0x00, 0xCB, 0x00, 0x00];
Expand Down Expand Up @@ -228,6 +231,35 @@ pub struct ReadResponse {

impl ResponseApdu for ReadResponse {}

impl ReadResponse {
pub fn signature(&self) -> Result<Signature, Error> {
Signature::from_compact(self.sig.as_slice()).map_err(|e| Error::CiborValue(e.to_string()))
// .expect("Failed to construct ECDSA signature from ReadResponse")
}

pub fn pubkey(&self, session_key: Option<SharedSecret>) -> PublicKey {
let pubkey = if let Some(sk) = session_key {
unzip(&self.pubkey, sk)
} else {
self.pubkey.clone()
};
PublicKey::from_slice(pubkey.as_slice())
.expect("Failed to construct a PublicKey from ReadResponse")
}
}

fn unzip(encoded: &[u8], session_key: SharedSecret) -> Vec<u8> {
let zipped_bytes = encoded.to_owned().split_off(1);
let unzipped_bytes = zipped_bytes
.iter()
.zip(session_key.as_ref())
.map(|(x, y)| x ^ y);

let mut pubkey = encoded.to_owned();
pubkey.splice(1..33, unzipped_bytes);
pubkey
}

impl fmt::Display for ReadResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "pubkey: {}", self.pubkey.to_hex())
Expand All @@ -251,8 +283,7 @@ pub struct DeriveCommand {
/// provided by app, cannot be all same byte (& should be random), 16 bytes
#[serde(with = "serde_bytes")]
nonce: Vec<u8>,
/// # Tapsigner: derivation path, can be empty list for `m` case (a no-op)
path: Option<Vec<usize>>,
path: Vec<u32>, // tapsigner: empty list for `m` case (a no-op)
/// app's ephemeral public key
#[serde(with = "serde_bytes")]
epubkey: Option<Vec<u8>>,
Expand All @@ -272,22 +303,22 @@ impl DeriveCommand {
DeriveCommand {
cmd: Self::name(),
nonce,
path: None,
path: vec![],
epubkey: None,
xcvc: None,
}
}

pub fn for_tapsigner(
nonce: Vec<u8>,
path: Vec<usize>,
path: Vec<u32>,
epubkey: PublicKey,
xcvc: Vec<u8>,
) -> Self {
DeriveCommand {
cmd: Self::name(),
nonce,
path: Some(path),
path,
epubkey: Some(epubkey.serialize().to_vec()),
xcvc: Some(xcvc),
}
Expand All @@ -297,11 +328,18 @@ impl DeriveCommand {
#[derive(Deserialize, Clone)]
pub struct DeriveResponse {
#[serde(with = "serde_bytes")]
pub sig: Vec<u8>, // 64 byes
pub sig: Vec<u8>, // 64 bytes
/// chain code of derived subkey
#[serde(with = "serde_bytes")]
pub chain_code: Vec<u8>, // 32 bytes
/// master public key in effect (`m`)
#[serde(with = "serde_bytes")]
pub master_pubkey: Vec<u8>, // 33 bytes
/// derived public key for indicated path
#[serde(with = "serde_bytes")]
#[serde(default = "Option::default")]
pub pubkey: Option<Vec<u8>>, // 33 bytes
/// new nonce value, for NEXT command (not this one)
#[serde(with = "serde_bytes")]
pub card_nonce: Vec<u8>, // 16 bytes
}
Expand All @@ -314,6 +352,7 @@ impl Debug for DeriveResponse {
.field("sig", &self.sig.to_hex())
.field("chain_code", &self.chain_code.to_hex())
.field("master_pubkey", &self.master_pubkey.to_hex())
.field("pubkey", &self.pubkey.clone().map(|pk| pk.to_hex()))
.field("card_nonce", &self.card_nonce.to_hex())
.finish()
}
Expand Down
68 changes: 19 additions & 49 deletions lib/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,39 +92,29 @@ where

fn read(&mut self, cvc: Option<String>) -> Result<ReadResponse, Error> {
let card_nonce = self.card_nonce().clone();
let rng = &mut rand::thread_rng();
let nonce = rand_nonce(rng).to_vec();
let app_nonce = rand_nonce();

if self.requires_auth() {
let (cmd, session_key) = if self.requires_auth() {
let (eprivkey, epubkey, xcvc) =
self.calc_ekeys_xcvc(cvc.unwrap(), &ReadCommand::name());
let cmd = ReadCommand::authenticated(nonce.clone(), epubkey, xcvc);

let read_response: Result<ReadResponse, Error> = self.transport().transmit(cmd);
if let Ok(response) = &read_response {
let message = self.message_digest(card_nonce, nonce);
let signature = Signature::from_compact(response.sig.as_slice())
.expect("Failed to construct ECDSA signature from check response");
let session_key = SharedSecret::new(self.pubkey(), &eprivkey);
let pubkey = unzip(&mut response.pubkey.clone(), session_key)?;
self.secp().verify_ecdsa(&message, &signature, &pubkey)?;
self.set_card_nonce(response.card_nonce.clone());
}
read_response
(
ReadCommand::authenticated(app_nonce.clone(), epubkey, xcvc),
Some(SharedSecret::new(self.pubkey(), &eprivkey)),
)
} else {
let cmd = ReadCommand::unauthenticated(nonce.clone());
let read_response: Result<ReadResponse, Error> = self.transport().transmit(cmd);
if let Ok(response) = &read_response {
let message = self.message_digest(card_nonce, nonce);
let signature = Signature::from_compact(response.sig.as_slice())
.expect("Failed to construct ECDSA signature from check response");
let pubkey: PublicKey =
PublicKey::from_slice(response.pubkey.clone().as_slice()).unwrap();
self.secp().verify_ecdsa(&message, &signature, &pubkey)?;
self.set_card_nonce(response.card_nonce.clone());
}
read_response
(ReadCommand::unauthenticated(app_nonce.clone()), None)
};

let read_response: Result<ReadResponse, Error> = self.transport().transmit(cmd);
if let Ok(response) = &read_response {
self.secp().verify_ecdsa(
&self.message_digest(card_nonce, app_nonce),
&response.signature()?, // or add 'from' trait: Signature::from(response.sig: )
&response.pubkey(session_key),
)?;
self.set_card_nonce(response.card_nonce.clone());
}
read_response
}

fn message_digest(&self, card_nonce: Vec<u8>, app_nonce: Vec<u8>) -> Message {
Expand Down Expand Up @@ -175,8 +165,7 @@ where
fn message_digest(&mut self, card_nonce: Vec<u8>, app_nonce: Vec<u8>) -> Message;

fn check_certificate(&mut self) -> Result<FactoryRootKey, Error> {
let rng = &mut rand::thread_rng();
let nonce = rand_nonce(rng).to_vec();
let nonce = rand_nonce();

let card_nonce = self.card_nonce().clone();

Expand Down Expand Up @@ -225,25 +214,6 @@ where
}
}

// pub trait Derive<T>: Authentication<T>
// where
// T: CkTransport,
// {

// }

fn unzip(encoded: &mut Vec<u8>, session_key: SharedSecret) -> Result<PublicKey, secp256k1::Error> {
let session_key = session_key.as_ref(); // 32 bytes
let mut pubkey = encoded.clone();
let xor_bytes = encoded.split_off(1);
let xor_bytes = xor_bytes
.iter()
.zip(session_key.as_ref())
.map(|(x, y)| x ^ y);
pubkey.splice(1..33, xor_bytes);
PublicKey::from_slice(pubkey.as_slice())
}

#[cfg(feature = "emulator")]
#[cfg(test)]
mod tests {
Expand Down
Loading

0 comments on commit 18d3070

Please sign in to comment.