Get address and add docs
This commit is contained in:
Generated
+1
@@ -2831,6 +2831,7 @@ name = "ledger"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"k256",
|
||||
"ledger-transport",
|
||||
"ledger-transport-hid",
|
||||
"thiserror",
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user