use std::fmt;
#[cfg(feature = "sign")]
use crate::utils::sign::NoRandomness;
use crate::utils::sign::goldilocks::Fp;
use crate::utils::sign::tx::{Envelope, SignTxError, Signed, Tx, sign_drawing};
use crate::utils::sign::{BadKey, PublicKey, SecretKey, hash_to_fp5};
use crate::utils::{AccountIndex, ApiKeyIndex};
pub const MAX_TOKEN_S: u64 = 8 * 3_600;
pub const TOKEN_S: u64 = 7 * 3_600;
#[derive(Clone)]
pub struct ApiKey {
account: AccountIndex,
slot: ApiKeyIndex,
secret: SecretKey,
public: PublicKey,
}
impl fmt::Debug for ApiKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ApiKey")
.field("account", &self.account)
.field("slot", &self.slot)
.field("public", &self.public.to_hex())
.finish_non_exhaustive()
}
}
impl ApiKey {
#[must_use]
pub fn new(account: AccountIndex, slot: ApiKeyIndex, secret: SecretKey) -> ApiKey {
let public = secret.public_key();
ApiKey { account, slot, secret, public }
}
pub fn from_hex(account: AccountIndex, slot: ApiKeyIndex, private_key: &str) -> Result<ApiKey, BadKey> {
Ok(ApiKey::new(account, slot, SecretKey::from_hex(private_key)?))
}
#[cfg(feature = "sign")]
pub fn generate(account: AccountIndex, slot: ApiKeyIndex) -> Result<ApiKey, NoRandomness> {
Ok(ApiKey::new(account, slot, SecretKey::generate()?))
}
#[must_use]
pub const fn account(&self) -> AccountIndex {
self.account
}
#[must_use]
pub const fn slot(&self) -> ApiKeyIndex {
self.slot
}
#[must_use]
pub const fn public_key(&self) -> PublicKey {
self.public
}
#[must_use]
pub const fn secret(&self) -> &SecretKey {
&self.secret
}
#[must_use]
pub fn at(&self, account: AccountIndex, slot: ApiKeyIndex) -> ApiKey {
ApiKey { account, slot, ..self.clone() }
}
#[must_use]
pub const fn envelope(&self, nonce: u64, expired_at: u64) -> Envelope {
Envelope { account: self.account, api_key: self.slot, nonce, expired_at }
}
pub fn sign_drawing<E: fmt::Display>(
&self,
tx: &(impl Tx + ?Sized),
chain_id: u32,
nonce: u64,
expired_at: u64,
draw: impl FnMut(&mut [u8; 40]) -> Result<(), E>,
) -> Result<Signed, SignTxError> {
sign_drawing(tx, chain_id, &self.envelope(nonce, expired_at), &self.secret, draw)
}
#[cfg(feature = "sign")]
pub fn sign(
&self,
tx: &(impl Tx + ?Sized),
chain_id: u32,
nonce: u64,
expired_at: u64,
) -> Result<Signed, SignTxError> {
self.sign_drawing(tx, chain_id, nonce, expired_at, crate::utils::sign::schnorr::os_draw)
}
pub fn auth_token_drawing<E>(
&self,
deadline_s: u64,
draw: impl FnMut(&mut [u8; 40]) -> Result<(), E>,
) -> Result<AuthToken, E> {
let message = format!("{deadline_s}:{}:{}", self.account, self.slot);
let elements = Fp::from_le_chunks(message.as_bytes()).unwrap_or_default();
let signature = self.secret.sign_drawing(hash_to_fp5(&elements), draw)?;
Ok(AuthToken { text: format!("{message}:{}", signature.to_hex()), deadline_s })
}
#[cfg(feature = "sign")]
pub fn auth_token(&self, deadline_s: u64) -> Result<AuthToken, NoRandomness> {
self.auth_token_drawing(deadline_s, crate::utils::sign::schnorr::os_draw)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct AuthToken {
text: String,
deadline_s: u64,
}
impl AuthToken {
#[must_use]
pub fn as_str(&self) -> &str {
&self.text
}
#[must_use]
pub const fn deadline_s(&self) -> u64 {
self.deadline_s
}
}
impl fmt::Debug for AuthToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AuthToken").field("deadline_s", &self.deadline_s).finish_non_exhaustive()
}
}
#[cfg(test)]
#[path = "tests/api_key.rs"]
mod tests;