use async_trait::async_trait;
use ootle_ledger_client::{Exchange, LedgerClient, LedgerClientError};
use ootle_ledger_common::arg_types::{KeyType, SignMode, SigningField};
use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::ByteArray};
use tari_ootle_address::OotleAddress;
use tari_ootle_transaction::{
IntoSigned,
PreimageField,
PreimageSegment,
Transaction,
TransactionSealSignature,
TransactionSignature,
UnsealedTransaction,
UnsignedTransaction,
};
use tari_template_lib_types::crypto::{RistrettoPublicKeyBytes, SchnorrSignatureBytes};
use crate::{
Address,
Network,
signer::{self, SignerError},
transaction::{TransactionSealSigner, TransactionSigner, TransactionStealthKeySigner},
wallet::TransactionAuthorization,
};
pub struct LedgerSigner<T> {
client: LedgerClient<T>,
address: Address,
account: u64,
index: u64,
}
impl<T> LedgerSigner<T>
where
T: Exchange,
T::Error: core::fmt::Display,
{
pub async fn connect(client: LedgerClient<T>, network: Network, account: u64, index: u64) -> signer::Result<Self> {
let account_pk = client
.get_public_key(account, index, KeyType::Account)
.await
.map_err(map_client_err)?;
let view_only_pk = client
.get_public_key(account, index, KeyType::ViewOnlyKey)
.await
.map_err(map_client_err)?;
let address = OotleAddress::new(network, view_only_pk, account_pk);
Ok(Self {
client,
address,
account,
index,
})
}
pub fn with_address(client: LedgerClient<T>, address: Address, account: u64, index: u64) -> Self {
Self {
client,
address,
account,
index,
}
}
async fn stream(
&self,
mode: SignMode,
stealth_public_nonce: Option<[u8; 32]>,
segments: Vec<PreimageSegment>,
) -> signer::Result<(RistrettoPublicKeyBytes, SchnorrSignatureBytes)> {
let refs: Vec<(SigningField, &[u8])> = segments
.iter()
.map(|seg| (to_wire(seg.field), seg.bytes.as_slice()))
.collect();
let response = self
.client
.sign_transaction(
self.account,
self.index,
KeyType::Account,
mode,
stealth_public_nonce,
&refs,
)
.await
.map_err(map_client_err)?;
let public_key = RistrettoPublicKeyBytes::from(response.public_key);
let signature = SchnorrSignatureBytes::try_from(&response.signature[..])
.map_err(|_| SignerError::other("device returned malformed signature bytes"))?;
Ok((public_key, signature))
}
}
#[async_trait]
impl<T> TransactionSigner for LedgerSigner<T>
where
T: Exchange + Send + Sync,
T::Error: core::fmt::Display + Send + Sync,
{
fn address(&self) -> &Address {
&self.address
}
async fn sign_transaction(&self, message: &UnsealedTransaction) -> signer::Result<TransactionSealSignature> {
let UnsealedTransaction::V1(unsealed) = message;
let segments = TransactionSealSignature::signing_preimage_v1(unsealed);
let (public_key, signature) = self.stream(SignMode::Seal, None, segments).await?;
Ok(TransactionSealSignature::new(public_key, signature))
}
async fn sign_authorization(
&self,
seal_signer: &RistrettoPublicKeyBytes,
tx: &UnsignedTransaction,
) -> signer::Result<TransactionAuthorization> {
let UnsignedTransaction::V1(unsigned) = tx;
let segments = TransactionSignature::signing_preimage_v1(seal_signer, unsigned);
let (public_key, signature) = self.stream(SignMode::AddSigner, None, segments).await?;
Ok(TransactionSignature::new(public_key, signature).into())
}
}
#[async_trait]
impl<T> TransactionStealthKeySigner for LedgerSigner<T>
where
T: Exchange + Send + Sync,
T::Error: core::fmt::Display + Send + Sync,
{
async fn sign_authorization_with_stealth(
&self,
public_nonce: &RistrettoPublicKey,
seal_signer: &RistrettoPublicKeyBytes,
tx: &UnsignedTransaction,
) -> signer::Result<TransactionAuthorization> {
let UnsignedTransaction::V1(unsigned) = tx;
let segments = TransactionSignature::signing_preimage_v1(seal_signer, unsigned);
let (public_key, signature) = self
.stream(SignMode::AddSigner, Some(nonce_bytes(public_nonce)), segments)
.await?;
Ok(TransactionSignature::new(public_key, signature).into())
}
async fn seal_transaction_with_stealth(
&self,
public_nonce: &RistrettoPublicKey,
message: &UnsealedTransaction,
) -> signer::Result<TransactionSealSignature> {
let UnsealedTransaction::V1(unsealed) = message;
let segments = TransactionSealSignature::signing_preimage_v1(unsealed);
let (public_key, signature) = self
.stream(SignMode::Seal, Some(nonce_bytes(public_nonce)), segments)
.await?;
Ok(TransactionSealSignature::new(public_key, signature))
}
}
#[async_trait]
impl<T> TransactionSealSigner for LedgerSigner<T>
where
T: Exchange + Send + Sync,
T::Error: core::fmt::Display + Send + Sync,
{
async fn seal_transaction(&self, tx: UnsealedTransaction) -> signer::Result<Transaction> {
let signature = self.sign_transaction(&tx).await?;
Ok(<UnsealedTransaction as IntoSigned<()>>::into_signed(tx, signature))
}
}
fn map_client_err<E: core::fmt::Display>(err: LedgerClientError<E>) -> SignerError {
SignerError::other(err.to_string())
}
fn nonce_bytes(public_nonce: &RistrettoPublicKey) -> [u8; 32] {
let mut bytes = [0u8; 32];
bytes.copy_from_slice(public_nonce.as_bytes());
bytes
}
fn to_wire(field: PreimageField) -> SigningField {
match field {
PreimageField::SchemaVersion => SigningField::SchemaVersion,
PreimageField::SealSigner => SigningField::SealSigner,
PreimageField::Network => SigningField::Network,
PreimageField::FeeInstructions => SigningField::FeeInstructions,
PreimageField::Instructions => SigningField::Instructions,
PreimageField::Inputs => SigningField::Inputs,
PreimageField::MinEpoch => SigningField::MinEpoch,
PreimageField::MaxEpoch => SigningField::MaxEpoch,
PreimageField::IsSealSignerAuthorized => SigningField::IsSealSignerAuthorized,
PreimageField::DryRun => SigningField::DryRun,
PreimageField::BlobHashes => SigningField::BlobHashes,
PreimageField::Signatures => SigningField::Signatures,
}
}