Get address and add docs

This commit is contained in:
Bogdan-Ștefan Neacșu
2022-09-09 14:20:09 +03:00
parent 94d70e47fc
commit 60d17389d0
7 changed files with 135 additions and 21 deletions
Generated
+1
View File
@@ -2831,6 +2831,7 @@ name = "ledger"
version = "0.1.0"
dependencies = [
"bip32",
"k256",
"ledger-transport",
"ledger-transport-hid",
"thiserror",
+1
View File
@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
bip32 = "0.3.0"
k256 = "0.10.4"
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use bip32::{PublicKey, PublicKeyBytes};
use ledger_transport::APDUAnswer;
/// SECP265K1 address of the device.
pub struct AddrSecp265k1Response {
/// SECP265K1 public key.
pub public_key: k256::PublicKey,
/// String representation of the Cosmos address.
pub address: String,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
if bytes.len() < 33 {
return Err(Self::Error::InvalidAnswerLength {
expected: 33,
received: bytes.len(),
});
}
let (pub_key, addr) = bytes.split_at(33);
let public_key = k256::PublicKey::from_bytes(
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
)?;
let address = String::from_utf8(addr.to_vec()).unwrap();
Ok(AddrSecp265k1Response {
public_key,
address,
})
}
}
+7
View File
@@ -5,6 +5,7 @@ use thiserror::Error;
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
/// Ledger specific errors.
#[derive(Debug, Error)]
pub enum LedgerError {
#[error("HID API - {0}")]
@@ -21,4 +22,10 @@ pub enum LedgerError {
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
InvalidAnswerLength { expected: usize, received: usize },
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
InvalidDerivationPath { expected: usize, received: usize },
#[error("Bip32 - {0}")]
Bip32(#[from] bip32::Error),
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::{LedgerError, Result};
use bip32::DerivationPath;
use ledger_transport::{APDUAnswer, APDUErrorCode};
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
@@ -15,3 +16,22 @@ pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
}),
}
}
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
let received = path.len();
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
if components.len() != 5 {
Err(LedgerError::InvalidDerivationPath {
expected: 5,
received,
})
} else {
Ok([
components[0],
components[1],
components[2],
components[3],
components[4],
])
}
}
+47 -5
View File
@@ -1,11 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod adpu_answer;
pub mod addr_secp265k1;
pub mod error;
pub(crate) mod helpers;
pub mod version;
use crate::addr_secp265k1::AddrSecp265k1Response;
use crate::helpers::path_bytes;
use crate::version::VersionResponse;
use bip32::DerivationPath;
use error::Result;
use ledger_transport::APDUCommand;
use ledger_transport_hid::hidapi::HidApi;
@@ -13,21 +17,25 @@ use ledger_transport_hid::TransportNativeHID;
const CLA: u8 = 0x55;
const INS_GET_VERSION: u8 = 0x00;
const INS_SIGN_SECP256K1: u8 = 0x02;
const _INS_SIGN_SECP256K1: u8 = 0x02;
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
pub struct Ledger {
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
pub struct CosmosLedger {
transport: TransportNativeHID,
}
impl Ledger {
impl CosmosLedger {
/// Create the connection to the first Ledger device that we can find.
pub fn new() -> Result<Self> {
let api = HidApi::new()?;
let transport = TransportNativeHID::new(&api)?;
Ok(Ledger { transport })
Ok(CosmosLedger { transport })
}
/// Get the version of the device.
pub fn get_version(&self) -> Result<VersionResponse> {
let command = APDUCommand {
cla: CLA,
@@ -39,4 +47,38 @@ impl Ledger {
let response = self.transport.exchange(&command)?;
VersionResponse::try_from(response)
}
/// Get the SECP265K1 address of the device.
pub fn get_addr_secp265k1(
&self,
path: DerivationPath,
prefix: &str,
display: bool,
) -> Result<AddrSecp265k1Response> {
let display = if display { 1 } else { 0 };
let components = path_bytes(path)?;
let data: Vec<u8> = vec![
[prefix.len() as u8].as_slice(),
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)?;
AddrSecp265k1Response::try_from(response)
}
}
+19 -16
View File
@@ -1,39 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::adpu_answer::answer_bytes;
use crate::error::LedgerError;
use crate::helpers::answer_bytes;
use ledger_transport::APDUAnswer;
/// Version and status data of the device.
pub struct VersionResponse {
/// Activation status of test mode.
pub test_mode: bool,
/// Major part of Cosmos application version.
pub major: u8,
/// Minor part of Cosmos application version.
pub minor: u8,
/// Patch part of Cosmos application version.
pub patch: u8,
/// PIN locked status.
pub device_locked: bool,
}
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
type Error = crate::error::LedgerError;
type Error = LedgerError;
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
let bytes = answer_bytes(&answer)?;
let received = bytes.len();
let err =
|expected| -> LedgerError { Self::Error::InvalidAnswerLength { expected, received } };
let test_mode = *bytes.get(0).ok_or_else(|| err(0))? != 0;
let major = *bytes.get(1).ok_or_else(|| err(1))?;
let minor = *bytes.get(2).ok_or_else(|| err(2))?;
let patch = *bytes.get(3).ok_or_else(|| err(3))?;
let device_locked = *bytes.get(4).ok_or_else(|| err(4))? != 0;
if bytes.len() != 5 {
return Err(Self::Error::InvalidAnswerLength {
expected: 5,
received: bytes.len(),
});
}
Ok(VersionResponse {
test_mode,
major,
minor,
patch,
device_locked,
test_mode: bytes[0] != 0,
major: bytes[1],
minor: bytes[2],
patch: bytes[3],
device_locked: bytes[4] != 0,
})
}
}