use crate::result::Result;
use serde::{Deserialize, Serialize};
use subxt::sp_core::{crypto::Ss58Codec, sr25519, Pair, U256};
const NONCE_LENGTH: usize = 24;
const SCRYPT_LENGTH: usize = 32 + (3 * 4);
const PKCS8_DIVIDER: [u8; 5] = [161, 35, 3, 33, 0];
const PKCS8_HEADER: [u8; 16] = [48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32];
const SEED_OFFSET: usize = 16;
const PUB_LENGTH: usize = 32;
const SALT_LENGTH: usize = 32;
const SEC_LENGTH: usize = 64;
const DIV_OFFSET: usize = SEED_OFFSET + SEC_LENGTH;
const PUB_OFFSET: usize = DIV_OFFSET + PKCS8_DIVIDER.len();
#[derive(Serialize, Deserialize)]
pub struct EncryptedEncoding {
pub content: [String; 2],
pub r#type: [String; 2],
pub version: String,
}
#[derive(Serialize, Deserialize)]
pub struct Encrypted {
pub encoded: String,
pub encoding: EncryptedEncoding,
pub address: String,
}
impl Encrypted {
fn decoded(&self) -> Result<Vec<u8>> {
Ok(base64::decode(&self.encoded)?)
}
fn decrypt(&self, passphrase: &str) -> Result<Vec<u8>> {
assert_eq!(
self.encoding.r#type.to_owned(),
["scrypt", "xsalsa20-poly1305"].to_owned()
);
let decoded = self.decoded()?;
let password = scrypt_from_slice(passphrase.as_bytes(), &decoded)?;
let encrypted = &decoded[SCRYPT_LENGTH..];
Ok(nacl::secret_box::open(
&encrypted[NONCE_LENGTH..],
&encrypted[..NONCE_LENGTH],
&password[..32],
)?)
}
pub fn create(self, passphrase: &str) -> Result<sr25519::Pair> {
assert!(
self.encoding.version != *"3" || self.encoding.content[0] == "pkcs8",
"Unable to decode non-pkcs8 type, [{}] found",
self.encoding.content.join(",")
);
assert_eq!(
self.encoding.content[1],
"sr25519".to_string(),
"Only supports sr25519 for now."
);
let decrypted = self.decrypt(passphrase)?;
assert_eq!(
&decrypted[0..PKCS8_HEADER.len()],
&PKCS8_HEADER,
"Invalid Pkcs8 header found in body"
);
let divider = &decrypted[DIV_OFFSET..(DIV_OFFSET + PKCS8_DIVIDER.len())];
assert_eq!(
divider, PKCS8_DIVIDER,
"Invalid Pkcs8 divider found in body"
);
let public_key = &decrypted[PUB_OFFSET..(PUB_OFFSET + PUB_LENGTH)];
let public = sr25519::Public::from_ss58check(&self.address)?;
assert_eq!(public.0, public_key);
let secret_key = &decrypted[SEED_OFFSET..SEED_OFFSET + SEC_LENGTH];
let pair = sr25519::Pair::from(schnorrkel::SecretKey::from_ed25519_bytes(secret_key)?);
assert_eq!(public.0, pair.public().0);
Ok(pair)
}
}
fn scrypt_from_slice(passphrase: &[u8], data: &[u8]) -> Result<[u8; 32]> {
let mut salt = [0; 32];
salt.copy_from_slice(&data[..SALT_LENGTH]);
assert_eq!(
U256::from_little_endian(&data[SALT_LENGTH..SALT_LENGTH + 4]),
U256::from(1 << 15),
"Invalid injected scrypt log_n found'"
);
assert_eq!(
U256::from_little_endian(&data[SALT_LENGTH + 4..SALT_LENGTH + 8]),
U256::from(1),
"Invalid injected scrypt parameter r found'"
);
assert_eq!(
U256::from_little_endian(&data[SALT_LENGTH + 8..SALT_LENGTH + 12]),
U256::from(8),
"Invalid injected scrypt parameter t found'"
);
let mut password: [u8; 32] = [0; 32];
let output = nacl::scrypt(passphrase, &salt, 15, 8, 1, 64, &|_: u32| {})?;
password.copy_from_slice(&output[..32]);
Ok(password)
}
#[cfg(test)]
mod tests {
use super::Encrypted;
use std::{fs, path::PathBuf};
#[test]
fn test_can_create_pair_from_json_file() {
let root = env!("CARGO_MANIFEST_DIR");
let json = fs::read(PathBuf::from(root).join("res/pair.json"))
.expect("Read res/pair.json failed.");
let encrypted =
serde_json::from_slice::<Encrypted>(&json).expect("Parse json pair failed.");
encrypted
.create("000000")
.expect("create pair from json file failed");
}
}