From 0f147750aedc0c07eec22e7d8c82d3c045fc6a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Mon, 12 Sep 2022 12:54:33 +0300 Subject: [PATCH] Add sign function --- common/ledger/src/error.rs | 6 ++++ common/ledger/src/lib.rs | 48 ++++++++++++++++++++++++++++- common/ledger/src/sign_secp265k1.rs | 25 +++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 common/ledger/src/sign_secp265k1.rs diff --git a/common/ledger/src/error.rs b/common/ledger/src/error.rs index d871c729bd..fc4a0a1b49 100644 --- a/common/ledger/src/error.rs +++ b/common/ledger/src/error.rs @@ -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, } diff --git a/common/ledger/src/lib.rs b/common/ledger/src/lib.rs index d3119b6d86..ab745e7609 100644 --- a/common/ledger/src/lib.rs +++ b/common/ledger/src/lib.rs @@ -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 { + let serialized_path: Vec = 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) + } } diff --git a/common/ledger/src/sign_secp265k1.rs b/common/ledger/src/sign_secp265k1.rs new file mode 100644 index 0000000000..25596d6492 --- /dev/null +++ b/common/ledger/src/sign_secp265k1.rs @@ -0,0 +1,25 @@ +// Copyright 2022 - Nym Technologies SA +// 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>> for SignSecp265k1Response { + type Error = LedgerError; + + fn try_from(answer: APDUAnswer>) -> Result { + let bytes = answer_bytes(&answer)?; + + Ok(SignSecp265k1Response { + signature: Signature::from_der(bytes)?, + }) + } +}