gring/
keystore.rs

1// This file is part of Gear.
2//
3// Copyright (C) 2024-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::{Keypair, KeypairInfo, Scrypt, ss58};
20use anyhow::{Result, anyhow};
21use base64::{Engine as _, engine::general_purpose::STANDARD};
22use rand::RngCore;
23// use schnorrkel::Keypair;
24use serde::{Deserialize, Serialize};
25use std::time::{SystemTime, UNIX_EPOCH};
26
27/// JSON keystore for storing sr25519 key pair.
28#[derive(Clone, Debug, Serialize, Deserialize, Default)]
29pub struct Keystore {
30    /// The encoded keypair in base64.
31    pub encoded: String,
32    /// Encoding format.
33    #[serde(default)]
34    pub encoding: Encoding,
35    /// The address of the keypair.
36    pub address: String,
37    /// The meta data of the keypair.
38    #[serde(default)]
39    pub meta: Meta,
40}
41
42impl Keystore {
43    /// The length of nonce.
44    const NONCE_LENGTH: usize = 24;
45
46    /// Encrypt the provided keypair with the given password.
47    pub fn encrypt(keypair: Keypair, passphrase: Option<&[u8]>) -> Result<Self> {
48        let info = KeypairInfo::from(keypair);
49        if let Some(passphrase) = passphrase {
50            Self::encrypt_scrypt(info, passphrase)
51        } else {
52            Self::encrypt_none(info)
53        }
54    }
55
56    /// Encrypt keypair info with scrypt.
57    pub fn encrypt_scrypt(info: KeypairInfo, passphrase: &[u8]) -> Result<Self> {
58        let mut encoded = Vec::new();
59
60        // 1. Get passwd from scrypt
61        let scrypt = Scrypt::default();
62        let passwd = scrypt.passwd(passphrase)?;
63        encoded.extend_from_slice(&scrypt.encode());
64
65        // 2. Generate random nonce
66        let mut nonce = [0; Self::NONCE_LENGTH];
67        rand::thread_rng().fill_bytes(&mut nonce);
68        encoded.extend_from_slice(&nonce);
69
70        // 3. Pack secret box
71        let encrypted = nacl::secret_box::pack(&info.encode(), &nonce, &passwd[..32])
72            .map_err(|e| anyhow!("{e:?}"))?;
73        encoded.extend_from_slice(&encrypted);
74
75        Ok(Self {
76            encoded: STANDARD.encode(&encoded),
77            address: ss58::encode(&info.public)?,
78            encoding: Encoding::scrypt(),
79            ..Default::default()
80        })
81    }
82
83    /// Encrypt keypair without encryption.
84    pub fn encrypt_none(info: KeypairInfo) -> Result<Self> {
85        Ok(Self {
86            encoded: STANDARD.encode(info.encode()),
87            address: ss58::encode(&info.public)?,
88            ..Default::default()
89        })
90    }
91
92    /// Decrypt keypair from encrypted data.
93    pub fn decrypt(&self, passphrase: Option<&[u8]>) -> Result<Keypair> {
94        if let Some(passphrase) = passphrase {
95            if !self.encoding.is_scrypt() {
96                return Err(anyhow!(
97                    "unsupported key deriven function {}.",
98                    self.encoding.ty[0]
99                ));
100            }
101
102            self.decrypt_scrypt(passphrase)
103        } else {
104            if self.encoding.is_xsalsa20_poly1305() {
105                return Err(anyhow!("password required to decode encrypted data."));
106            }
107
108            self.decrypt_none()
109        }
110    }
111
112    /// Decrypt keypair from encrypted data with scrypt.
113    pub fn decrypt_scrypt(&self, passphrase: &[u8]) -> Result<Keypair> {
114        let decoded = self.decoded()?;
115
116        // 1. Get passwd from scrypt
117        let mut encoded_scrypt = [0; Scrypt::ENCODED_LENGTH];
118        encoded_scrypt.copy_from_slice(&decoded[..Scrypt::ENCODED_LENGTH]);
119        let passwd = Scrypt::decode(encoded_scrypt).passwd(passphrase)?;
120
121        // 2. Decrypt the secret key with xsalsa20-poly1305
122        let encrypted = &decoded[Scrypt::ENCODED_LENGTH..];
123        let secret = nacl::secret_box::open(
124            &encrypted[Self::NONCE_LENGTH..],
125            &encrypted[..Self::NONCE_LENGTH],
126            &passwd[..32],
127        )
128        .map_err(|e| anyhow!("{e:?}"))?;
129
130        // 3. Decode the secret key to keypair
131        KeypairInfo::decode(&secret[..KeypairInfo::ENCODED_LENGTH])?.into_keypair()
132    }
133
134    /// Decrypt keypair from data without encryption.
135    pub fn decrypt_none(&self) -> Result<Keypair> {
136        KeypairInfo::decode(&self.decoded()?)?.into_keypair()
137    }
138
139    /// Returns self with the given name in meta.
140    pub fn with_name(mut self, name: &str) -> Self {
141        self.meta.name = name.to_owned();
142        self
143    }
144
145    /// Decode the encoded keypair info with base64.
146    fn decoded(&self) -> Result<Vec<u8>> {
147        STANDARD.decode(&self.encoded).map_err(Into::into)
148    }
149}
150
151/// Encoding format for the keypair.
152#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
153pub struct Encoding {
154    /// The content of the keystore.
155    ///
156    /// - The first element is the standard.
157    /// - The second element is the key algorithm.
158    pub content: (String, String),
159
160    /// The type of the keystore.
161    ///
162    /// - The first element is the key deriven function of the keystore.
163    ///   - if the first element is `none`, there will be no cipher following.
164    /// - The second element is the encryption cipher of the keystore.
165    #[serde(rename = "type")]
166    pub ty: Vec<String>,
167
168    /// The version of the keystore.
169    pub version: String,
170}
171
172impl Encoding {
173    /// None encoding format.
174    pub fn none() -> Self {
175        Self {
176            content: ("pkcs8".into(), "sr25519".into()),
177            ty: vec!["none".into()],
178            version: "3".to_string(),
179        }
180    }
181
182    /// Recommend encoding format.
183    pub fn scrypt() -> Self {
184        Self {
185            content: ("pkcs8".into(), "sr25519".into()),
186            ty: vec!["scrypt".into(), "xsalsa20-poly1305".into()],
187            ..Default::default()
188        }
189    }
190
191    /// Check if is encoding with scrypt.
192    pub fn is_scrypt(&self) -> bool {
193        self.ty.first() == Some(&"scrypt".into())
194    }
195
196    /// Check if the cipher is xsalsa20-poly1305.
197    pub fn is_xsalsa20_poly1305(&self) -> bool {
198        self.ty.get(1) == Some(&"xsalsa20-poly1305".into())
199    }
200}
201
202impl Default for Encoding {
203    fn default() -> Self {
204        Self::none()
205    }
206}
207
208/// The metadata of the key pair.
209#[derive(Clone, Debug, Serialize, Deserialize)]
210pub struct Meta {
211    /// The name of the key pair.
212    pub name: String,
213
214    /// The timestamp when the key pair is created in milliseconds.
215    #[serde(rename = "whenCreated")]
216    pub when_created: u128,
217}
218
219impl Default for Meta {
220    fn default() -> Self {
221        Self {
222            name: "".into(),
223            when_created: SystemTime::now()
224                .duration_since(UNIX_EPOCH)
225                .expect("time went backwards")
226                .as_millis(),
227        }
228    }
229}