c9c0de1fba
* renamed secp265k1 to secp256k1 * added optional 'explicit_signer_data' argument for sign method * wip * made client signer generic which required to undertake an adventure into coconut bandwidth trait bounds... * separate wrapper for TxSigner * moves signing-related features to separate module * fixed build of binaries from outside default workspace * offline signing example * fixed wallet build * post rebasing formatting * fixed bity integration build * making clippy happier * fixed post-rebase bip39 dependency version
141 lines
4.3 KiB
Rust
141 lines
4.3 KiB
Rust
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
pub mod addr_secp256k1;
|
|
pub mod error;
|
|
pub(crate) mod helpers;
|
|
mod sign_secp256k1;
|
|
pub mod version;
|
|
|
|
use crate::addr_secp256k1::AddrSecp256k1Response;
|
|
use crate::error::LedgerError;
|
|
use crate::helpers::path_bytes;
|
|
use crate::sign_secp256k1::SignSecp256k1Response;
|
|
use crate::version::VersionResponse;
|
|
use bip32::DerivationPath;
|
|
use error::Result;
|
|
use ledger_transport::APDUCommand;
|
|
use ledger_transport_hid::hidapi::HidApi;
|
|
use ledger_transport_hid::TransportNativeHID;
|
|
use std::fmt::Debug;
|
|
use std::fmt::Formatter;
|
|
use std::sync::Arc;
|
|
|
|
const CLA: u8 = 0x55;
|
|
const INS_GET_VERSION: u8 = 0x00;
|
|
const INS_SIGN_SECP256K1: u8 = 0x02;
|
|
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
|
|
|
|
const PAYLOAD_TYPE_INIT: u8 = 0x00;
|
|
const PAYLOAD_TYPE_ADD: u8 = 0x01;
|
|
const PAYLOAD_TYPE_LAST: u8 = 0x02;
|
|
const CHUNK_SIZE: usize = 250;
|
|
|
|
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
|
|
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
|
|
#[derive(Clone)]
|
|
pub struct CosmosLedger {
|
|
path: DerivationPath,
|
|
prefix: String,
|
|
transport: Arc<TransportNativeHID>,
|
|
}
|
|
|
|
impl Debug for CosmosLedger {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
write!(f, "()")
|
|
}
|
|
}
|
|
|
|
impl CosmosLedger {
|
|
/// Create the connection to the first Ledger device that we can find.
|
|
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
|
|
let api = HidApi::new()?;
|
|
let transport = Arc::new(TransportNativeHID::new(&api)?);
|
|
|
|
Ok(CosmosLedger {
|
|
path,
|
|
prefix,
|
|
transport,
|
|
})
|
|
}
|
|
|
|
/// Get the version of the device.
|
|
pub fn get_version(&self) -> Result<VersionResponse> {
|
|
let command = APDUCommand {
|
|
cla: CLA,
|
|
ins: INS_GET_VERSION,
|
|
p1: 0,
|
|
p2: 0,
|
|
data: vec![],
|
|
};
|
|
let response = self.transport.exchange(&command)?;
|
|
VersionResponse::try_from(response)
|
|
}
|
|
|
|
/// Get the SECP256K1 address of the device.
|
|
pub fn get_addr_secp256k1(&self, display: bool) -> Result<AddrSecp256k1Response> {
|
|
let display = u8::from(display);
|
|
let components = path_bytes(self.path.clone())?;
|
|
let data: Vec<u8> = vec![
|
|
[self.prefix.len() as u8].as_slice(),
|
|
self.prefix.as_bytes(),
|
|
components[0].as_slice(),
|
|
components[1].as_slice(),
|
|
components[2].as_slice(),
|
|
components[3].as_slice(),
|
|
components[4].as_slice(),
|
|
]
|
|
.into_iter()
|
|
.flatten()
|
|
.copied()
|
|
.collect();
|
|
|
|
let command = APDUCommand {
|
|
cla: CLA,
|
|
ins: INS_GET_ADDR_SECP256K1,
|
|
p1: display,
|
|
p2: 0,
|
|
data,
|
|
};
|
|
let response = self.transport.exchange(&command)?;
|
|
AddrSecp256k1Response::try_from(response)
|
|
}
|
|
|
|
pub fn sign_secp256k1(&self, message: String) -> Result<SignSecp256k1Response> {
|
|
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
|
|
.into_iter()
|
|
.flatten()
|
|
.collect();
|
|
let mut chunks = vec![serialized_path];
|
|
if message.is_empty() {
|
|
return Err(LedgerError::NoMessageFound);
|
|
}
|
|
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
|
|
chunks.push(chunk.to_vec());
|
|
}
|
|
let length = chunks.len();
|
|
for (idx, chunk) in chunks.into_iter().enumerate() {
|
|
let payload_desc = if idx == 0 {
|
|
PAYLOAD_TYPE_INIT
|
|
} else if idx + 1 == length {
|
|
PAYLOAD_TYPE_LAST
|
|
} else {
|
|
PAYLOAD_TYPE_ADD
|
|
};
|
|
let command = APDUCommand {
|
|
cla: CLA,
|
|
ins: INS_SIGN_SECP256K1,
|
|
p1: payload_desc,
|
|
p2: 0,
|
|
data: chunk,
|
|
};
|
|
let sign_response = self.transport.exchange(&command)?;
|
|
if payload_desc == PAYLOAD_TYPE_LAST {
|
|
return SignSecp256k1Response::try_from(sign_response);
|
|
}
|
|
}
|
|
// It should never reach this, as the message is not empty
|
|
Err(LedgerError::NoMessageFound)
|
|
}
|
|
}
|