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