1use crate::{
22 utils::{self, print_from_public, print_from_uri},
23 with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, NetworkSchemeFlag, OutputTypeFlag,
24};
25use clap::Parser;
26use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
27use std::str::FromStr;
28
29#[derive(Debug, Parser)]
31#[command(
32 name = "inspect",
33 about = "Gets a public key and a SS58 address from the provided Secret URI"
34)]
35pub struct InspectKeyCmd {
36 uri: Option<String>,
43
44 #[arg(long)]
46 public: bool,
47
48 #[allow(missing_docs)]
49 #[clap(flatten)]
50 pub keystore_params: KeystoreParams,
51
52 #[allow(missing_docs)]
53 #[clap(flatten)]
54 pub network_scheme: NetworkSchemeFlag,
55
56 #[allow(missing_docs)]
57 #[clap(flatten)]
58 pub output_scheme: OutputTypeFlag,
59
60 #[allow(missing_docs)]
61 #[clap(flatten)]
62 pub crypto_scheme: CryptoSchemeFlag,
63
64 #[arg(long, conflicts_with = "public")]
72 pub expect_public: Option<String>,
73}
74
75impl InspectKeyCmd {
76 pub fn run(&self) -> Result<(), Error> {
78 let uri = utils::read_uri(self.uri.as_ref())?;
79 let password = self.keystore_params.read_password()?;
80
81 if self.public {
82 with_crypto_scheme!(
83 self.crypto_scheme.scheme,
84 print_from_public(
85 &uri,
86 self.network_scheme.network,
87 self.output_scheme.output_type,
88 )
89 )?;
90 } else {
91 if let Some(ref expect_public) = self.expect_public {
92 with_crypto_scheme!(
93 self.crypto_scheme.scheme,
94 expect_public_from_phrase(expect_public, &uri, password.as_ref())
95 )?;
96 }
97
98 with_crypto_scheme!(
99 self.crypto_scheme.scheme,
100 print_from_uri(
101 &uri,
102 password,
103 self.network_scheme.network,
104 self.output_scheme.output_type,
105 )
106 );
107 }
108
109 Ok(())
110 }
111}
112
113fn expect_public_from_phrase<Pair: sp_core::Pair>(
120 expect_public: &str,
121 suri: &str,
122 password: Option<&SecretString>,
123) -> Result<(), Error> {
124 let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?;
125 let expected_public = if let Some(public) = expect_public.strip_prefix("0x") {
126 let hex_public = array_bytes::hex2bytes(public)
127 .map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?;
128 Pair::Public::try_from(&hex_public)
129 .map_err(|_| format!("Invalid expected public key: `{}`", expect_public))?
130 } else {
131 Pair::Public::from_string_with_version(expect_public)
132 .map_err(|_| format!("Invalid expected account id: `{}`", expect_public))?
133 .0
134 };
135
136 let pair = Pair::from_string_with_seed(
137 secret_uri.phrase.expose_secret().as_str(),
138 password
139 .or_else(|| secret_uri.password.as_ref())
140 .map(|p| p.expose_secret().as_str()),
141 )
142 .map_err(|_| format!("Invalid secret uri: {}", suri))?
143 .0;
144
145 if pair.public() == expected_public {
146 Ok(())
147 } else {
148 Err(format!("Expected public ({}) key does not match.", expect_public).into())
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use sp_core::crypto::{ByteArray, Pair};
156 use sp_runtime::traits::IdentifyAccount;
157
158 #[test]
159 fn inspect() {
160 let words =
161 "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
162 let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
163
164 let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]);
165 assert!(inspect.run().is_ok());
166
167 let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]);
168 assert!(inspect.run().is_ok());
169 }
170
171 #[test]
172 fn inspect_public_key() {
173 let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
174
175 let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]);
176 assert!(inspect.run().is_ok());
177 }
178
179 #[test]
180 fn inspect_with_expected_public_key() {
181 let check_cmd = |seed, expected_public, success| {
182 let inspect = InspectKeyCmd::parse_from(&[
183 "inspect-key",
184 "--expect-public",
185 expected_public,
186 seed,
187 ]);
188 let res = inspect.run();
189
190 if success {
191 assert!(res.is_ok());
192 } else {
193 assert!(res.unwrap_err().to_string().contains(&format!(
194 "Expected public ({}) key does not match.",
195 expected_public
196 )));
197 }
198 };
199
200 let seed =
201 "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
202 let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
203 let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None)
204 .expect("Valid")
205 .0
206 .public();
207 let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice());
208 let valid_accountid = format!("{}", valid_public.into_account());
209
210 check_cmd(seed, invalid_public, false);
212
213 check_cmd(seed, &valid_public_hex, true);
215 check_cmd(seed, &valid_accountid, true);
216
217 let password = "test12245";
218 let seed_with_password = format!("{}///{}", seed, password);
219 let valid_public_with_password =
220 sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password))
221 .expect("Valid")
222 .0
223 .public();
224 let valid_public_hex_with_password =
225 array_bytes::bytes2hex("0x", valid_public_with_password.as_slice());
226 let valid_accountid_with_password =
227 format!("{}", &valid_public_with_password.into_account());
228
229 check_cmd(&seed_with_password, &valid_public_hex, false);
231 check_cmd(&seed_with_password, &valid_accountid, false);
232
233 check_cmd(&seed_with_password, &valid_public_hex_with_password, true);
234 check_cmd(&seed_with_password, &valid_accountid_with_password, true);
235
236 let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password);
237
238 let valid_public_with_password_and_derivation =
239 sp_core::sr25519::Pair::from_string_with_seed(
240 &seed_with_password_and_derivation,
241 Some(password),
242 )
243 .expect("Valid")
244 .0
245 .public();
246 let valid_public_hex_with_password_and_derivation =
247 array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
248
249 check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true);
251 check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true);
252
253 check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);
255 check_cmd(&seed_with_password_and_derivation, &valid_accountid, false);
256
257 check_cmd(
259 &seed_with_password_and_derivation,
260 &valid_public_hex_with_password_and_derivation,
261 false,
262 );
263 }
264}