#![warn(missing_docs)]
use std::str::FromStr;
use rings_derive::wasm_export;
use serde::Deserialize;
use serde::Serialize;
use crate::consts::DEFAULT_SESSION_TTL_MS;
use crate::dht::Did;
use crate::ecc::signers;
use crate::ecc::PublicKey;
use crate::ecc::SecretKey;
use crate::error::Error;
use crate::error::Result;
use crate::utils;
fn pack_session(session_id: Did, ts_ms: u128, ttl_ms: usize) -> String {
format!("{}\n{}\n{}", session_id, ts_ms, ttl_ms)
}
#[wasm_export]
pub struct SessionSkBuilder {
sk: SecretKey,
account_entity: String,
account_type: String,
ttl_ms: usize,
ts_ms: u128,
sig: Vec<u8>,
}
#[wasm_export]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct SessionSk {
session: Session,
sk: SecretKey,
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
pub struct Session {
session_id: Did,
account: Account,
ttl_ms: usize,
ts_ms: u128,
sig: Vec<u8>,
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
pub enum Account {
Secp256k1(Did),
EIP191(Did),
BIP137(Did),
Ed25519(PublicKey),
}
impl TryFrom<(String, String)> for Account {
type Error = Error;
fn try_from((account_entity, account_type): (String, String)) -> Result<Self> {
match account_type.as_str() {
"secp256k1" => Ok(Account::Secp256k1(Did::from_str(&account_entity)?)),
"eip191" => Ok(Account::EIP191(Did::from_str(&account_entity)?)),
"bip137" => Ok(Account::BIP137(Did::from_str(&account_entity)?)),
"ed25519" => Ok(Account::Ed25519(PublicKey::try_from_b58t(&account_entity)?)),
_ => Err(Error::UnknownAccount),
}
}
}
impl FromStr for SessionSk {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let s = base58_monero::decode_check(s).map_err(|_| Error::Decode)?;
let session_sk: SessionSk = serde_json::from_slice(&s).map_err(Error::Deserialize)?;
Ok(session_sk)
}
}
#[wasm_export]
impl SessionSkBuilder {
pub fn new(account_entity: String, account_type: String) -> SessionSkBuilder {
let sk = SecretKey::random();
Self {
sk,
account_entity,
account_type,
ttl_ms: DEFAULT_SESSION_TTL_MS,
ts_ms: utils::get_epoch_ms(),
sig: vec![],
}
}
pub fn validate_account(&self) -> bool {
Account::try_from((self.account_entity.clone(), self.account_type.clone()))
.map_err(|e| {
tracing::warn!("validate_account error: {:?}", e);
e
})
.is_ok()
}
pub fn unsigned_proof(&self) -> String {
pack_session(self.sk.address().into(), self.ts_ms, self.ttl_ms)
}
pub fn set_session_sig(mut self, sig: Vec<u8>) -> Self {
self.sig = sig;
self
}
pub fn set_ttl(mut self, ttl_ms: usize) -> Self {
self.ttl_ms = ttl_ms;
self
}
pub fn build(self) -> Result<SessionSk> {
let account = Account::try_from((self.account_entity, self.account_type))?;
let session = Session {
session_id: self.sk.address().into(),
account,
ttl_ms: self.ttl_ms,
ts_ms: self.ts_ms,
sig: self.sig,
};
session.verify_self()?;
Ok(SessionSk {
session,
sk: self.sk,
})
}
}
impl Session {
pub fn pack(&self) -> String {
pack_session(self.session_id, self.ts_ms, self.ttl_ms)
}
pub fn is_expired(&self) -> bool {
let now = utils::get_epoch_ms();
now > self.ts_ms + self.ttl_ms as u128
}
pub fn verify_self(&self) -> Result<()> {
if self.is_expired() {
return Err(Error::SessionExpired);
}
let auth_str = self.pack();
if !(match self.account {
Account::Secp256k1(did) => {
signers::secp256k1::verify(&auth_str, &did.into(), &self.sig)
}
Account::EIP191(did) => signers::eip191::verify(&auth_str, &did.into(), &self.sig),
Account::BIP137(did) => signers::bip137::verify(&auth_str, &did.into(), &self.sig),
Account::Ed25519(pk) => {
signers::ed25519::verify(&auth_str, &pk.address(), &self.sig, pk)
}
}) {
return Err(Error::VerifySignatureFailed);
}
Ok(())
}
pub fn verify(&self, msg: &str, sig: impl AsRef<[u8]>) -> Result<()> {
self.verify_self()?;
if !signers::secp256k1::verify(msg, &self.session_id, sig) {
return Err(Error::VerifySignatureFailed);
}
Ok(())
}
pub fn account_pubkey(&self) -> Result<PublicKey> {
let auth_str = self.pack();
match self.account {
Account::Secp256k1(_) => signers::secp256k1::recover(&auth_str, &self.sig),
Account::BIP137(_) => signers::bip137::recover(&auth_str, &self.sig),
Account::EIP191(_) => signers::eip191::recover(&auth_str, &self.sig),
Account::Ed25519(pk) => Ok(pk),
}
}
pub fn account_did(&self) -> Did {
match self.account {
Account::Secp256k1(did) => did,
Account::BIP137(did) => did,
Account::EIP191(did) => did,
Account::Ed25519(pk) => pk.address().into(),
}
}
}
impl SessionSk {
pub fn new_with_seckey(key: &SecretKey) -> Result<Self> {
let account_entity = Did::from(key.address()).to_string();
let account_type = "secp256k1".to_string();
let mut builder = SessionSkBuilder::new(account_entity, account_type);
let sig = key.sign(&builder.unsigned_proof());
builder = builder.set_session_sig(sig.to_vec());
builder.build()
}
pub fn session(&self) -> Session {
self.session.clone()
}
pub fn sign(&self, msg: &str) -> Result<Vec<u8>> {
let key = self.sk;
Ok(signers::secp256k1::sign_raw(key, msg).to_vec())
}
pub fn account_did(&self) -> Did {
self.session.account_did()
}
pub fn dump(&self) -> Result<String> {
let s = serde_json::to_string(&self).map_err(|_| Error::SerializeError)?;
base58_monero::encode_check(s.as_bytes()).map_err(|_| Error::Encode)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_session_verify() {
let key = SecretKey::random();
let sm = SessionSk::new_with_seckey(&key).unwrap();
let session = sm.session();
assert!(session.verify_self().is_ok());
}
#[test]
pub fn test_account_pubkey() {
let key = SecretKey::random();
let sm = SessionSk::new_with_seckey(&key).unwrap();
let session = sm.session();
let pubkey = session.account_pubkey().unwrap();
assert_eq!(key.pubkey(), pubkey);
}
#[test]
pub fn test_dump_restore() {
let key = SecretKey::random();
let sm = SessionSk::new_with_seckey(&key).unwrap();
let dump = sm.dump().unwrap();
let sm2 = SessionSk::from_str(&dump).unwrap();
assert_eq!(sm, sm2);
}
}