Add sign function
This commit is contained in:
@@ -28,4 +28,10 @@ pub enum LedgerError {
|
||||
|
||||
#[error("Bip32 - {0}")]
|
||||
Bip32(#[from] bip32::Error),
|
||||
|
||||
#[error("Signature error - {0}")]
|
||||
Signature(#[from] k256::ecdsa::Error),
|
||||
|
||||
#[error("No message found for signing transaction")]
|
||||
NoMessageFound,
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
pub mod addr_secp265k1;
|
||||
pub mod error;
|
||||
pub(crate) mod helpers;
|
||||
mod sign_secp265k1;
|
||||
pub mod version;
|
||||
|
||||
use crate::addr_secp265k1::AddrSecp265k1Response;
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::path_bytes;
|
||||
use crate::sign_secp265k1::SignSecp265k1Response;
|
||||
use crate::version::VersionResponse;
|
||||
use bip32::DerivationPath;
|
||||
use error::Result;
|
||||
@@ -17,9 +20,14 @@ 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;
|
||||
|
||||
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
|
||||
pub struct CosmosLedger {
|
||||
@@ -81,4 +89,42 @@ impl CosmosLedger {
|
||||
let response = self.transport.exchange(&command)?;
|
||||
AddrSecp265k1Response::try_from(response)
|
||||
}
|
||||
|
||||
pub fn sign_secp265k1(
|
||||
&self,
|
||||
path: DerivationPath,
|
||||
message: String,
|
||||
) -> Result<SignSecp265k1Response> {
|
||||
let serialized_path: Vec<u8> = path_bytes(path)?.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 SignSecp265k1Response::try_from(sign_response);
|
||||
}
|
||||
}
|
||||
// It should never reach this, as the message is not empty
|
||||
Err(LedgerError::NoMessageFound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::answer_bytes;
|
||||
use k256::ecdsa::Signature;
|
||||
use ledger_transport::APDUAnswer;
|
||||
|
||||
/// Version and status data of the device.
|
||||
pub struct SignSecp265k1Response {
|
||||
/// DER encoded signature data
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
|
||||
type Error = LedgerError;
|
||||
|
||||
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||
let bytes = answer_bytes(&answer)?;
|
||||
|
||||
Ok(SignSecp265k1Response {
|
||||
signature: Signature::from_der(bytes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user