use super::error::Error;
use ledger::{ApduAnswer, ApduCommand};
const CLA: u8 = 0x56;
const INS_PUBLIC_KEY_ED25519: u8 = 0x01;
const INS_SIGN_ED25519: u8 = 0x02;
const USER_MESSAGE_CHUNK_SIZE: usize = 250;
#[allow(dead_code)]
const INS_GET_VERSION: u8 = 0x00;
pub(super) struct TendermintValidatorApp {
app: ledger::LedgerApp,
}
#[allow(unsafe_code)]
unsafe impl Send for TendermintValidatorApp {}
#[allow(dead_code)]
pub struct Version {
mode: u8,
major: u8,
minor: u8,
patch: u8,
}
impl TendermintValidatorApp {
pub fn connect() -> Result<Self, Error> {
let app = ledger::LedgerApp::new()?;
Ok(TendermintValidatorApp { app })
}
#[allow(dead_code)]
pub fn version(&self) -> Result<Version, Error> {
let command = ApduCommand {
cla: CLA,
ins: INS_GET_VERSION,
p1: 0x00,
p2: 0x00,
length: 0,
data: Vec::new(),
};
let response = self.app.exchange(command)?;
if response.retcode != 0x9000 {
return Err(Error::InvalidVersion);
}
let version = Version {
mode: response.data[0],
major: response.data[1],
minor: response.data[2],
patch: response.data[3],
};
Ok(version)
}
pub fn public_key(&self) -> Result<[u8; 32], Error> {
let command = ApduCommand {
cla: CLA,
ins: INS_PUBLIC_KEY_ED25519,
p1: 0x00,
p2: 0x00,
length: 0,
data: Vec::new(),
};
match self.app.exchange(command) {
Ok(response) => {
if response.retcode != 0x9000 {
println!("WARNING: retcode={:X?}", response.retcode);
}
if response.data.len() != 32 {
return Err(Error::InvalidPk);
}
let mut array = [0u8; 32];
array.copy_from_slice(&response.data[..32]);
Ok(array)
}
Err(err) => {
Err(Error::Ledger(err))
}
}
}
pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], Error> {
let chunks = message.chunks(USER_MESSAGE_CHUNK_SIZE);
if chunks.len() > 255 {
return Err(Error::InvalidMessageSize);
}
if chunks.len() == 0 {
return Err(Error::InvalidEmptyMessage);
}
let packet_count = chunks.len() as u8;
let mut response: ApduAnswer = ApduAnswer {
data: vec![],
retcode: 0,
};
for (packet_idx, chunk) in chunks.enumerate() {
let _command = ApduCommand {
cla: CLA,
ins: INS_SIGN_ED25519,
p1: (packet_idx + 1) as u8,
p2: packet_count,
length: chunk.len() as u8,
data: chunk.to_vec(),
};
response = self.app.exchange(_command)?;
}
if response.data.is_empty() && response.retcode == 0x9000 {
return Err(Error::NoSignature);
}
if response.data.len() != 64 {
return Err(Error::InvalidSignature);
}
let mut array = [0u8; 64];
array.copy_from_slice(&response.data[..64]);
Ok(array)
}
}
#[cfg(test)]
mod tests {
use once_cell::sync::Lazy;
use signature::Verifier;
use std::sync::Mutex;
use std::time::Instant;
use super::{Error, TendermintValidatorApp};
static APP: Lazy<Mutex<TendermintValidatorApp>> =
Lazy::new(|| Mutex::new(TendermintValidatorApp::connect().unwrap()));
fn get_fake_proposal(index: u64, round: i64) -> Vec<u8> {
use byteorder::{LittleEndian, WriteBytesExt};
let other: [u8; 12] = [
0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1,
];
let mut message = Vec::new();
message.write_u8(0).unwrap();
message.write_u8(0x08).unwrap(); message.write_u8(0x01).unwrap();
message.write_u8(0x11).unwrap(); message.write_u64::<LittleEndian>(index).unwrap();
message.write_u8(0x19).unwrap(); message.write_i64::<LittleEndian>(round).unwrap();
message.write_u8(0x22).unwrap(); message.extend_from_slice(&other);
message[0] = message.len() as u8 - 1;
message
}
#[test]
#[ignore]
fn version() {
let app = APP.lock().unwrap();
let resp = app.version();
match resp {
Ok(version) => {
println!("mode {}", version.mode);
println!("major {}", version.major);
println!("minor {}", version.minor);
println!("patch {}", version.patch);
assert_eq!(version.mode, 0xFF);
assert_eq!(version.major, 0x00);
assert!(version.minor >= 0x04);
}
Err(err) => {
eprintln!("Error: {err:?}");
}
}
}
#[test]
#[ignore]
fn public_key() {
let app = APP.lock().unwrap();
let resp = app.public_key();
match resp {
Ok(pk) => {
assert_eq!(pk.len(), 32);
println!("PK {pk:0X?}");
}
Err(err) => {
eprintln!("Error: {err:?}");
panic!()
}
}
}
#[test]
#[ignore]
fn sign_empty() {
let app = APP.lock().unwrap();
let some_message0 = b"";
let signature = app.sign(some_message0);
assert!(signature.is_err());
assert!(matches!(
signature.err().unwrap(),
Error::InvalidEmptyMessage
));
}
#[test]
#[ignore]
fn sign_verify() {
let app = APP.lock().unwrap();
let some_message1 = get_fake_proposal(5, 0);
app.sign(&some_message1).unwrap();
let some_message2 = get_fake_proposal(6, 0);
match app.sign(&some_message2) {
Ok(sig) => {
use crate::keyring::ed25519;
println!("{:#?}", sig.to_vec());
let public_key_bytes = app.public_key().unwrap();
let public_key =
ed25519::VerifyingKey::try_from(public_key_bytes.as_ref()).unwrap();
let signature = ed25519::Signature::try_from(sig.as_ref()).unwrap();
assert!(public_key.verify(&some_message2, &signature).is_ok());
}
Err(e) => {
println!("Err {e:#?}");
panic!();
}
}
}
#[test]
#[ignore]
fn sign_many() {
let app = APP.lock().unwrap();
let _resp = app.public_key().unwrap();
for index in 50u8..254u8 {
let some_message1 = [
0x8, 0x1, 0x11, index, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1,
];
let signature = app.sign(&some_message1);
match signature {
Ok(sig) => {
println!("{:#?}", sig.to_vec());
}
Err(e) => {
println!("Err {e:#?}");
panic!();
}
}
}
}
#[test]
#[ignore]
fn quick_benchmark() {
let app = APP.lock().unwrap();
let msg = get_fake_proposal(0, 100);
app.sign(&msg).unwrap();
let start = Instant::now();
for i in 1u64..20u64 {
app.sign(&get_fake_proposal(i, 100)).unwrap();
}
let duration = start.elapsed();
println!("Elapsed {duration:?}");
}
}