#![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::keccak256;
use crate::ecc::keys::AccountVerifier;
use crate::ecc::keys::SignatureAlgorithm;
use crate::ecc::keys::VerificationPublicKey;
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: u64) -> String {
format!("{session_id}\n{ts_ms}\n{ttl_ms}")
}
#[wasm_export]
pub struct SessionSkBuilder {
sk: SecretKey,
account_entity: String,
account_type: String,
ttl_ms: u64,
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: u64,
ts_ms: u128,
sig: Vec<u8>,
}
#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone)]
pub enum Account {
Secp256k1(Did),
Secp256r1(PublicKey<33>),
EIP191(Did),
BIP137(Did),
Ed25519(PublicKey<33>),
Bls12381(PublicKey<48>),
}
impl TryFrom<(String, String)> for Account {
type Error = Error;
fn try_from((account_entity, account_type): (String, String)) -> Result<Self> {
match AccountVerifier::from_account_parts(&account_entity, &account_type)? {
AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Secp256k1,
did,
} => Ok(Account::Secp256k1(did)),
AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Eip191,
did,
} => Ok(Account::EIP191(did)),
AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Bip137,
did,
} => Ok(Account::BIP137(did)),
AccountVerifier::PublicKey(VerificationPublicKey::Secp256r1(pk)) => {
Ok(Account::Secp256r1(pk))
}
AccountVerifier::PublicKey(VerificationPublicKey::Ed25519(pk)) => {
Ok(Account::Ed25519(pk))
}
AccountVerifier::PublicKey(VerificationPublicKey::Bls12381(pk)) => {
Ok(Account::Bls12381(pk))
}
_ => Err(Error::UnknownAccount),
}
}
}
impl Account {
fn account_verifier(&self) -> AccountVerifier {
match self {
Self::Secp256k1(did) => AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Secp256k1,
did: *did,
},
Self::EIP191(did) => AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Eip191,
did: *did,
},
Self::BIP137(did) => AccountVerifier::Recoverable {
algorithm: SignatureAlgorithm::Bip137,
did: *did,
},
Self::Secp256r1(pk) => {
AccountVerifier::PublicKey(VerificationPublicKey::Secp256r1(*pk))
}
Self::Ed25519(pk) => AccountVerifier::PublicKey(VerificationPublicKey::Ed25519(*pk)),
Self::Bls12381(pk) => AccountVerifier::PublicKey(VerificationPublicKey::Bls12381(*pk)),
}
}
}
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: u64) -> 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) -> Vec<u8> {
pack_session(self.session_id, self.ts_ms, self.ttl_ms)
.as_bytes()
.to_vec()
}
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_bytes = self.pack();
if !self
.account
.account_verifier()
.verify(&auth_bytes, &self.sig)
{
return Err(Error::VerifySignatureFailed);
}
Ok(())
}
pub fn verify(&self, msg: &[u8], 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<33>> {
match self.account_verification_pubkey()? {
VerificationPublicKey::Secp256k1(pk)
| VerificationPublicKey::Eip191(pk)
| VerificationPublicKey::Bip137(pk) => Ok(pk),
VerificationPublicKey::Secp256r1(_)
| VerificationPublicKey::Ed25519(_)
| VerificationPublicKey::Bls12381(_) => Err(Error::UnknownAccount),
}
}
pub fn account_verification_pubkey(&self) -> Result<VerificationPublicKey> {
self.account
.account_verifier()
.verification_key_from_signature(&self.pack(), &self.sig)
}
pub fn account_verifier(&self) -> AccountVerifier {
self.account.account_verifier()
}
pub fn account_did(&self) -> Did {
self.account.account_verifier().did()
}
}
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: &[u8]) -> Result<Vec<u8>> {
let key = self.sk;
let h = keccak256(msg);
Ok(signers::secp256k1::sign(key, &h).to_vec())
}
pub fn account_did(&self) -> Did {
self.session.account_did()
}
pub fn account_verifier(&self) -> AccountVerifier {
self.session.account_verifier()
}
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::*;
use crate::ecc::keys::SigningSecretKey;
#[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_session_verify_secp256r1_account_key() {
let account_entity = "17a6afd392fcbe4ac9270a599a9c5732c4f838ce35ea2234d389d8f0c367f3f5dcab906352e27289002c7f2c96039ddce7c1b5aad8b87ba94984d4c8b4f95702";
let account_key = VerificationPublicKey::Secp256r1(
PublicKey::<33>::from_hex_string(account_entity).unwrap(),
);
let signing_key =
SecretKey::try_from("2544acda37415a476d42312969926dc48e529867036cec71922d4177ea9c1038")
.unwrap();
let mut builder =
SessionSkBuilder::new(account_entity.to_string(), "secp256r1".to_string());
let proof = builder.unsigned_proof();
let sig =
signers::secp256r1::sign(signing_key, &signers::secp256r1::hash(proof.as_bytes()))
.unwrap();
builder = builder.set_session_sig(sig.to_vec());
let session = builder.build().unwrap().session();
assert_eq!(session.account_verification_pubkey().unwrap(), account_key);
assert_eq!(session.account_did(), account_key.did());
assert!(session.verify_self().is_ok());
assert!(session.account_pubkey().is_err());
}
#[test]
pub fn test_session_rejects_invalid_secp256r1_account_key() {
let mut invalid_key = None;
for i in 0u8..=u8::MAX {
let mut key = [0u8; 33];
key[0] = 2;
key[32] = i;
let public_key = PublicKey(key);
let verifying_key = public_key.ct_try_into_secp256r1_pubkey();
if !bool::from(verifying_key.is_some()) || verifying_key.unwrap().is_err() {
invalid_key = Some(key);
break;
}
}
let account_entity = hex::encode(invalid_key.expect("at least one invalid P-256 x"));
let builder = SessionSkBuilder::new(account_entity, "secp256r1".to_string())
.set_session_sig(vec![0u8; 64]);
assert!(!builder.validate_account());
assert!(builder.build().is_err());
}
#[test]
pub fn test_session_verify_bls12381_account_key() {
let signing_key = SigningSecretKey::random_bls12381().unwrap();
let account_key = signing_key.public_key().unwrap();
let VerificationPublicKey::Bls12381(raw_account_key) = account_key else {
unreachable!("random_bls12381 returns a BLS verification key");
};
let account_entity = base58_monero::encode_check(&raw_account_key.0).unwrap();
let mut builder = SessionSkBuilder::new(account_entity, "bls12-381".to_string());
let proof = builder.unsigned_proof();
builder = builder.set_session_sig(signing_key.sign_raw(proof.as_bytes()).unwrap());
let session = builder.build().unwrap().session();
assert_eq!(
session.account_verification_pubkey().unwrap(),
VerificationPublicKey::Bls12381(raw_account_key)
);
assert_eq!(session.account_did(), account_key.did());
assert!(session.verify_self().is_ok());
assert!(session.account_pubkey().is_err());
}
#[test]
pub fn test_session_verify_ed25519_account_key() {
let signing_key = SigningSecretKey::random_ed25519();
let account_key = signing_key.public_key().unwrap();
let VerificationPublicKey::Ed25519(raw_account_key) = account_key else {
unreachable!("random_ed25519 returns an Ed25519 verification key");
};
let account_entity = raw_account_key.to_base58_string().unwrap();
let mut builder = SessionSkBuilder::new(account_entity, "ed25519".to_string());
let proof = builder.unsigned_proof();
builder = builder.set_session_sig(signing_key.sign_raw(proof.as_bytes()).unwrap());
let session = builder.build().unwrap().session();
assert_eq!(
session.account_verification_pubkey().unwrap(),
VerificationPublicKey::Ed25519(raw_account_key)
);
assert_eq!(session.account_did(), account_key.did());
assert!(session.verify_self().is_ok());
assert!(session.account_pubkey().is_err());
}
#[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);
}
}