Integrate process wasm/ts

This commit is contained in:
Sosthene00 2024-04-03 10:02:05 +02:00
parent fbddacdeb8
commit c209bee651
11 changed files with 923 additions and 905 deletions

View File

@ -1,447 +0,0 @@
use std::collections::HashMap;
use anyhow::{Error, Result};
use sp_backend::{
bitcoin::{
consensus::serde::hex,
hex::DisplayHex,
key::constants::SECRET_KEY_SIZE,
secp256k1::{ecdh::SharedSecret, SecretKey},
Txid,
},
silentpayments::sending::SilentPaymentAddress,
};
use wasm_bindgen::JsValue;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use aes::cipher::generic_array::GenericArray;
use aes::{
cipher::consts::{U32, U8},
Aes256,
};
use aes_gcm::{
aead::{Aead, AeadInPlace, KeyInit, Nonce},
AeadCore, Aes256Gcm, AesGcm, Key, TagSize,
};
use rand::{thread_rng, RngCore};
const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2;
const THIRTYTWO: usize = 32;
pub struct HalfKey([u8; HALFKEYSIZE]);
impl TryFrom<Vec<u8>> for HalfKey {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> std::prelude::v1::Result<Self, Error> {
if value.len() == HALFKEYSIZE {
let mut buf = [0u8; HALFKEYSIZE];
buf.copy_from_slice(&value);
Ok(HalfKey(buf))
} else {
Err(Error::msg("Invalid length for HalfKey"))
}
}
}
impl HalfKey {
pub fn as_slice(&self) -> &[u8] {
&self.0
}
pub fn to_inner(&self) -> Vec<u8> {
self.0.to_vec()
}
}
pub enum Purpose {
Login,
ThirtyTwoBytes,
}
pub type CipherText = Vec<u8>;
pub type EncryptedKey = Vec<u8>;
pub struct Aes256Decryption {
pub purpose: Purpose,
cipher_text: CipherText,
aes_key: [u8; 32],
nonce: [u8; 12],
}
impl Aes256Decryption {
pub fn new(
purpose: Purpose,
cipher_text: CipherText,
encrypted_aes_key: Vec<u8>, // If shared_secret is none this is actually the aes_key
shared_secret: Option<SharedSecret>, // We don't need that for certain purpose, like Login
) -> Result<Self> {
let mut aes_key = [0u8; 32];
if let Some(shared_secret) = shared_secret {
if encrypted_aes_key.len() <= 12 {
return Err(Error::msg("encrypted_aes_key is shorter than nonce length"));
} // Actually we could probably test that if the remnant is not a multiple of 32, something's wrong
// take the first 12 bytes form encrypted_aes_key as nonce
let (decrypt_key_nonce, encrypted_key) = encrypted_aes_key.split_at(12);
// decrypt key with shared_secret obtained from transaction
let decrypt_key_cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref())
.map_err(|e| Error::msg(format!("{}", e)))?;
let aes_key_plain = decrypt_key_cipher
.decrypt(decrypt_key_nonce.into(), encrypted_key)
.map_err(|e| Error::msg(format!("{}", e)))?;
if aes_key_plain.len() != 32 {
return Err(Error::msg("Invalid length for decrypted key"));
}
aes_key.copy_from_slice(&aes_key_plain);
} else {
if encrypted_aes_key.len() != 32 {
return Err(Error::msg("Invalid length for decrypted key"));
}
aes_key.copy_from_slice(&encrypted_aes_key);
}
if cipher_text.len() <= 12 {
return Err(Error::msg("cipher_text is shorter than nonce length"));
}
let (message_nonce, message_cipher) = cipher_text.split_at(12);
let mut nonce = [0u8; 12];
nonce.copy_from_slice(message_nonce);
Ok(Self {
purpose,
cipher_text: message_cipher.to_vec(),
aes_key,
nonce,
})
}
pub fn decrypt_with_key(&self) -> Result<Vec<u8>> {
match self.purpose {
Purpose::Login => {
let half_key = self.decrypt_login()?;
Ok(half_key.to_inner())
}
Purpose::ThirtyTwoBytes => {
let thirty_two_buf = self.decrypt_thirty_two()?;
Ok(thirty_two_buf.to_vec())
}
}
}
fn decrypt_login(&self) -> Result<HalfKey> {
let cipher = Aes256Gcm::new(&self.aes_key.into());
let plain = cipher
.decrypt(&self.nonce.into(), &*self.cipher_text)
.map_err(|e| Error::msg(format!("{}", e)))?;
if plain.len() != SECRET_KEY_SIZE / 2 {
return Err(Error::msg("Plain text of invalid lenght for a login"));
}
let mut key_half = [0u8; SECRET_KEY_SIZE / 2];
key_half.copy_from_slice(&plain);
Ok(HalfKey(key_half))
}
fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> {
let cipher = Aes256Gcm::new(&self.aes_key.into());
let plain = cipher
.decrypt(&self.nonce.into(), &*self.cipher_text)
.map_err(|e| Error::msg(format!("{}", e)))?;
if plain.len() != THIRTYTWO {
return Err(Error::msg("Plain text of invalid length, should be 32"));
}
let mut thirty_two = [0u8; THIRTYTWO];
thirty_two.copy_from_slice(&plain);
Ok(thirty_two)
}
}
pub struct Aes256Encryption {
pub purpose: Purpose,
plaintext: Vec<u8>,
aes_key: [u8; 32],
nonce: [u8; 12],
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>,
}
impl Aes256Encryption {
pub fn new(purpose: Purpose, plaintext: Vec<u8>) -> Result<Self> {
let mut rng = thread_rng();
let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into();
let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into();
Self::import_key(purpose, plaintext, aes_key, nonce)
}
pub fn set_shared_secret(
&mut self,
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>,
) {
self.shared_secrets = shared_secrets;
}
pub fn encrypt_keys_with_shared_secrets(
&self,
) -> Result<HashMap<SilentPaymentAddress, EncryptedKey>> {
let mut res = HashMap::new();
let mut rng = thread_rng();
for (_, sp_address2shared_secret) in self.shared_secrets.iter() {
for (sp_address, shared_secret) in sp_address2shared_secret {
let cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref())
.map_err(|e| Error::msg(format!("{}", e)))?;
let nonce = Aes256Gcm::generate_nonce(&mut rng);
let encrypted_key = cipher
.encrypt(&nonce, self.aes_key.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut ciphertext = Vec::<u8>::with_capacity(nonce.len() + encrypted_key.len());
ciphertext.extend(nonce);
ciphertext.extend(encrypted_key);
res.insert(sp_address.to_owned(), ciphertext);
}
}
Ok(res)
}
pub fn import_key(
purpose: Purpose,
plaintext: Vec<u8>,
aes_key: [u8; 32],
nonce: [u8; 12],
) -> Result<Self> {
if plaintext.len() == 0 {
return Err(Error::msg("Can't create encryption for an empty message"));
}
Ok(Self {
purpose,
plaintext,
aes_key,
nonce,
shared_secrets: HashMap::new(),
})
}
pub fn encrypt_with_aes_key(&self) -> Result<CipherText> {
match self.purpose {
Purpose::Login => self.encrypt_login(),
Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(),
}
}
fn encrypt_login(&self) -> Result<CipherText> {
let half_key: HalfKey = self.plaintext.clone().try_into()?;
let cipher = Aes256Gcm::new(&self.aes_key.into());
let cipher_text = cipher
.encrypt(&self.nonce.into(), half_key.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
res.extend_from_slice(&self.nonce);
res.extend_from_slice(&cipher_text);
Ok(res)
}
fn encrypt_thirty_two(&self) -> Result<CipherText> {
if self.plaintext.len() != 32 {
return Err(Error::msg("Invalid length, should be 32"));
}
let mut thirty_two = [0u8; 32];
thirty_two.copy_from_slice(&self.plaintext);
let cipher = Aes256Gcm::new(&self.aes_key.into());
let cipher_text = cipher
.encrypt(&self.nonce.into(), thirty_two.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
log::info!("{}", cipher_text.len());
res.extend_from_slice(&self.nonce);
res.extend_from_slice(&cipher_text);
Ok(res)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz";
const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08";
const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b";
const ALICE_SHARED_SECRET: &str =
"ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106";
const BOB_SHARED_SECRET: &str =
"15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521";
#[test]
fn new_aes_empty_plaintext() {
let plaintext = Vec::new();
let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext);
assert!(aes_enc.is_err());
}
#[test]
fn aes_encrypt_login_invalid_length() {
let plaintext = "example";
let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec());
assert!(aes_enc_short.is_ok());
let cipher = aes_enc_short.unwrap().encrypt_with_aes_key();
assert!(cipher.is_err());
let plaintext = [1u8; 64];
let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec());
assert!(aes_enc_long.is_ok());
let cipher = aes_enc_long.unwrap().encrypt_with_aes_key();
assert!(cipher.is_err());
}
#[test]
fn aes_encrypt_login() {
let plaintext = [1u8; HALFKEYSIZE];
let aes_key = Aes256Gcm::generate_key(&mut thread_rng());
let nonce = Aes256Gcm::generate_nonce(&mut thread_rng());
let aes_enc = Aes256Encryption::import_key(
Purpose::Login,
plaintext.to_vec(),
aes_key.into(),
nonce.into(),
);
assert!(aes_enc.is_ok());
let cipher = aes_enc.unwrap().encrypt_with_aes_key();
assert!(cipher.is_ok());
let mut plain_key = [0u8; 32];
plain_key.copy_from_slice(&aes_key.to_vec());
let aes_dec =
Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key.to_vec(), None);
assert!(aes_dec.is_ok());
}
#[test]
fn aes_encrypt_key() {
let plaintext = [1u8; HALFKEYSIZE];
let mut aes_enc = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()).unwrap();
let mut shared_secrets: HashMap<Txid, _> = HashMap::new();
let mut sp_address2shared_secrets: HashMap<SilentPaymentAddress, SharedSecret> =
HashMap::new();
sp_address2shared_secrets.insert(
ALICE_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(),
);
shared_secrets.insert(
Txid::from_str(TRANSACTION).unwrap(),
sp_address2shared_secrets,
);
aes_enc.set_shared_secret(shared_secrets);
let sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets();
assert!(sp_address2encrypted_keys.is_ok());
let encrypted_key = sp_address2encrypted_keys
.unwrap()
.get(&ALICE_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
assert!(ciphertext.is_ok());
let aes_dec = Aes256Decryption::new(
Purpose::Login,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()),
);
assert!(aes_dec.is_ok());
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.is_ok());
assert!(retrieved_plain.unwrap() == plaintext);
}
#[test]
fn aes_encrypt_key_many() {
let plaintext = [1u8; THIRTYTWO];
let mut aes_enc =
Aes256Encryption::new(Purpose::ThirtyTwoBytes, plaintext.to_vec()).unwrap();
let mut shared_secrets: HashMap<Txid, _> = HashMap::new();
let mut sp_address2shared_secrets: HashMap<SilentPaymentAddress, SharedSecret> =
HashMap::new();
sp_address2shared_secrets.insert(
ALICE_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(),
);
sp_address2shared_secrets.insert(
BOB_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(BOB_SHARED_SECRET).unwrap(),
);
shared_secrets.insert(
Txid::from_str(TRANSACTION).unwrap(),
sp_address2shared_secrets,
);
aes_enc.set_shared_secret(shared_secrets);
let mut sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets();
assert!(sp_address2encrypted_keys.is_ok());
// Alice
let encrypted_key = sp_address2encrypted_keys
.as_mut()
.unwrap()
.get(&ALICE_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
let aes_dec = Aes256Decryption::new(
Purpose::ThirtyTwoBytes,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()),
);
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.unwrap() == plaintext);
// Bob
let encrypted_key = sp_address2encrypted_keys
.unwrap()
.get(&BOB_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
let aes_dec = Aes256Decryption::new(
Purpose::ThirtyTwoBytes,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(BOB_SHARED_SECRET).unwrap()),
);
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.unwrap() == plaintext);
}
}

