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