use crate::{
contents::{key_pair::KeyPair, public_key_info::KeyType, Content, ContentEntity, Contents},
locked::LockedWallet,
Error,
};
use chacha20poly1305::{
aead::{Aead, NewAead},
XChaCha20Poly1305, XNonce,
};
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use serde_json::to_string;
use sha3::{Digest, Sha3_256};
#[cfg(feature = "didcomm")]
mod didcomm;
#[derive(Serialize, Deserialize)]
pub struct UnlockedWallet {
#[serde(rename = "@context")]
pub context: Vec<String>,
pub id: String,
#[serde(rename = "type")]
pub wallet_type: Vec<String>,
contents: Contents,
}
impl UnlockedWallet {
pub fn new(id: &str) -> Self {
UnlockedWallet {
context: vec![
"https://www.w3.org/2018/credentials/v1".to_string(),
"https://transmute-industries.github.io/universal-wallet/contexts/wallet-v1.json"
.to_string(),
],
id: id.to_string(),
wallet_type: vec!["UniversalWallet2020".to_string()],
contents: Contents::new(),
}
}
pub fn new_key(
&mut self,
key_type: KeyType,
key_controller: Option<Vec<String>>,
) -> Result<ContentEntity, Error> {
let kp = KeyPair::random_pair(key_type).map_err(|e| Error::Other(Box::new(e)))?;
let pk = kp.public_key.clone();
let key_pair = Content::KeyPair(kp.set_controller(match key_controller {
Some(c) => c,
None => vec![[
self.id.clone(),
base64::encode_config(pk.public_key, base64::URL_SAFE),
]
.join("#")],
}));
self.contents
.import(key_pair)
.map(|(id, content)| content.to_entity(&id).clean())
.ok_or(Error::KeyPairAddFailed)
}
pub fn import_content(&mut self, content: &Content) -> Option<ContentEntity> {
self.contents
.import(content.clone())
.map(|(id, content)| content.to_entity(&id).clean())
}
pub fn set_content(&mut self, cref: &str, content: Content) -> Option<ContentEntity> {
self.contents
.insert(cref, content)
.map(|content| content.to_entity(cref).clean())
}
pub fn replace_content_id(&mut self, old_id: &str, new_id: &str) -> Option<Content> {
self.contents.replace_key(old_id, new_id)
}
pub fn get_key(&self, key_ref: &str) -> Option<ContentEntity> {
self.contents
.get(key_ref)
.and_then(|content| match content {
Content::Entropy(_) => None,
_ => Some(content.to_entity(key_ref).clean()),
})
}
pub fn get_key_by_controller(&self, controller: &str) -> Option<ContentEntity> {
self.contents
.get_by_controller(controller)
.map(|(id, content)| content.to_entity(&id).clean())
}
pub fn set_key_controller(&mut self, key_ref: &str, controller: &str) -> Option<()> {
self.contents.set_key_controller(key_ref, controller)?;
Some(())
}
pub fn get_content_by_controller(&self, controller: &str) -> Option<&Content> {
self.contents.get(controller)
}
pub fn get_keys(&self) -> Vec<ContentEntity> {
self.contents
.get_keys()
.iter()
.map(|(id, content)| content.to_entity(id).clean())
.collect()
}
pub fn sign_raw(&self, key_ref: &str, data: &[u8]) -> Result<Vec<u8>, Error> {
match self.contents.get(key_ref) {
Some(c) => match &c {
Content::KeyPair(k) => k.sign(data),
_ => Err(Error::ContentTypeIncorrect),
},
None => Err(Error::KeyNotFound),
}
}
pub fn decrypt(
&self,
key_ref: &str,
data: &[u8],
aad: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
match self.contents.get(key_ref) {
Some(c) => match &c {
Content::KeyPair(k) => k.decrypt(data, aad),
_ => Err(Error::ContentTypeIncorrect),
},
None => Err(Error::KeyNotFound),
}
}
pub fn ecdh_key_agreement(&self, key_ref: &str, key: &[u8]) -> Result<Vec<u8>, Error> {
match self.contents.get(key_ref) {
Some(c) => match &c {
Content::KeyPair(k) => k.ecdh_key_agreement(key),
_ => Err(Error::ContentTypeIncorrect),
},
None => Err(Error::KeyNotFound),
}
}
pub fn lock(&self, key: &[u8]) -> Result<LockedWallet, Error> {
let mut sha3 = Sha3_256::new();
sha3.update(key);
let pass = sha3.finalize();
let cha_cha = XChaCha20Poly1305::new(&pass);
let mut nonce = get_nonce(); let mut cypher = cha_cha
.encrypt(&nonce, to_string(&self).map_err(Error::Serde)?.as_bytes())
.map_err(Error::AeadCryptoError)?;
cypher.append(&mut nonce.iter_mut().map(|v| *v).collect());
Ok(LockedWallet {
id: self.id.clone(),
ciphertext: cypher,
})
}
}
fn get_nonce() -> XNonce {
let mut base = [0u8; 24];
OsRng.fill_bytes(&mut base);
*XNonce::from_slice(&base)
}