View File

@ -173,19 +173,49 @@ pub fn get_processes() -> ApiResult<get_process_return> {
//instances of process
let process1 = Process {
id: String::from("1"),
id: 1,
name: String::from("CREATE_ID"),
version: String::from("1.0"),
gestionnaires: vec![member1.clone(), member2.clone()],
members: vec![member1.clone(), member2.clone()],
html: crate::process::HTML_CREATE_ID.to_owned(),
style: crate::process::CSS.to_owned(),
script: "".to_owned(),
};
let process2 = Process {
id: String::from("2"),
version: String::from("2.0"),
gestionnaires: vec![member2.clone(), member3.clone()],
id: 2,
name: String::from("UPDATE_ID"),
version: String::from("1.0"),
members: vec![member1.clone(), member2.clone()],
html: crate::process::HTML_UPDATE_ID.to_owned(),
style: crate::process::CSS.to_owned(),
script: "".to_owned(),
};
let process3 = Process {
id: String::from("3"),
id: 3,
name: String::from("RECOVER"),
version: String::from("1.0"),
gestionnaires: vec![member3.clone(), member1.clone()],
members: vec![member1.clone(), member2.clone()],
html: crate::process::HTML_RECOVER.to_owned(),
style: crate::process::CSS.to_owned(),
script: "".to_owned(),
};
let process4 = Process {
id: 4,
name: String::from("REVOKE_IMAGE"),
version: String::from("1.0"),
members: vec![member1.clone(), member2.clone()],
html: crate::process::HTML_REVOKE_IMAGE.to_owned(),
style: crate::process::CSS.to_owned(),
script: "".to_owned(),
};
let process5 = Process {
id: 5,
name: String::from("REVOKE"),
version: String::from("1.0"),
members: vec![member1.clone(), member2.clone()],
html: crate::process::HTML_REVOKE.to_owned(),
style: crate::process::CSS.to_owned(),
script: "".to_owned(),
};
// vec with the instances of processes
@ -193,6 +223,8 @@ pub fn get_processes() -> ApiResult<get_process_return> {
data_process.push(process1);
data_process.push(process2);
data_process.push(process3);
data_process.push(process4);
data_process.push(process5);
Ok(get_process_return(data_process))
}

View File

@ -1,9 +1,447 @@
use aes_gcm;
use std::collections::HashMap;
pub enum KeyType {
Aes256GcmIv96BitKey([u8;32])
use anyhow::{Error, Result};
use sp_backend::{
bitcoin::{
consensus::serde::hex,
hex::DisplayHex,
key::constants::SECRET_KEY_SIZE,
secp256k1::{ecdh::SharedSecret, SecretKey},
Txid,
},
silentpayments::sending::SilentPaymentAddress,
};
use wasm_bindgen::JsValue;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use aes::cipher::generic_array::GenericArray;
use aes::{
cipher::consts::{U32, U8},
Aes256,
};
use aes_gcm::{
aead::{Aead, AeadInPlace, KeyInit, Nonce},
AeadCore, Aes256Gcm, AesGcm, Key, TagSize,
};
use rand::{thread_rng, RngCore};
const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2;
const THIRTYTWO: usize = 32;
pub struct HalfKey([u8; HALFKEYSIZE]);
impl TryFrom<Vec<u8>> for HalfKey {
type Error = anyhow::Error;
fn try_from(value: Vec<u8>) -> std::prelude::v1::Result<Self, Error> {
if value.len() == HALFKEYSIZE {
let mut buf = [0u8; HALFKEYSIZE];
buf.copy_from_slice(&value);
Ok(HalfKey(buf))
} else {
Err(Error::msg("Invalid length for HalfKey"))
}
}
}
pub struct EncryptionKey {
sk: KeyType
impl HalfKey {
pub fn as_slice(&self) -> &[u8] {
&self.0
}
pub fn to_inner(&self) -> Vec<u8> {
self.0.to_vec()
}
}
pub enum Purpose {
Login,
ThirtyTwoBytes,
}
pub type CipherText = Vec<u8>;
pub type EncryptedKey = Vec<u8>;
pub struct Aes256Decryption {
pub purpose: Purpose,
cipher_text: CipherText,
aes_key: [u8; 32],
nonce: [u8; 12],
}
impl Aes256Decryption {
pub fn new(
purpose: Purpose,
cipher_text: CipherText,
encrypted_aes_key: Vec<u8>, // If shared_secret is none this is actually the aes_key
shared_secret: Option<SharedSecret>, // We don't need that for certain purpose, like Login
) -> Result<Self> {
let mut aes_key = [0u8; 32];
if let Some(shared_secret) = shared_secret {
if encrypted_aes_key.len() <= 12 {
return Err(Error::msg("encrypted_aes_key is shorter than nonce length"));
} // Actually we could probably test that if the remnant is not a multiple of 32, something's wrong
// take the first 12 bytes form encrypted_aes_key as nonce
let (decrypt_key_nonce, encrypted_key) = encrypted_aes_key.split_at(12);
// decrypt key with shared_secret obtained from transaction
let decrypt_key_cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref())
.map_err(|e| Error::msg(format!("{}", e)))?;
let aes_key_plain = decrypt_key_cipher
.decrypt(decrypt_key_nonce.into(), encrypted_key)
.map_err(|e| Error::msg(format!("{}", e)))?;
if aes_key_plain.len() != 32 {
return Err(Error::msg("Invalid length for decrypted key"));
}
aes_key.copy_from_slice(&aes_key_plain);
} else {
if encrypted_aes_key.len() != 32 {
return Err(Error::msg("Invalid length for decrypted key"));
}
aes_key.copy_from_slice(&encrypted_aes_key);
}
if cipher_text.len() <= 12 {
return Err(Error::msg("cipher_text is shorter than nonce length"));
}
let (message_nonce, message_cipher) = cipher_text.split_at(12);
let mut nonce = [0u8; 12];
nonce.copy_from_slice(message_nonce);
Ok(Self {
purpose,
cipher_text: message_cipher.to_vec(),
aes_key,
nonce,
})
}
pub fn decrypt_with_key(&self) -> Result<Vec<u8>> {
match self.purpose {
Purpose::Login => {
let half_key = self.decrypt_login()?;
Ok(half_key.to_inner())
}
Purpose::ThirtyTwoBytes => {
let thirty_two_buf = self.decrypt_thirty_two()?;
Ok(thirty_two_buf.to_vec())
}
}
}
fn decrypt_login(&self) -> Result<HalfKey> {
let cipher = Aes256Gcm::new(&self.aes_key.into());
let plain = cipher
.decrypt(&self.nonce.into(), &*self.cipher_text)
.map_err(|e| Error::msg(format!("{}", e)))?;
if plain.len() != SECRET_KEY_SIZE / 2 {
return Err(Error::msg("Plain text of invalid lenght for a login"));
}
let mut key_half = [0u8; SECRET_KEY_SIZE / 2];
key_half.copy_from_slice(&plain);
Ok(HalfKey(key_half))
}
fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> {
let cipher = Aes256Gcm::new(&self.aes_key.into());
let plain = cipher
.decrypt(&self.nonce.into(), &*self.cipher_text)
.map_err(|e| Error::msg(format!("{}", e)))?;
if plain.len() != THIRTYTWO {
return Err(Error::msg("Plain text of invalid length, should be 32"));
}
let mut thirty_two = [0u8; THIRTYTWO];
thirty_two.copy_from_slice(&plain);
Ok(thirty_two)
}
}
pub struct Aes256Encryption {
pub purpose: Purpose,
plaintext: Vec<u8>,
aes_key: [u8; 32],
nonce: [u8; 12],
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>,
}
impl Aes256Encryption {
pub fn new(purpose: Purpose, plaintext: Vec<u8>) -> Result<Self> {
let mut rng = thread_rng();
let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into();
let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into();
Self::import_key(purpose, plaintext, aes_key, nonce)
}
pub fn set_shared_secret(
&mut self,
shared_secrets: HashMap<Txid, HashMap<SilentPaymentAddress, SharedSecret>>,
) {
self.shared_secrets = shared_secrets;
}
pub fn encrypt_keys_with_shared_secrets(
&self,
) -> Result<HashMap<SilentPaymentAddress, EncryptedKey>> {
let mut res = HashMap::new();
let mut rng = thread_rng();
for (_, sp_address2shared_secret) in self.shared_secrets.iter() {
for (sp_address, shared_secret) in sp_address2shared_secret {
let cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref())
.map_err(|e| Error::msg(format!("{}", e)))?;
let nonce = Aes256Gcm::generate_nonce(&mut rng);
let encrypted_key = cipher
.encrypt(&nonce, self.aes_key.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut ciphertext = Vec::<u8>::with_capacity(nonce.len() + encrypted_key.len());
ciphertext.extend(nonce);
ciphertext.extend(encrypted_key);
res.insert(sp_address.to_owned(), ciphertext);
}
}
Ok(res)
}
pub fn import_key(
purpose: Purpose,
plaintext: Vec<u8>,
aes_key: [u8; 32],
nonce: [u8; 12],
) -> Result<Self> {
if plaintext.len() == 0 {
return Err(Error::msg("Can't create encryption for an empty message"));
}
Ok(Self {
purpose,
plaintext,
aes_key,
nonce,
shared_secrets: HashMap::new(),
})
}
pub fn encrypt_with_aes_key(&self) -> Result<CipherText> {
match self.purpose {
Purpose::Login => self.encrypt_login(),
Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(),
}
}
fn encrypt_login(&self) -> Result<CipherText> {
let half_key: HalfKey = self.plaintext.clone().try_into()?;
let cipher = Aes256Gcm::new(&self.aes_key.into());
let cipher_text = cipher
.encrypt(&self.nonce.into(), half_key.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
res.extend_from_slice(&self.nonce);
res.extend_from_slice(&cipher_text);
Ok(res)
}
fn encrypt_thirty_two(&self) -> Result<CipherText> {
if self.plaintext.len() != 32 {
return Err(Error::msg("Invalid length, should be 32"));
}
let mut thirty_two = [0u8; 32];
thirty_two.copy_from_slice(&self.plaintext);
let cipher = Aes256Gcm::new(&self.aes_key.into());
let cipher_text = cipher
.encrypt(&self.nonce.into(), thirty_two.as_slice())
.map_err(|e| Error::msg(format!("{}", e)))?;
let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len());
log::info!("{}", cipher_text.len());
res.extend_from_slice(&self.nonce);
res.extend_from_slice(&cipher_text);
Ok(res)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz";
const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08";
const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b";
const ALICE_SHARED_SECRET: &str =
"ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106";
const BOB_SHARED_SECRET: &str =
"15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521";
#[test]
fn new_aes_empty_plaintext() {
let plaintext = Vec::new();
let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext);
assert!(aes_enc.is_err());
}
#[test]
fn aes_encrypt_login_invalid_length() {
let plaintext = "example";
let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec());
assert!(aes_enc_short.is_ok());
let cipher = aes_enc_short.unwrap().encrypt_with_aes_key();
assert!(cipher.is_err());
let plaintext = [1u8; 64];
let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec());
assert!(aes_enc_long.is_ok());
let cipher = aes_enc_long.unwrap().encrypt_with_aes_key();
assert!(cipher.is_err());
}
#[test]
fn aes_encrypt_login() {
let plaintext = [1u8; HALFKEYSIZE];
let aes_key = Aes256Gcm::generate_key(&mut thread_rng());
let nonce = Aes256Gcm::generate_nonce(&mut thread_rng());
let aes_enc = Aes256Encryption::import_key(
Purpose::Login,
plaintext.to_vec(),
aes_key.into(),
nonce.into(),
);
assert!(aes_enc.is_ok());
let cipher = aes_enc.unwrap().encrypt_with_aes_key();
assert!(cipher.is_ok());
let mut plain_key = [0u8; 32];
plain_key.copy_from_slice(&aes_key.to_vec());
let aes_dec =
Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key.to_vec(), None);
assert!(aes_dec.is_ok());
}
#[test]
fn aes_encrypt_key() {
let plaintext = [1u8; HALFKEYSIZE];
let mut aes_enc = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()).unwrap();
let mut shared_secrets: HashMap<Txid, _> = HashMap::new();
let mut sp_address2shared_secrets: HashMap<SilentPaymentAddress, SharedSecret> =
HashMap::new();
sp_address2shared_secrets.insert(
ALICE_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(),
);
shared_secrets.insert(
Txid::from_str(TRANSACTION).unwrap(),
sp_address2shared_secrets,
);
aes_enc.set_shared_secret(shared_secrets);
let sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets();
assert!(sp_address2encrypted_keys.is_ok());
let encrypted_key = sp_address2encrypted_keys
.unwrap()
.get(&ALICE_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
assert!(ciphertext.is_ok());
let aes_dec = Aes256Decryption::new(
Purpose::Login,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()),
);
assert!(aes_dec.is_ok());
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.is_ok());
assert!(retrieved_plain.unwrap() == plaintext);
}
#[test]
fn aes_encrypt_key_many() {
let plaintext = [1u8; THIRTYTWO];
let mut aes_enc =
Aes256Encryption::new(Purpose::ThirtyTwoBytes, plaintext.to_vec()).unwrap();
let mut shared_secrets: HashMap<Txid, _> = HashMap::new();
let mut sp_address2shared_secrets: HashMap<SilentPaymentAddress, SharedSecret> =
HashMap::new();
sp_address2shared_secrets.insert(
ALICE_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(),
);
sp_address2shared_secrets.insert(
BOB_SP_ADDRESS.try_into().unwrap(),
SharedSecret::from_str(BOB_SHARED_SECRET).unwrap(),
);
shared_secrets.insert(
Txid::from_str(TRANSACTION).unwrap(),
sp_address2shared_secrets,
);
aes_enc.set_shared_secret(shared_secrets);
let mut sp_address2encrypted_keys = aes_enc.encrypt_keys_with_shared_secrets();
assert!(sp_address2encrypted_keys.is_ok());
// Alice
let encrypted_key = sp_address2encrypted_keys
.as_mut()
.unwrap()
.get(&ALICE_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
let aes_dec = Aes256Decryption::new(
Purpose::ThirtyTwoBytes,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap()),
);
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.unwrap() == plaintext);
// Bob
let encrypted_key = sp_address2encrypted_keys
.unwrap()
.get(&BOB_SP_ADDRESS.try_into().unwrap())
.cloned();
let ciphertext = aes_enc.encrypt_with_aes_key();
let aes_dec = Aes256Decryption::new(
Purpose::ThirtyTwoBytes,
ciphertext.unwrap(),
encrypted_key.unwrap(),
Some(SharedSecret::from_str(BOB_SHARED_SECRET).unwrap()),
);
let retrieved_plain = aes_dec.unwrap().decrypt_with_key();
assert!(retrieved_plain.unwrap() == plaintext);
}
}

