near_api/signer/
ledger.rs

1use near_api_types::{
2    crypto::KeyType,
3    transaction::{
4        delegate_action::{DelegateAction, NonDelegateAction, SignedDelegateAction},
5        PrepopulateTransaction, SignedTransaction, Transaction, TransactionV0,
6    },
7    AccountId, BlockHeight, CryptoHash, Nonce, PublicKey, SecretKey, Signature,
8};
9use slipped10::BIP32Path;
10use tokio::sync::OnceCell;
11use tracing::{debug, info, instrument, warn};
12
13use crate::errors::{LedgerError, MetaSignError, SignerError};
14
15use super::{NEP413Payload, SignerTrait};
16
17const LEDGER_SIGNER_TARGET: &str = "near_api::signer::ledger";
18
19#[derive(Debug, Clone)]
20pub struct LedgerSigner {
21    hd_path: BIP32Path,
22    public_key: OnceCell<PublicKey>,
23}
24
25impl LedgerSigner {
26    pub const fn new(hd_path: BIP32Path) -> Self {
27        Self {
28            hd_path,
29            public_key: OnceCell::const_new(),
30        }
31    }
32}
33
34#[async_trait::async_trait]
35impl SignerTrait for LedgerSigner {
36    #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
37    async fn sign(
38        &self,
39        tr: PrepopulateTransaction,
40        public_key: PublicKey,
41        nonce: Nonce,
42        block_hash: CryptoHash,
43    ) -> Result<SignedTransaction, SignerError> {
44        debug!(target: LEDGER_SIGNER_TARGET, "Preparing unsigned transaction");
45        let unsigned_tx = Transaction::V0(TransactionV0 {
46            signer_id: tr.signer_id.clone(),
47            public_key,
48            receiver_id: tr.receiver_id,
49            nonce,
50            block_hash,
51            actions: tr.actions,
52        });
53        let unsigned_tx_bytes = borsh::to_vec(&unsigned_tx).map_err(LedgerError::from)?;
54        let hd_path = self.hd_path.clone();
55
56        info!(target: LEDGER_SIGNER_TARGET, "Signing transaction with Ledger");
57        let signature = tokio::task::spawn_blocking(move || {
58            let unsigned_tx_bytes = unsigned_tx_bytes;
59            let signature = near_ledger::sign_transaction(&unsigned_tx_bytes, hd_path)
60                .map_err(LedgerError::from)?;
61
62            Ok::<_, LedgerError>(signature)
63        })
64        .await
65        .map_err(LedgerError::from)?;
66
67        let signature = signature?;
68
69        debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object");
70        let signature = Signature::from_parts(KeyType::ED25519, signature.as_ref())
71            .map_err(|e| LedgerError::SignatureDeserializationError(e.to_string()))?;
72
73        info!(target: LEDGER_SIGNER_TARGET, "Transaction signed successfully");
74        Ok(SignedTransaction::new(signature, unsigned_tx))
75    }
76
77    #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))]
78    async fn sign_meta(
79        &self,
80        tr: PrepopulateTransaction,
81        public_key: PublicKey,
82        nonce: Nonce,
83        _block_hash: CryptoHash,
84        max_block_height: BlockHeight,
85    ) -> Result<SignedDelegateAction, MetaSignError> {
86        debug!(target: LEDGER_SIGNER_TARGET, "Preparing delegate action");
87        let actions = tr
88            .actions
89            .into_iter()
90            .map(NonDelegateAction::try_from)
91            .collect::<Result<_, _>>()
92            .map_err(|_| MetaSignError::DelegateActionIsNotSupported)?;
93        let delegate_action = DelegateAction {
94            sender_id: tr.signer_id,
95            receiver_id: tr.receiver_id,
96            actions,
97            nonce,
98            max_block_height,
99            public_key,
100        };
101
102        let delegate_action_bytes = borsh::to_vec(&delegate_action)
103            .map_err(LedgerError::from)
104            .map_err(SignerError::from)?;
105        let hd_path = self.hd_path.clone();
106
107        info!(target: LEDGER_SIGNER_TARGET, "Signing delegate action with Ledger");
108        let signature = tokio::task::spawn_blocking(move || {
109            let delegate_action_bytes = delegate_action_bytes;
110            let signature =
111                near_ledger::sign_message_nep366_delegate_action(&delegate_action_bytes, hd_path)
112                    .map_err(LedgerError::from)?;
113
114            Ok::<_, LedgerError>(signature)
115        })
116        .await
117        .map_err(LedgerError::from)
118        .map_err(SignerError::from)?;
119
120        let signature = signature.map_err(SignerError::from)?;
121
122        debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object for delegate action");
123        let signature =
124            Signature::from_parts(KeyType::ED25519, signature.as_ref()).map_err(|e| {
125                SignerError::LedgerError(LedgerError::SignatureDeserializationError(e.to_string()))
126            })?;
127
128        info!(target: LEDGER_SIGNER_TARGET, "Delegate action signed successfully");
129        Ok(SignedDelegateAction {
130            delegate_action,
131            signature,
132        })
133    }
134
135    #[instrument(skip(self), fields(signer_id = %_signer_id, receiver_id = %payload.recipient, message = %payload.message))]
136    async fn sign_message_nep413(
137        &self,
138        _signer_id: AccountId,
139        _public_key: PublicKey,
140        payload: NEP413Payload,
141    ) -> Result<Signature, SignerError> {
142        info!(target: LEDGER_SIGNER_TARGET, "Signing NEP413 message with Ledger");
143        let hd_path = self.hd_path.clone();
144        let payload = payload.into();
145
146        let signature: Vec<u8> = tokio::task::spawn_blocking(move || {
147            let signature =
148                near_ledger::sign_message_nep413(&payload, hd_path).map_err(LedgerError::from)?;
149
150            Ok::<_, LedgerError>(signature)
151        })
152        .await
153        .unwrap_or_else(|tokio_join_error| Err(LedgerError::from(tokio_join_error)))?;
154
155        debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object for NEP413");
156        let signature =
157            Signature::from_parts(KeyType::ED25519, signature.as_ref()).map_err(|e| {
158                SignerError::LedgerError(LedgerError::SignatureDeserializationError(e.to_string()))
159            })?;
160
161        Ok(signature)
162    }
163
164    async fn get_secret_key(
165        &self,
166        _signer_id: &AccountId,
167        _public_key: &PublicKey,
168    ) -> Result<SecretKey, SignerError> {
169        warn!(target: LEDGER_SIGNER_TARGET, "Attempted to access secret key, which is not available for Ledger signer");
170        Err(SignerError::SecretKeyIsNotAvailable)
171    }
172
173    #[instrument(skip(self))]
174    fn get_public_key(&self) -> Result<PublicKey, SignerError> {
175        if let Some(public_key) = self.public_key.get() {
176            Ok(public_key.clone())
177        } else {
178            let public_key = near_ledger::get_wallet_id(self.hd_path.clone())
179                .map_err(|_| SignerError::PublicKeyIsNotAvailable)?;
180            let public_key = PublicKey::ED25519(
181                near_api_types::crypto::public_key::ED25519PublicKey(*public_key.as_bytes()),
182            );
183            self.public_key
184                .set(public_key.clone())
185                .map_err(LedgerError::from)?;
186            Ok(public_key)
187        }
188    }
189}