tx_bakery/utils/
key_wallet.rs

1//! Simple wallet reading the signing key(s) from disk
2
3use std::io::Cursor;
4use std::path::Path;
5
6use anyhow::anyhow;
7use data_encoding::HEXLOWER;
8use futures::future::OptionFuture;
9use plutus_ledger_api::csl::{csl_to_pla::ToPLA, lib as csl};
10use plutus_ledger_api::v3::{
11    address::{Address, Credential, StakingCredential},
12    crypto::Ed25519PubKeyHash,
13};
14use thiserror::Error;
15use tokio;
16use tokio::fs;
17
18use crate::wallet::{Wallet, WalletError};
19
20#[derive(Error, Debug)]
21pub enum KeyWalletError {
22    #[error("Failed to read private key: {0}")]
23    PrivateKeyReadError(std::io::Error),
24
25    #[error("Failed to parse private key: {0}")]
26    PrivateKeyParseError(anyhow::Error),
27}
28
29impl From<KeyWalletError> for WalletError {
30    fn from(err: KeyWalletError) -> WalletError {
31        WalletError(anyhow!(err))
32    }
33}
34
35#[derive(Debug, serde::Deserialize)]
36struct TextEnvelope {
37    // TODO: Verify that the TextEnvelope is correct (PaymentSigningKeyShelley_ed25519 or StakeSigningKeyShelley_ed25519)
38    // #[serde(rename(deserialize = "type"))]
39    // data_type: String,
40    // description: String,
41    #[serde(rename(deserialize = "cborHex"))]
42    cbor_hex: String,
43}
44
45/// Simple wallet reading the signing key(s) from disk
46pub struct KeyWallet {
47    pay_priv_key: csl::PrivateKey,
48    pay_pkh: Ed25519PubKeyHash,
49    // TODO: Use these to implement staking features
50    // stk_priv_key: Option<PrivateKey>,
51    // stk_pkh: Option<Ed25519PubKeyHash>,
52    address: Address,
53}
54
55impl KeyWallet {
56    /// Initialise a base wallet by reading the signinig keys into memory
57    pub async fn new(
58        payment_skey: impl AsRef<Path>,
59        staking_skey: Option<impl AsRef<Path>>,
60    ) -> Result<KeyWallet, KeyWalletError> {
61        let pay_priv_key = Self::read_priv_key(payment_skey).await?;
62        let pay_pkh: Ed25519PubKeyHash = pay_priv_key.to_public().hash().to_pla();
63
64        let stk_priv_key = OptionFuture::from(staking_skey.map(Self::read_priv_key))
65            .await
66            .transpose()?;
67        let stk_pkh = stk_priv_key
68            .as_ref()
69            .map(|priv_key| priv_key.to_public().hash().to_pla());
70
71        let address = Address {
72            credential: Credential::PubKey(pay_pkh.clone()),
73            staking_credential: stk_pkh
74                .clone()
75                .map(|pkh| StakingCredential::Hash(Credential::PubKey(pkh))),
76        };
77
78        Ok(KeyWallet {
79            pay_priv_key,
80            pay_pkh,
81            // stk_priv_key,
82            // stk_pkh,
83            address,
84        })
85    }
86
87    /// Initialise an enterprise wallet by reading the signinig key into memory
88    pub async fn new_enterprise(
89        payment_skey: impl AsRef<Path>,
90    ) -> Result<KeyWallet, KeyWalletError> {
91        Self::new(payment_skey, None::<&str>).await
92    }
93
94    /// Get the private key
95    async fn read_priv_key(filepath: impl AsRef<Path>) -> Result<csl::PrivateKey, KeyWalletError> {
96        let skey_str = fs::read_to_string(&filepath)
97            .await
98            .map_err(KeyWalletError::PrivateKeyReadError)?;
99
100        let text_envelope: TextEnvelope = serde_json::from_str(&skey_str)
101            .map_err(|err| KeyWalletError::PrivateKeyParseError(anyhow!(err)))?;
102
103        let mut raw = cbor_event::de::Deserializer::from(Cursor::new(
104            HEXLOWER
105                .decode(&text_envelope.cbor_hex.clone().into_bytes())
106                .unwrap(),
107        ));
108        let bytes: Vec<u8> = raw.bytes().unwrap();
109
110        csl::PrivateKey::from_normal_bytes(&bytes)
111            .map_err(|err| KeyWalletError::PrivateKeyParseError(anyhow!(err)))
112    }
113}
114
115impl Wallet for KeyWallet {
116    fn sign_transaction(&self, tx: &csl::FixedTransaction) -> csl::FixedTransaction {
117        let tx_hash = tx.transaction_hash();
118
119        let witness = &csl::make_vkey_witness(&tx_hash, &self.pay_priv_key);
120
121        let mut tx = tx.clone();
122        tx.add_vkey_witness(witness);
123
124        tx
125    }
126
127    fn get_change_pkh(&self) -> Ed25519PubKeyHash {
128        self.pay_pkh.clone()
129    }
130
131    fn get_change_addr(&self) -> Address {
132        self.address.clone()
133    }
134}