1use std::time::{SystemTime, UNIX_EPOCH};
6
7use alloy::hex;
8use base64ct::{Base64UrlUnpadded, Encoding};
9use ed25519_dalek::{SigningKey, ed25519::signature::SignerMut};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 constants::CRYPTO_JWT_TTL,
14 utils::{encode_iss, random_bytes32},
15};
16
17pub struct RelayAuth {
18 client_seed: [u8; 32],
19}
20
21impl RelayAuth {
22 pub fn new(client_seed: [u8; 32]) -> Self {
23 Self { client_seed }
24 }
25
26 pub fn get_client_id(&self) -> String {
28 let seed = self.client_seed;
29 let key_pair = Keypair::from_seed(seed);
30 encode_iss(&key_pair.public_key)
31 }
32
33 pub fn sign_jwt(&self, aud: &str) -> String {
35 let keypair = Keypair::from_seed(self.client_seed);
36
37 let sub = random_bytes32(); let ttl = CRYPTO_JWT_TTL;
39 sign_jwt(&hex::encode(sub), aud, ttl, &keypair, None)
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct Keypair {
46 pub seed: [u8; 32],
47 pub secret_key: [u8; 64],
48 pub public_key: [u8; 32],
49}
50
51impl Keypair {
52 pub fn generate() -> Self {
53 Keypair::from_seed(random_bytes32())
54 }
55
56 pub fn sign(&self, data: &[u8]) -> [u8; 64] {
57 let mut signing_key = SigningKey::from(self.seed);
58 signing_key.sign(data).to_bytes()
59 }
60
61 pub fn from_bytes64_secret(secret_key: [u8; 64]) -> Self {
62 let mut seed = [0u8; 32];
63 let mut public_key = [0u8; 32];
64
65 seed.copy_from_slice(&secret_key[..32]);
66 public_key.copy_from_slice(&secret_key[32..]);
67
68 let signing_key = SigningKey::from_bytes(&seed);
69 let calculated_public_key = signing_key.verifying_key().to_bytes();
70
71 assert_eq!(
72 public_key, calculated_public_key,
73 "Public key does not match the signing key"
74 );
75
76 Keypair {
77 seed,
78 secret_key,
79 public_key,
80 }
81 }
82
83 pub fn from_seed(seed: [u8; 32]) -> Keypair {
85 let signing_key = SigningKey::from_bytes(&seed);
86 let public_key = signing_key.verifying_key().to_bytes();
87
88 let mut secret_key = [0u8; 64];
89
90 secret_key[..32].copy_from_slice(&seed);
91 secret_key[32..].copy_from_slice(&public_key);
92
93 Keypair {
94 seed,
95 secret_key,
96 public_key,
97 }
98 }
99}
100
101#[derive(Serialize, Deserialize)]
102pub struct IridiumJWTHeader {
103 pub alg: &'static str,
104 pub typ: &'static str,
105}
106
107#[derive(Serialize, Deserialize)]
108pub struct IridiumJWTPayload {
109 pub iss: String,
110 pub sub: String,
111 pub aud: String,
112 pub iat: u64,
113 pub exp: u64,
114}
115
116#[derive(Serialize)]
117pub struct IridiumJWTSigned<'a> {
118 pub header: &'a IridiumJWTHeader,
119 pub payload: &'a IridiumJWTPayload,
120 pub signature: String,
121}
122
123fn encode_json<T: ?Sized + Serialize>(val: &T) -> String {
124 Base64UrlUnpadded::encode_string(
125 serde_json::to_string(val).unwrap().as_bytes(),
126 )
127}
128
129fn encode_data(
130 header: &IridiumJWTHeader,
131 payload: &IridiumJWTPayload,
132) -> (Vec<u8>, String) {
133 let h = encode_json(header);
134 let p = encode_json(payload);
135 let joined = format!("{h}.{p}");
136 (joined.as_bytes().to_vec(), joined)
137}
138
139pub fn sign_jwt(
140 sub: &str,
141 aud: &str,
142 ttl: u64,
143 keypair: &Keypair,
144 iat_opt: Option<u64>,
145) -> String {
146 let iat = iat_opt.unwrap_or_else(|| {
147 SystemTime::now()
148 .duration_since(UNIX_EPOCH)
149 .unwrap()
150 .as_secs()
151 });
152 let exp = iat + ttl;
153
154 let header = IridiumJWTHeader {
155 alg: "EdDSA",
156 typ: "JWT",
157 };
158 let iss = encode_iss(&keypair.public_key);
159 let payload = IridiumJWTPayload {
160 iss,
161 sub: sub.to_string(),
162 aud: aud.to_string(),
163 iat,
164 exp,
165 };
166
167 let (data, jwt_head_payload) = encode_data(&header, &payload);
168
169 let signature = keypair.sign(&data);
170 let sig_encoded = Base64UrlUnpadded::encode_string(&signature);
171
172 format!("{jwt_head_payload}.{sig_encoded}")
173}
174
175#[cfg(test)]
176mod test {
177 use super::RelayAuth;
178
179 #[test]
180 fn test_1() {
181 let wk = RelayAuth::new([0; 32]);
182 let client_id = wk.get_client_id();
183 assert_eq!(
184 client_id,
185 "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
186 );
187 }
188
189 #[test]
190 fn test_2() {
191 let wk = RelayAuth::new([
192 23, 113, 199, 94, 246, 41, 119, 10, 250, 248, 253, 136, 173, 241,
193 191, 149, 165, 249, 17, 42, 46, 189, 120, 175, 78, 88, 53, 83, 254,
194 16, 32, 150,
195 ]);
196 let client_id = wk.get_client_id();
197 assert_eq!(
198 client_id,
199 "did:key:z6MkriJMhx6cLMiwwfuJ3NCGw8C8UjB9KoVHB7QSBaBxMx3y"
200 );
201 }
202}