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