1#![deny(missing_docs)]
13#![deny(unsafe_code)]
14
15use std::path::PathBuf;
16use std::{fmt::Display, str::FromStr};
17
18use clap::ValueEnum;
19use essential_signer::Key;
20use essential_signer::PublicKey;
21use essential_types::contract::Contract;
22use essential_types::{Hash, Word};
23use rand::SeedableRng;
24use serde::Serialize;
25
26pub use essential_signer::ed25519_dalek;
27pub use essential_signer::secp256k1;
28pub use essential_signer::Padding;
29pub use essential_signer::Signature;
30
31mod store;
32
33const NAME: &str = "essential-wallet";
34
35#[derive(ValueEnum, Clone, Copy, Debug)]
36pub enum Scheme {
38 Secp256k1,
40 Ed25519,
42}
43
44pub struct Wallet {
48 store: store::Store,
49 #[cfg(feature = "test-utils")]
50 dir: Option<tempfile::TempDir>,
51}
52
53impl Wallet {
54 pub fn new(password: &str, path: PathBuf) -> anyhow::Result<Self> {
56 let mut path = db_dir(Some(path.clone()))?;
57 path.push("accounts.sqlite3");
58 let store = store::Store::new(password, &path)?;
59
60 #[cfg(not(feature = "test-utils"))]
61 let r = Ok(Self { store });
62 #[cfg(feature = "test-utils")]
63 let r = Ok(Self { store, dir: None });
64 r
65 }
66
67 pub fn with_default_path(password: &str) -> anyhow::Result<Self> {
71 let path = db_dir(None)?;
72 Self::new(password, path)
73 }
74
75 #[cfg(feature = "test-utils")]
76 pub fn temp() -> anyhow::Result<Self> {
78 let dir = tempfile::tempdir()?;
79 let path = db_dir(Some(dir.path().to_path_buf()))?;
80 let mut s = Self::new("password", path)?;
81 s.dir = Some(dir);
82 Ok(s)
83 }
84
85 #[cfg(feature = "test-utils")]
86 pub fn insert_key(&mut self, name: &str, key: Key) -> anyhow::Result<()> {
89 match key {
90 Key::Secp256k1(private_key) => {
91 self.store
92 .set_secret(name, Scheme::Secp256k1, private_key.as_ref().as_slice())
93 }
94 Key::Ed25519(_) => todo!("Not supported yet"),
95 }
96 }
97
98 #[cfg(feature = "test-utils")]
99 pub fn generate_private_key(&mut self, scheme: Scheme) -> anyhow::Result<Key> {
102 match scheme {
103 Scheme::Secp256k1 => {
104 let mut rng = rand::rngs::StdRng::from_entropy();
105 let (private_key, _) = secp256k1::generate_keypair(&mut rng);
106 Ok(Key::Secp256k1(private_key))
107 }
108 Scheme::Ed25519 => todo!("Not supported yet"),
109 }
110 }
111
112 pub fn new_key_pair(&mut self, name: &str, scheme: Scheme) -> anyhow::Result<()> {
117 match scheme {
118 Scheme::Secp256k1 => {
119 let mut rng = rand::rngs::StdRng::from_entropy();
120 let (private_key, _) = secp256k1::generate_keypair(&mut rng);
121 self.store
122 .set_secret(name, scheme, private_key.as_ref().as_slice())
123 }
124 Scheme::Ed25519 => todo!("Not supported yet"),
125 }
126 }
127
128 pub fn delete_key_pair(&mut self, name: &str) -> anyhow::Result<()> {
130 self.store.delete_secret(name)
131 }
132
133 pub fn list_names(&mut self) -> anyhow::Result<Vec<String>> {
135 Ok(self.list()?.into_iter().map(|(n, _)| n).collect())
136 }
137
138 pub fn get_public_key(&mut self, name: &str) -> anyhow::Result<PublicKey> {
140 let key = self.name_to_key(name)?;
141 Ok(essential_signer::public_key(&key))
142 }
143
144 pub fn get_private_key(&mut self, name: &str) -> anyhow::Result<Key> {
146 self.name_to_key(name)
147 }
148
149 pub fn sign_contract(
155 &mut self,
156 data: Contract,
157 name: &str,
158 ) -> anyhow::Result<essential_types::contract::SignedContract> {
159 let key = self.name_to_key(name)?;
160 match key {
161 Key::Secp256k1(key) => Ok(essential_sign::contract::sign(data, &key)),
162 Key::Ed25519(_) => Err(anyhow::anyhow!(
163 "Ed25519 not supported for signing contracts. Please use a Secp256k1 key"
164 ))?,
165 }
166 }
167
168 pub fn sign_postcard<T: Serialize>(
173 &mut self,
174 data: &T,
175 name: &str,
176 ) -> anyhow::Result<Signature> {
177 let key = self.name_to_key(name)?;
178 essential_signer::sign_postcard(data, &key)
179 }
180
181 pub fn sign_postcard_with_padding<T: Serialize>(
186 &mut self,
187 data: &T,
188 padding: Padding,
189 name: &str,
190 ) -> anyhow::Result<Signature> {
191 let key = self.name_to_key(name)?;
192 essential_signer::sign_postcard_with_padding(data, padding, &key)
193 }
194
195 pub fn sign_hash(&mut self, data: Hash, name: &str) -> anyhow::Result<Signature> {
197 let key = self.name_to_key(name)?;
198 essential_signer::sign_hash(data, &key)
199 }
200
201 pub fn sign_words(&mut self, data: &[Word], name: &str) -> anyhow::Result<Signature> {
203 let key = self.name_to_key(name)?;
204 essential_signer::sign_words(data, &key)
205 }
206
207 pub fn sign_bytes_with_padding(
211 &mut self,
212 data: Vec<u8>,
213 padding: Padding,
214 name: &str,
215 ) -> anyhow::Result<Signature> {
216 let key = self.name_to_key(name)?;
217 essential_signer::sign_bytes_with_padding(data, padding, &key)
218 }
219
220 pub fn sign_aligned_bytes(&mut self, data: &[u8], name: &str) -> anyhow::Result<Signature> {
225 let key = self.name_to_key(name)?;
226 essential_signer::sign_aligned_bytes(data, &key)
227 }
228
229 pub fn sign_bytes_unchecked(&mut self, data: &[u8], name: &str) -> anyhow::Result<Signature> {
234 let key = self.name_to_key(name)?;
235 essential_signer::sign_bytes_unchecked(data, &key)
236 }
237
238 fn name_to_key(&mut self, name: &str) -> anyhow::Result<Key> {
239 let (private_key, scheme) = self.store.get_secret(name)?;
240 match scheme {
241 Scheme::Secp256k1 => {
242 let private_key = secp256k1::SecretKey::from_slice(private_key.as_slice())?;
243 Ok(Key::Secp256k1(private_key))
244 }
245 Scheme::Ed25519 => todo!("Not supported yet"),
246 }
247 }
248
249 fn list(&mut self) -> anyhow::Result<Vec<(String, Scheme)>> {
251 self.store.list()
252 }
253}
254
255impl Display for Scheme {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 match self {
258 Scheme::Secp256k1 => write!(f, "secp256k1"),
259 Scheme::Ed25519 => write!(f, "ed25519"),
260 }
261 }
262}
263
264impl FromStr for Scheme {
265 type Err = anyhow::Error;
266
267 fn from_str(s: &str) -> Result<Self, Self::Err> {
268 match s {
269 "secp256k1" => Ok(Scheme::Secp256k1),
270 "ed25519" => Ok(Scheme::Ed25519),
271 _ => Err(anyhow::anyhow!("Unknown scheme: {}", s)),
272 }
273 }
274}
275
276fn db_dir(in_path: Option<PathBuf>) -> anyhow::Result<PathBuf> {
277 let path = match in_path {
278 None => {
279 let mut path = dirs::home_dir().unwrap_or_else(|| {
280 dirs::document_dir().unwrap_or_else(|| {
281 dirs::data_local_dir()
282 .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR").to_string()))
283 })
284 });
285 path.push(format!(".{}", NAME));
286 path
287 }
288 Some(path) => path,
289 };
290
291 if !path.is_dir() {
292 std::fs::create_dir_all(&path)?;
293 }
294 Ok(path)
295}