View File

@ -1,105 +0,0 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn inject_html_create_id() -> String {
String::from(
"
<div class='card'>
<div class='side-by-side'>
<h3>Create an Id</h3>
<div><a href='#'>Processes</a></div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' /><hr/>
<input type='hidden' id='currentpage' value='creatid' />
<select id='selectProcess' class='custom-select'></select><hr/>
<div class='side-by-side'>
<button type='submit' id='submitButton' class='bg-primary'>Create</button>
<div>
<a href='#' id='displayrecover'>Recover</a>
</div>
</div>
</form><br/>
<div id='passwordalert' class='passwordalert'></div>
</div>
",
)
}
#[wasm_bindgen]
pub fn inject_html_recover() -> String {
String::from("
<div class='card'>
<div class='side-by-side'>
<h3>Recover my Id</h3>
<div><a href='#'>Processes</a></div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' />
<input type='hidden' id='currentpage' value='recover' />
<select id='selectProcess' class='custom-select'></select><hr/>
<div class='side-by-side'>
<button type='submit' id='submitButton' class='recover bg-primary'>Recover</button>
<div>
<a href='#' id='displaycreateid'>Create an Id</a>
</div>
</div><hr/>
<a href='#' id='displayrevoke' class='btn'>Revoke</a>
</form><br/>
<div id='passwordalert' class='passwordalert'></div>
</div>
")
}
#[wasm_bindgen]
pub fn inject_html_revokeimage() -> String {
String::from("
<div class='card'>
<div class='side-by-side'>
<h3>Revoke image</h3>
<div><a href='#' id='displayupdateanid'>Update an Id</a></div>
</div>
</div>
<div class='card-revoke'>
<a href='#' download='revoke_4NK.jpg' id='revoke'>
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'>
<path
d='M246.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 109.3V320c0 17.7 14.3 32 32 32s32-14.3 32-32V109.3l73.4 73.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-128-128zM64 352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 53 43 96 96 96H352c53 0 96-43 96-96V352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V352z'
/>
</svg>
</a>
<div class='image-container'>
<img src='assets/4nk_revoke.jpg' alt='' />
</div>
</div>
")
}
#[wasm_bindgen]
pub fn inject_html_revoke() -> String {
String::from(
"
<div class='card'>
<div class='side-by-side'>
<h3>Revoke an Id</h3>
<div>
<a href='#' id='displayrecover'>Recover</a>
</div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' />
<hr/>
<div class='image-container'>
<label class='image-label'>Revoke image</label>
<img src='assets/revoke.jpeg' alt='' />
</div>
<hr/>
<button type='submit' id='submitButton' class='recover bg-primary'>Revoke</button>
</form>
</div>
",
)
}

View File

@ -1,8 +1,7 @@
#![allow(warnings)]
mod Prd_list;
mod aesgcm;
pub mod api;
mod injecteurhtml;
mod crypto;
mod peers;
mod process;
mod user;

View File

@ -3,6 +3,321 @@ use serde_json::{json, Value};
use tsify::Tsify;
use wasm_bindgen::prelude::*;
pub const HTML_CREATE_ID: &str = "
<div class='card'>
<div class='side-by-side'>
<h3>Create an Id</h3>
<div><a href='#'>Processes</a></div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' /><hr/>
<input type='hidden' id='currentpage' value='creatid' />
<select id='selectProcess' class='custom-select'></select><hr/>
<div class='side-by-side'>
<button type='submit' id='submitButton' class='bg-primary'>Create</button>
<div>
<a href='#' id='displayrecover'>Recover</a>
</div>
</div>
</form><br/>
<div id='passwordalert' class='passwordalert'></div>
</div>
";
pub const HTML_UPDATE_ID: &str = "
<body>
<div class='container'>
<div>
<h3>Update an Id</h3>
</div>
<hr />
<form id='form4nk' action='#'>
<label for='firstName'>First Name:</label>
<input type='text' id='firstName' name='firstName' required />
<label for='lastName'>Last Name:</label>
<input type='text' id='lastName' name='lastName' required />
<label for='Birthday'>Birthday:</label>
<input type='date' id='Birthday' name='birthday' />
<label for='file'>File:</label>
<input type='file' id='fileInput' name='file' />
<label>Third parties:</label>
<div id='sp-address-block'>
<div class='side-by-side'>
<input
type='text'
name='sp-address'
id='sp-address'
placeholder='sp address'
form='no-form'
/>
<button
type='button'
class='circle-btn bg-secondary'
id='add-sp-address-btn'
>
+
</button>
</div>
</div>
<div class='div-text-area'>
<textarea
name='bio'
id=''
cols='30'
rows='10'
placeholder='Bio'
></textarea>
</div>
<button type='submit' class='bg-primary'>Update</button>
</form>
</div>
</body>
";
pub const HTML_RECOVER: &str = "
<div class='card'>
<div class='side-by-side'>
<h3>Recover my Id</h3>
<div><a href='#'>Processes</a></div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' />
<input type='hidden' id='currentpage' value='recover' />
<select id='selectProcess' class='custom-select'></select><hr/>
<div class='side-by-side'>
<button type='submit' id='submitButton' class='recover bg-primary'>Recover</button>
<div>
<a href='#' id='displaycreateid'>Create an Id</a>
</div>
</div><hr/>
<a href='#' id='displayrevoke' class='btn'>Revoke</a>
</form><br/>
<div id='passwordalert' class='passwordalert'></div>
</div>
";
pub const HTML_REVOKE_IMAGE: &str = "
<div class='card'>
<div class='side-by-side'>
<h3>Revoke image</h3>
<div><a href='#' id='displayupdateanid'>Update an Id</a></div>
</div>
</div>
<div class='card-revoke'>
<a href='#' download='revoke_4NK.jpg' id='revoke'>
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'>
<path
d='M246.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 109.3V320c0 17.7 14.3 32 32 32s32-14.3 32-32V109.3l73.4 73.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-128-128zM64 352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 53 43 96 96 96H352c53 0 96-43 96-96V352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V352z'
/>
</svg>
</a>
<div class='image-container'>
<img src='assets/4nk_revoke.jpg' alt='' />
</div>
</div>
";
pub const HTML_REVOKE: &str = "
<div class='card'>
<div class='side-by-side'>
<h3>Revoke an Id</h3>
<div>
<a href='#' id='displayrecover'>Recover</a>
</div>
</div>
<form id='form4nk' action='#'>
<label for='password'>Password :</label>
<input type='password' id='password' />
<hr/>
<div class='image-container'>
<label class='image-label'>Revoke image</label>
<img src='assets/revoke.jpeg' alt='' />
</div>
<hr/>
<button type='submit' id='submitButton' class='recover bg-primary'>Revoke</button>
</form>
</div>
";
pub const CSS: &str = "
body {
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #f4f4f4;
font-family: 'Arial', sans-serif;
}
.container {
text-align: center;
}
.card {
max-width: 400px;
width: 100%;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
border-radius: 8px;
text-align: left;
overflow: hidden;
}
form {
display: flex;
flex-direction: column;
/* flex-wrap: wrap; */
}
label {
font-weight: bold;
margin-bottom: 8px;
}
hr {
border: 0;
height: 1px;
background-color: #ddd;
margin: 10px 0;
}
input, select {
width: 100%;
padding: 10px;
margin: 8px 0;
box-sizing: border-box;
}
select {
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
button {
display: inline-block;
background-color: #4caf50;
color: #fff;
border: none;
padding: 12px 17px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.side-by-side {
display: flex;
align-items: center;
justify-content: space-between;
}
.side-by-side>* {
display: inline-block;
}
button.recover {
display: inline-block;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: #4caf50;
color: #fff;
border: none;
padding: 12px 17px;
border-radius: 4px;
cursor: pointer;
}
button.recover:hover {
background-color: #45a049;
}
a.btn {
display: inline-block;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: #4caf50;
color: #fff;
border: none;
padding: 12px 17px;
border-radius: 4px;
cursor: pointer;
}
a.btn:hover {
background-color: #45a049;
}
a {
text-decoration: none;
color: #78a6de;
}
.bg-secondary {
background-color: #2b81ed;
}
.bg-primary {
background-color: #1A61ED;
}
.bg-primary:hover {
background-color: #457be8;
}
.card-revoke {
display: flex;
flex-direction: column;
max-width: 400px;
width: 100%;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
border-radius: 8px;
text-align: center;
align-items: center;
overflow: hidden;
}
.card-revoke a {
max-width: 50px;
width: 100%;
background: none;
border: none;
cursor: pointer;
}
.card-revoke button {
max-width: 200px;
width: 100%;
background: none;
border: none;
cursor: pointer;
color: #78a6de;
}
.card-revoke svg {
width: 100%;
height: auto;
fill: #333;
}
.image-label {
display: block;
color: #fff;
padding: 5px;
margin-top: 10px;
}
.image-container {
width: 400px;
height: 300px;
overflow: hidden;
}
.image-container img {
text-align: center;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
}
.passwordalert {
color: #FF0000;
}
";
// process member (gestionnaire for now)
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub enum Role {
@ -10,6 +325,7 @@ pub enum Role {
#[default]
User,
}
#[derive(Debug, Serialize, Deserialize, Default, Tsify, Clone)]
#[tsify(into_wasm_abi)]
pub struct ItemMember {
@ -31,8 +347,12 @@ impl ItemMember {
#[derive(Debug, Serialize, Deserialize, Default, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Process {
pub id: String,
pub id: u32,
pub name: String,
pub version: String,
pub gestionnaires: Vec<ItemMember>,
pub members: Vec<ItemMember>,
pub html: String,
pub style: String,
pub script: String,
//item_name : String,
}

View File

@ -18,7 +18,7 @@ use wasm_bindgen::prelude::*;
use shamir::SecretData;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write, Cursor};
use std::io::{Cursor, Read, Write};
use std::str::FromStr;
use std::sync::{Mutex, OnceLock};
@ -28,9 +28,7 @@ use sp_backend::silentpayments::sending::SilentPaymentAddress;
use sp_backend::spclient::SpendKey;
use sp_backend::spclient::{OutputList, SpClient};
use crate::aesgcm::Aes256Decryption;
use crate::aesgcm::HalfKey;
use crate::aesgcm::{Aes256Encryption, Purpose};
use crate::crypto::{Aes256Decryption, Aes256Encryption, HalfKey, Purpose};
use crate::peers::Peer;
use crate::user;
@ -200,7 +198,7 @@ impl User {
let mut entropy1 = [0u8; 32];
let mut entropy2 = [0u8; 32];
let mut entropy3 = [0u8; 32];
let mut cipher_scan_key = [0u8; 60];
let mut cipher_scan_key = [0u8; 60]; // cipher length == plain.len() + 16 + nonce.len()
let mut part1_ciphertext = [0u8; 44];
let mut reader = Cursor::new(recover_data);

View File

@ -38,8 +38,14 @@ class Database {
},
AnkProcess: {
name: "process",
options: {},
indices: []
options: {'keyPath': 'id'},
indices: [{
name: 'by_name',
keyPath: 'name',
options: {
'unique': true
}
}]
}
}
@ -101,7 +107,7 @@ class Database {
return objectList;
}
public writeObject(db: IDBDatabase, storeName: string, obj: any, key: string | null): Promise<IDBRequest> {
public writeObject(db: IDBDatabase, storeName: string, obj: any, key: IDBValidKey | null): Promise<IDBRequest> {
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
@ -117,7 +123,7 @@ class Database {
});
}
public getObject<T>(db: IDBDatabase, storeName: string, key: string | number): Promise<T> {
public getObject<T>(db: IDBDatabase, storeName: string, key: IDBValidKey): Promise<T> {
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
@ -128,6 +134,25 @@ class Database {
});
}
public getFirstMatchWithIndex<T>(db: IDBDatabase, storeName: string, indexName: string, lookup: string): Promise<T | null> {
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.openCursor(IDBKeyRange.only(lookup));
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
resolve(cursor.value);
} else {
resolve(null)
}
}
});
}
public setObject(db: IDBDatabase, storeName: string, obj: any, key: string | null): Promise<IDBRequest> {
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');

View File

@ -4,6 +4,7 @@ import IndexedDB from './database'
document.addEventListener('DOMContentLoaded', async () => {
try {
const services = await Services.getInstance();
if ((await services.isNewUser())) {
await services.displayCreateId();
}

View File

@ -1,6 +1,5 @@
import { createUserReturn, User } from '../dist/pkg/sdk_client';
import { createUserReturn, User, Process } from '../dist/pkg/sdk_client';
import IndexedDB from './database'
import Processstore from './store/processstore';
class Services {
private static instance: Services;
@ -60,14 +59,11 @@ class Services {
}
public async displayCreateId(): Promise<void> {
Services.instance.injectHtml(Services.instance.get_html_create_id());
Services.instance.attachSubmitListener("form4nk", (event) => Services.instance.createId(event));
Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover);
Services.instance.displayProcess(await Services.instance.getAllProcess());
}
public get_html_create_id(): string {
return this.sdkClient.inject_html_create_id();
const services = await Services.getInstance();
await services.injectHtml('CREATE_ID');
services.attachSubmitListener("form4nk", (event) => services.createId(event));
services.attachClickListener("displayrecover", services.displayRecover);
services.displayProcess();
}
public async createId(event: Event): Promise<void> {
@ -88,15 +84,14 @@ class Services {
// if (!Services.instance.isPasswordValid(password)) return;
let label = null;
let birthday = 50000;
const user: createUserReturn = this.sdkClient.create_user(password, label, birthday, this.current_process);
let birthday_signet = 50000;
let birthday_main = 500000;
const user: createUserReturn = this.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process);
try {
const indexedDb = await IndexedDB.getInstance();
const db = indexedDb.getDb();
await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user.user, null);
// console.log("JS User added");
await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, user.output_list_vec, null);
} catch (error) {
console.error("Failed to write user object :", error);
@ -106,16 +101,13 @@ class Services {
}
public async displayRecover(): Promise<void> {
Services.instance.injectHtml(Services.instance.get_html_recover());
Services.instance.attachSubmitListener("form4nk", Services.instance.recover);
Services.instance.attachClickListener("displaycreateid", Services.instance.displayCreateId);
Services.instance.attachClickListener("displayrevoke", Services.instance.displayRevoke);
Services.instance.attachClickListener("submitButtonRevoke", Services.instance.revoke);
Services.instance.displayProcess(await Services.instance.getAllUserProcess());
}
public get_html_recover(): string {
return this.sdkClient.inject_html_recover();
const services = await Services.getInstance();
services.injectHtml('RECOVER');
services.attachSubmitListener("form4nk", services.recover);
services.attachClickListener("displaycreateid", services.displayCreateId);
services.attachClickListener("displayrevoke", services.displayRevoke);
services.attachClickListener("submitButtonRevoke", services.revoke);
services.displayProcess();
}
public async recover(event: Event) {
@ -141,8 +133,7 @@ class Services {
}
public async displayRevokeImage(): Promise<void> {
const html = Services.instance.get_html_revokeimage();
Services.instance.injectHtml(html);
Services.instance.injectHtml('REVOKE_IMAGE');
Services.instance.attachClickListener("displayupdateanid", Services.instance.displayUpdateAnId);
let imageBytes = await Services.instance.getRecoverImage('assets/4nk_revoke.jpg');
@ -155,10 +146,6 @@ class Services {
}
}
public get_html_revokeimage(): string {
return this.sdkClient.inject_html_revokeimage();
}
private async getRecoverImage(imageUrl:string): Promise<Uint8Array|null> {
let imageBytes = null;
try {
@ -175,16 +162,11 @@ class Services {
}
public async displayRevoke(): Promise<void> {
const html = Services.instance.get_html_revoke();
Services.instance.injectHtml(html);
Services.instance.injectHtml('REVOKE');
Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover);
Services.instance.attachSubmitListener("form4nk", Services.instance.revoke);
}
public get_html_revoke(): string {
return this.sdkClient.inject_html_revoke();
}
public async revoke(event: Event): Promise<void> {
event.preventDefault();
console.log("JS revoke click ");
@ -198,18 +180,14 @@ class Services {
let style = "";
let script = "";
try {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
try {
let processObject = await indexedDB.getObject<Processstore>(db, indexedDB.getStoreList().AnkProcess, Services.instance.current_process!);
const processObject = await this.getProcessByName(Services.instance.current_process!);
if (processObject) {
body = processObject.html;
style = processObject.style;
script = processObject.script;
} catch (error) {
console.log("JS Processstore not exist ");
}
} catch (error) {
console.error("Failed to retrieve user object :", error);
console.error("Failed to retrieve process with Error:", error);
}
Services.instance.injectUpdateAnIdHtml(body, style, script);
@ -253,76 +231,82 @@ class Services {
// TODO Mock add user member to process
}
public displayProcess(processList: string[]): void {
console.log("JS processList : "+processList);
public async displayProcess(): Promise<void> {
const services = await Services.getInstance();
const processList = await services.getAllProcess();
const selectProcess = document.getElementById("selectProcess");
if (selectProcess) {
processList.forEach((process) => {
let child = new Option(process, process);
let child = new Option(process.name, process.name);
if (!selectProcess.contains(child)) {
selectProcess.appendChild(child);
}
})
}
}
public async getAllUserProcess(): Promise<string[]> {
let userProcessList: string[] = [];
public async getAllProcess(): Promise<Process[]> {
try {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
let processListObject = await indexedDB.getAll<Processstore>(db, indexedDB.getStoreList().AnkProcess);
let processListObject = await indexedDB.getAll<Process>(db, indexedDB.getStoreList().AnkProcess);
return processListObject;
} catch (error) {
console.log('getAllProcess failed: ',error);
return [];
}
}
public async getAllProcessForUser(pre_id: string): Promise<Process[]> {
const services = await Services.getInstance();
let user: User;
let userProcessList: Process[] = [];
try {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
user = await indexedDB.getObject<User>(db, indexedDB.getStoreList().AnkUser, pre_id);
} catch (error) {
console.error('getAllUserProcess failed: ',error);
return [];
}
try {
const processListObject = await services.getAllProcess();
processListObject.forEach(async (processObject) => {
const listMember = processObject.listMember;
const processName = processObject.process;
listMember.forEach(async (member) => {
if (member == "user1") {
userProcessList.push(processName);
console.log("JS UserProcess found");
}
})
if (processObject.members.includes(user.pre_id)) {
userProcessList.push(processObject);
}
})
} catch (error) {
console.log("JS Processstore not found");
console.error('getAllUserProcess failed: ',error);
return [];
}
return userProcessList;
}
public async getAllProcess(): Promise<string[]> {
// if indexedDB is empty, get list from wasm
let processList: string[] = [];
try {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
let processListObject = await indexedDB.getAll<Processstore>(db, indexedDB.getStoreList().AnkProcess);
processListObject.forEach(async (processObject) => {
const processName = processObject.process;
processList.push(processName);
console.log("JS Processstore found");
})
} catch (error) {
console.log("JS Processstore not found");
}
if (processList.length == 0) {
processList = await this.addProcessStore();
}
return processList;
public async getProcessByName(name: string): Promise<Process | null> {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
const process = await indexedDB.getFirstMatchWithIndex<Process>(db, indexedDB.getStoreList().AnkProcess, 'by_name', name);
return process;
}
public async addProcessStore(): Promise<string[]> {
const processList = this.sdkClient.get_process()
processList.forEach(async (process: string) => {
// TODO process mock
let processstore = new Processstore;
processstore.process = process;
public async loadProcesses(): Promise<void> {
const services = await Services.getInstance();
const processList: Process[] = services.sdkClient.get_processes();
processList.forEach(async (process: Process) => {
const indexedDB = await IndexedDB.getInstance();
const db = indexedDB.getDb();
await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, processstore, process);
console.log("JS Processstore mock added");
try {
if (await indexedDB.getObject<Process>(db, indexedDB.getStoreList().AnkProcess, process.id) === null) {
await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null);
}
} catch (error) {
console.warn('Error while writing process', process.name, 'to indexedDB:', error);
}
})
return processList;
}
public attachClickListener(elementId: string, callback: (event: Event) => void): void {
const element = document.getElementById(elementId);
@ -336,15 +320,25 @@ class Services {
element?.addEventListener("submit", callback);
}
public injectHtml(html: string) {
console.log("JS html : "+html);
public async injectHtml(processName: string) {
// console.log("JS html : "+html);
const container = document.getElementById('containerId');
if (!container) {
console.error("No html container");
return;
}
container.innerHTML = html;
const services = await Services.getInstance();
await services.loadProcesses();
const process = await services.getProcessByName(processName);
if (process) {
container.innerHTML = process.html;
} else {
console.error("No process ", processName);
}
}
// public async getCurrentProcess(): Promise<string> {

View File

@ -1,237 +0,0 @@
class Processstore {
process: string;
html: string;
style: string;
script: string;
listMember: string[];
createDate: Date;
constructor() {
this.process = "";
this.html = getMockHtml();
this.style = getMockStyle();
this.script = getMockScript();
this.script = getMockScript();
this.listMember = getMockListMember();
this.createDate = new Date;
}
}
export default Processstore;
function getMockHtml(): string {
let html: string = `
<body>
<div class='container'>
<div>
<h3>Update an Id</h3>
</div>
<hr />
<form id='form4nk' action='#'>
<label for='firstName'>First Name:</label>
<input type='text' id='firstName' name='firstName' required />
<label for='lastName'>Last Name:</label>
<input type='text' id='lastName' name='lastName' required />
<label for='Birthday'>Birthday:</label>
<input type='date' id='Birthday' name='birthday' />
<label for='file'>File:</label>
<input type='file' id='fileInput' name='file' />
<label>Third parties:</label>
<div id='sp-address-block'>
<div class='side-by-side'>
<input
type='text'
name='sp-address'
id='sp-address'
placeholder='sp address'
form='no-form'
/>
<button
type='button'
class='circle-btn bg-secondary'
id='add-sp-address-btn'
>
+
</button>
</div>
</div>
<div class='div-text-area'>
<textarea
name='bio'
id=''
cols='30'
rows='10'
placeholder='Bio'
></textarea>
</div>
<button type='submit' class='bg-primary'>Update</button>
</form>
</div>
</body>
`;
return html;
}
function getMockStyle(): string {
let style: string = `
<style>
body {
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #f4f4f4;
font-family: 'Arial', sans-serif;
}
.container {
max-width: 400px;
width: 100%;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
border-radius: 8px;
text-align: left;
overflow: hidden;
}
form {
display: grid;
grid-template-columns: repeat(1fr, 2fr);
gap: 10px;
max-width: 400px;
margin: auto;
}
.bg-primary {
background-color: #1a61ed;
}
.bg-primary:hover {
background-color: #457be8;
}
.bg-secondary {
background-color: #2b81ed;
}
.bg-secondary:hover {
background-color: #5f9bff;
}
label {
text-align: left;
padding-right: 10px;
line-height: 2;
}
input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
button {
grid-column: span 2;
display: inline-block;
color: #fff;
border: none;
padding: 12px 17px;
border-radius: 4px;
cursor: pointer;
}
.div-text-area {
grid-column: span 2;
}
textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
.side-by-side {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 5px;
}
.circle-btn {
width: 25px;
height: 25px;
border-radius: 50%;
border: none;
color: white;
padding: 0px;
text-align: center;
}
#fileInput {
width: 100%;
padding: 8px;
padding-left: 0px;
box-sizing: border-box;
}
</style>`;
return style;
}
function getMockScript(): string {
let script: string = `
var addSpAddressBtn = document.getElementById('add-sp-address-btn');
var removeSpAddressBtn = document.querySelectorAll('.minus-sp-address-btn');
addSpAddressBtn.addEventListener('click', function (event) {
addDynamicField(this);
});
function addDynamicField(element) {
var addSpAddressBlock = document.getElementById('sp-address-block');
var spAddress = addSpAddressBlock.querySelector('#sp-address').value;
addSpAddressBlock.querySelector('#sp-address').value = '';
spAddress = spAddress.trim();
if (spAddress != '') {
var sideBySideDiv = document.createElement('div');
sideBySideDiv.className = 'side-by-side';
var inputElement = document.createElement('input');
inputElement.type = 'text';
inputElement.name = 'spAddresses[]';
inputElement.setAttribute('form', 'no-form');
inputElement.value = spAddress;
inputElement.disabled = true;
var buttonElement = document.createElement('button');
buttonElement.type = 'button';
buttonElement.className =
'circle-btn bg-secondary minus-sp-address-btn';
buttonElement.innerHTML = '-';
buttonElement.addEventListener('click', function (event) {
removeDynamicField(this.parentElement);
});
sideBySideDiv.appendChild(inputElement);
sideBySideDiv.appendChild(buttonElement);
addSpAddressBlock.appendChild(sideBySideDiv);
}
function removeDynamicField(element) {
element.remove();
}
}
`;
return script;
}
function getMockListMember(): string[] {
return ["user1","user2","user3"];
}