1use core::fmt;
2use std::env;
3
4use hmac::{Hmac, Mac};
5use sha2::Sha256;
6
7use crate::error::{ErrorKind, MarketError, Result};
8
9type HmacSha256 = Hmac<Sha256>;
10
11pub trait Signer: Send + Sync {
13 fn sign_hex(&self, payload: &[u8]) -> Result<String>;
14}
15
16pub struct MemorySigner {
18 secret: Vec<u8>,
19}
20
21impl MemorySigner {
22 #[must_use]
23 pub fn new(secret: impl AsRef<[u8]>) -> Self {
24 Self {
25 secret: secret.as_ref().to_vec(),
26 }
27 }
28}
29
30impl fmt::Debug for MemorySigner {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.debug_struct("MemorySigner")
33 .field("secret", &"<redacted>")
34 .finish()
35 }
36}
37
38impl Signer for MemorySigner {
39 fn sign_hex(&self, payload: &[u8]) -> Result<String> {
40 let mut mac = HmacSha256::new_from_slice(&self.secret)
41 .map_err(|_| MarketError::new(ErrorKind::ConfigError, "invalid hmac key length"))?;
42 mac.update(payload);
43 Ok(hex::encode(mac.finalize().into_bytes()))
44 }
45}
46
47pub struct EnvSigner {
49 secret_var: Box<str>,
50}
51
52impl EnvSigner {
53 #[must_use]
54 pub fn new(secret_var: impl Into<Box<str>>) -> Self {
55 Self {
56 secret_var: secret_var.into(),
57 }
58 }
59}
60
61impl fmt::Debug for EnvSigner {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.debug_struct("EnvSigner")
64 .field("secret_var", &self.secret_var)
65 .field("secret", &"<redacted>")
66 .finish()
67 }
68}
69
70impl Signer for EnvSigner {
71 fn sign_hex(&self, payload: &[u8]) -> Result<String> {
72 let secret = env::var(self.secret_var.as_ref()).map_err(|_| {
73 MarketError::new(
74 ErrorKind::AuthError,
75 format!("missing env secret {}", self.secret_var),
76 )
77 })?;
78 MemorySigner::new(secret).sign_hex(payload)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::{MemorySigner, Signer};
85
86 #[test]
87 fn memory_signer_produces_stable_hmac() {
88 let signer = MemorySigner::new("secret");
89 let signature = signer.sign_hex(b"payload");
90 assert_eq!(
91 signature.as_deref(),
92 Ok("b82fcb791acec57859b989b430a826488ce2e479fdf92326bd0a2e8375a42ba4")
93 );
94 }
95}