near_kit/client/
keyring_signer.rs1use crate::client::signer::{InMemorySigner, Signer, SigningKey};
48use crate::error::{Error, KeyStoreError, ParseKeyError};
49use crate::types::{AccountId, PublicKey, SecretKey};
50
51#[derive(Clone)]
71pub struct KeyringSigner {
72 inner: InMemorySigner,
73}
74
75impl KeyringSigner {
76 pub fn new(
104 network: impl AsRef<str>,
105 account_id: impl AsRef<str>,
106 public_key: impl AsRef<str>,
107 ) -> Result<Self, Error> {
108 let network = network.as_ref();
109 let account_id_str = account_id.as_ref();
110 let public_key_str = public_key.as_ref();
111
112 let account_id: AccountId = account_id_str.parse()?;
114 let public_key: PublicKey = public_key_str.parse()?;
115
116 let service_name = format!("near-{}-{}", network, account_id_str);
120 let username = format!("{}:{}", account_id_str, public_key_str);
121
122 let entry = keyring::Entry::new(&service_name, &username).map_err(|e| {
123 Error::KeyStore(KeyStoreError::Platform(format!(
124 "Failed to access keyring: {}. On Linux, ensure a Secret Service daemon \
125 (gnome-keyring, kwallet) is running.",
126 e
127 )))
128 })?;
129
130 let password = entry.get_password().map_err(|e| match e {
131 keyring::Error::NoEntry => {
132 Error::KeyStore(KeyStoreError::KeyNotFound(account_id.clone()))
133 }
134 _ => Error::KeyStore(KeyStoreError::Platform(format!(
135 "Failed to read from keyring: {}",
136 e
137 ))),
138 })?;
139
140 let secret_key = parse_keyring_credential(&password, &account_id, &public_key)?;
142
143 let inner = InMemorySigner::from_secret_key(account_id, secret_key);
145
146 if inner.public_key() != &public_key {
148 return Err(Error::KeyStore(KeyStoreError::InvalidFormat(format!(
149 "Public key mismatch: stored key has {}, but requested {}",
150 inner.public_key(),
151 public_key
152 ))));
153 }
154
155 Ok(Self { inner })
156 }
157
158 pub fn public_key(&self) -> &PublicKey {
160 self.inner.public_key()
161 }
162
163 pub fn into_inner(self) -> InMemorySigner {
165 self.inner
166 }
167}
168
169impl std::fmt::Debug for KeyringSigner {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 f.debug_struct("KeyringSigner")
172 .field("account_id", self.inner.account_id())
173 .field("public_key", self.inner.public_key())
174 .finish()
175 }
176}
177
178impl Signer for KeyringSigner {
179 fn account_id(&self) -> &AccountId {
180 self.inner.account_id()
181 }
182
183 fn key(&self) -> SigningKey {
184 self.inner.key()
185 }
186}
187
188fn parse_keyring_credential(
215 json_str: &str,
216 account_id: &AccountId,
217 _public_key: &PublicKey,
218) -> Result<SecretKey, Error> {
219 let value: serde_json::Value = serde_json::from_str(json_str).map_err(|e| {
221 Error::KeyStore(KeyStoreError::InvalidFormat(format!(
222 "Invalid JSON in keyring credential for {}: {}",
223 account_id, e
224 )))
225 })?;
226
227 let private_key_str = value
229 .get("private_key")
230 .and_then(|v| v.as_str())
231 .ok_or_else(|| {
232 Error::KeyStore(KeyStoreError::InvalidFormat(format!(
233 "Missing 'private_key' field in keyring credential for {}",
234 account_id
235 )))
236 })?;
237
238 let secret_key: SecretKey = private_key_str
240 .parse()
241 .map_err(|e: ParseKeyError| Error::KeyStore(KeyStoreError::InvalidKey(e)))?;
242
243 Ok(secret_key)
244}
245
246#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_parse_full_format() {
256 let json = r#"{
257 "seed_phrase_hd_path": "m/44'/397'/0'",
258 "master_seed_phrase": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
259 "implicit_account_id": "c4f5941e81e071c2fd1dae2e71fd3d859d462484391d9a90bf219211dcbb320f",
260 "public_key": "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847",
261 "private_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
262 }"#;
263
264 let account_id: AccountId = "alice.testnet".parse().unwrap();
265 let public_key: PublicKey = "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
266 .parse()
267 .unwrap();
268
269 let secret_key = parse_keyring_credential(json, &account_id, &public_key).unwrap();
270
271 assert!(secret_key.to_string().starts_with("ed25519:"));
273 }
274
275 #[test]
276 fn test_parse_simple_format() {
277 let json = r#"{
278 "public_key": "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847",
279 "private_key": "ed25519:3D4YudUahN1nawWogh8pAKSj92sUNMdbZGjn7kERKzYoTy8tnFQuwoGUC51DowKqorvkr2pytJSnwuSbsNVfqygr"
280 }"#;
281
282 let account_id: AccountId = "alice.testnet".parse().unwrap();
283 let public_key: PublicKey = "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
284 .parse()
285 .unwrap();
286
287 let secret_key = parse_keyring_credential(json, &account_id, &public_key).unwrap();
288
289 assert!(secret_key.to_string().starts_with("ed25519:"));
291 }
292
293 #[test]
294 fn test_parse_missing_private_key() {
295 let json = r#"{
296 "public_key": "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
297 }"#;
298
299 let account_id: AccountId = "alice.testnet".parse().unwrap();
300 let public_key: PublicKey = "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
301 .parse()
302 .unwrap();
303
304 let result = parse_keyring_credential(json, &account_id, &public_key);
305 assert!(result.is_err());
306
307 let err = result.unwrap_err();
308 assert!(err.to_string().contains("Missing 'private_key' field"));
309 }
310
311 #[test]
312 fn test_parse_invalid_json() {
313 let json = "not valid json";
314
315 let account_id: AccountId = "alice.testnet".parse().unwrap();
316 let public_key: PublicKey = "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
317 .parse()
318 .unwrap();
319
320 let result = parse_keyring_credential(json, &account_id, &public_key);
321 assert!(result.is_err());
322
323 let err = result.unwrap_err();
324 assert!(err.to_string().contains("Invalid JSON"));
325 }
326
327 #[test]
328 fn test_parse_invalid_key_format() {
329 let json = r#"{
330 "public_key": "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847",
331 "private_key": "not-a-valid-key"
332 }"#;
333
334 let account_id: AccountId = "alice.testnet".parse().unwrap();
335 let public_key: PublicKey = "ed25519:DcA2MzgpJbrUATQLLceocVckhhAqrkingax4oJ9kZ847"
336 .parse()
337 .unwrap();
338
339 let result = parse_keyring_credential(json, &account_id, &public_key);
340 assert!(result.is_err());
341 }
342}