use anyhow::{Result, anyhow};
use bip39::{Language, Mnemonic, MnemonicType, Seed};
use chrono::Utc;
use rand_chacha::ChaCha12Rng;
use rand_core::SeedableRng;
use rsa::{BigUint, Oaep, RsaPrivateKey, RsaPublicKey, traits::PublicKeyParts};
use sha2::Sha256;
use std::sync::Arc;
use wnfs::{
common::{BlockStore, MemoryBlockStore},
private::{
AccessKey, ExchangeKey, PUBLIC_KEY_EXPONENT, PrivateDirectory, PrivateKey, PrivateNode,
forest::{hamt::HamtForest, traits::PrivateForest},
share::{recipient, sharer},
},
public::{PublicDirectory, PublicLink},
};
#[async_std::main]
async fn main() -> Result<()> {
let store = &MemoryBlockStore::new();
let (mut forest, access_key) = root_dir_setup(store).await?;
let mnemonic = setup_seeded_keypair_access(&mut forest, access_key, store).await?;
println!("seed phrase: {}", mnemonic.phrase());
let node = regain_access_from_mnemonic(&forest, mnemonic, store).await?;
let dir = node.as_dir()?;
let content_bytes = dir
.read(&["hello".into(), "world".into()], true, &forest, store)
.await?;
let content = String::from_utf8_lossy(&content_bytes);
println!("Contents were: {content}");
assert_eq!(content, "Hello, World!");
Ok(())
}
async fn root_dir_setup(store: &impl BlockStore) -> Result<(Arc<HamtForest>, AccessKey)> {
let rng = &mut ChaCha12Rng::from_entropy();
let forest = &mut HamtForest::new_trusted_rc(rng);
let root_dir =
&mut PrivateDirectory::new_and_store(&forest.empty_name(), Utc::now(), forest, store, rng)
.await?;
root_dir
.write(
&["hello".into(), "world".into()],
true,
Utc::now(),
b"Hello, World!".to_vec(),
forest,
store,
rng,
)
.await?;
let access_key = root_dir.as_node().store(forest, store, rng).await?;
Ok((Arc::clone(forest), access_key))
}
async fn setup_seeded_keypair_access(
forest: &mut Arc<HamtForest>,
access_key: AccessKey,
store: &impl BlockStore,
) -> Result<Mnemonic> {
let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
let seed = Seed::new(&mnemonic, "");
let exchange_keypair = SeededExchangeKey::from_bip39_seed(seed)?;
let mut exchange_root = PublicDirectory::new_rc(Utc::now());
exchange_root
.write(
&["main".into(), "v1.exchange_key".into()],
exchange_keypair.encode_public_key(),
Utc::now(),
store,
)
.await?;
let exchange_root = PublicLink::with_rc_dir(exchange_root);
let root_did = "did:key:zExample";
let counter = recipient::find_latest_share_counter(
0,
1000,
&exchange_keypair.encode_public_key(),
root_did,
forest,
store,
)
.await?
.map(|x| x + 1)
.unwrap_or_default();
sharer::share::<PublicExchangeKey>(
&access_key,
counter,
root_did,
exchange_root,
forest,
store,
)
.await?;
Ok(mnemonic)
}
async fn regain_access_from_mnemonic(
forest: &HamtForest,
mnemonic: Mnemonic,
store: &impl BlockStore,
) -> Result<PrivateNode> {
let seed = Seed::new(&mnemonic, "");
let exchange_keypair = SeededExchangeKey::from_bip39_seed(seed)?;
let root_did = "did:key:zExample";
let counter = recipient::find_latest_share_counter(
0,
1000,
&exchange_keypair.encode_public_key(),
root_did,
forest,
store,
)
.await?
.unwrap_or_default();
let name = sharer::create_share_name(
counter,
root_did,
&exchange_keypair.encode_public_key(),
forest,
);
let node = recipient::receive_share(&name, &exchange_keypair, forest, store).await?;
let latest_node = node.search_latest(forest, store).await?;
Ok(latest_node)
}
struct SeededExchangeKey(RsaPrivateKey);
struct PublicExchangeKey(RsaPublicKey);
impl SeededExchangeKey {
pub fn from_bip39_seed(seed: Seed) -> Result<Self> {
let seed_bytes: [u8; 32] = seed.as_bytes()[..32].try_into()?;
let rng = &mut ChaCha12Rng::from_seed(seed_bytes);
let private_key = RsaPrivateKey::new(rng, 2048)?;
Ok(Self(private_key))
}
pub fn encode_public_key(&self) -> Vec<u8> {
self.0.n().to_bytes_be()
}
}
impl PrivateKey for SeededExchangeKey {
async fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
let padding = Oaep::new::<Sha256>();
self.0.decrypt(padding, ciphertext).map_err(|e| anyhow!(e))
}
}
impl ExchangeKey for PublicExchangeKey {
async fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
let padding = Oaep::new::<Sha256>();
self.0
.encrypt(&mut ChaCha12Rng::from_entropy(), padding, data)
.map_err(|e| anyhow!(e))
}
async fn from_modulus(modulus: &[u8]) -> Result<Self> {
let n = BigUint::from_bytes_be(modulus);
let e = BigUint::from(PUBLIC_KEY_EXPONENT);
Ok(Self(rsa::RsaPublicKey::new(n, e).map_err(|e| anyhow!(e))?))
}
}