1use blueprint_std::rand::{CryptoRng, Rng};
40
41pub mod api_keys;
43pub mod api_tokens;
45pub mod auth_token;
47pub mod certificate_authority;
49pub mod db;
51pub mod models;
53pub mod oauth;
55pub mod paseto_tokens;
57pub mod proxy;
59pub mod request_auth;
61pub mod request_extensions;
63pub mod tls_assets;
65pub mod tls_client;
67pub mod tls_envelope;
69pub mod tls_listener;
71pub mod types;
73pub mod validation;
75
76#[cfg(test)]
77mod test_client;
78
79#[cfg(test)]
80mod tests;
81
82#[derive(Debug, thiserror::Error)]
83pub enum Error {
84 #[error("k256 error: {0}")]
86 K256(k256::ecdsa::Error),
87
88 #[error("Schnorrkel error: {0}")]
90 Schnorrkel(schnorrkel::SignatureError),
91
92 #[error("BN254 BLS error: {0}")]
94 Bn254Bls(String),
95
96 #[error(transparent)]
97 RocksDB(#[from] rocksdb::Error),
98
99 #[error("Invalid database compaction style: {0}")]
100 InvalidDBCompactionStyle(String),
101
102 #[error("Invalid database compression type: {0}")]
103 InvalidDBCompressionType(String),
104
105 #[error("unknown database column family: {0}")]
106 UnknownColumnFamily(&'static str),
107
108 #[error(transparent)]
109 ProtobufDecode(#[from] prost::DecodeError),
110
111 #[error("Unknown key type")]
112 UnknownKeyType,
113
114 #[error(transparent)]
115 Uri(#[from] axum::http::uri::InvalidUri),
116
117 #[error("IO error: {0}")]
118 Io(#[from] std::io::Error),
119
120 #[error("TLS envelope error: {0}")]
121 TlsEnvelope(#[from] crate::tls_envelope::TlsEnvelopeError),
122
123 #[error("Certificate generation error: {0}")]
124 Certificate(#[from] rcgen::Error),
125
126 #[error("TLS error: {0}")]
127 Tls(String),
128
129 #[error("Service not found: {0}")]
130 ServiceNotFound(crate::types::ServiceId),
131}
132
133pub fn generate_challenge<R: Rng + CryptoRng>(rng: &mut R) -> [u8; 32] {
137 let mut challenge = [0u8; 32];
138 rng.fill(&mut challenge);
139 challenge
140}
141
142pub fn verify_challenge(
144 challenge: &[u8; 32],
145 signature: &[u8],
146 pub_key: &[u8],
147 key_type: types::KeyType,
148) -> Result<bool, Error> {
149 match key_type {
150 types::KeyType::Unknown => Err(Error::UnknownKeyType),
151 types::KeyType::Ecdsa => verify_challenge_ecdsa(challenge, signature, pub_key),
152 types::KeyType::Sr25519 => verify_challenge_sr25519(challenge, signature, pub_key),
153 types::KeyType::Bn254Bls => verify_challenge_bn254_bls(challenge, signature, pub_key),
154 }
155}
156
157fn verify_challenge_ecdsa(
159 challenge: &[u8; 32],
160 signature: &[u8],
161 pub_key: &[u8],
162) -> Result<bool, Error> {
163 use k256::ecdsa::signature::hazmat::PrehashVerifier;
164 let pub_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(pub_key).map_err(Error::K256)?;
165 let signature = k256::ecdsa::Signature::try_from(signature).map_err(Error::K256)?;
166 Ok(pub_key.verify_prehash(challenge, &signature).is_ok())
167}
168
169fn verify_challenge_sr25519(
173 challenge: &[u8; 32],
174 signature: &[u8],
175 pub_key: &[u8],
176) -> Result<bool, Error> {
177 const CTX: &[u8] = b"substrate";
178 let pub_key = schnorrkel::PublicKey::from_bytes(pub_key).map_err(Error::Schnorrkel)?;
179 let signature = schnorrkel::Signature::from_bytes(signature).map_err(Error::Schnorrkel)?;
180 Ok(pub_key.verify_simple(CTX, challenge, &signature).is_ok())
181}
182
183fn verify_challenge_bn254_bls(
188 challenge: &[u8; 32],
189 signature: &[u8],
190 pub_key: &[u8],
191) -> Result<bool, Error> {
192 use blueprint_crypto::BytesEncoding;
193 use blueprint_crypto::bn254::{ArkBlsBn254Public, ArkBlsBn254Signature};
194
195 let public_key = ArkBlsBn254Public::from_bytes(pub_key)
196 .map_err(|e| Error::Bn254Bls(format!("Invalid public key: {e:?}")))?;
197 let sig = ArkBlsBn254Signature::from_bytes(signature)
198 .map_err(|e| Error::Bn254Bls(format!("Invalid signature: {e:?}")))?;
199
200 Ok(blueprint_crypto::bn254::verify(
201 public_key.0,
202 challenge,
203 sig.0,
204 ))
205}
206
207#[cfg(test)]
208mod lib_tests {
209 use super::*;
210
211 use crate::types::{KeyType, VerifyChallengeRequest};
212 use k256::ecdsa::SigningKey;
213
214 #[test]
215 fn test_generate_challenge() {
216 let mut rng = blueprint_std::BlueprintRng::new();
218 let challenge1 = generate_challenge(&mut rng);
219
220 let challenge2 = generate_challenge(&mut rng);
222
223 assert_ne!(challenge1, challenge2);
225
226 assert_ne!(challenge1, [0u8; 32]);
228 }
229
230 #[test]
231 fn test_verify_challenge_ecdsa_valid() {
232 let mut rng = blueprint_std::BlueprintRng::new();
233
234 let challenge = generate_challenge(&mut rng);
236
237 let signing_key = SigningKey::random(&mut rng);
239 let verification_key = signing_key.verifying_key();
240 let public_key = verification_key.to_sec1_bytes();
241
242 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
244
245 let result =
247 verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
248 assert!(result.is_ok());
249 assert!(result.unwrap());
250 }
251
252 #[test]
253 fn test_verify_challenge_ecdsa_invalid_signature() {
254 let mut rng = blueprint_std::BlueprintRng::new();
255
256 let challenge = generate_challenge(&mut rng);
258 let different_challenge = generate_challenge(&mut rng);
259
260 let signing_key = SigningKey::random(&mut rng);
262 let verification_key = signing_key.verifying_key();
263 let public_key = verification_key.to_sec1_bytes();
264
265 let signature = &signing_key
267 .sign_prehash_recoverable(&different_challenge)
268 .unwrap()
269 .0;
270 let result =
272 verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
273 assert!(result.is_ok());
274 assert!(!result.unwrap());
275 }
276
277 #[test]
278 fn test_verify_challenge_ecdsa_invalid_key() {
279 let mut rng = blueprint_std::BlueprintRng::new();
280
281 let challenge = generate_challenge(&mut rng);
283
284 let signing_key = SigningKey::random(&mut rng);
286
287 let different_signing_key = SigningKey::random(&mut rng);
289 let different_verification_key = different_signing_key.verifying_key();
290 let different_public_key = different_verification_key.to_sec1_bytes();
291
292 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
294
295 let result = verify_challenge_ecdsa(
297 &challenge,
298 signature.to_bytes().as_slice(),
299 &different_public_key,
300 );
301 assert!(result.is_ok());
302 assert!(!result.unwrap());
303 }
304
305 #[test]
306 fn test_verify_challenge_unknown_key_type() {
307 let mut rng = blueprint_std::BlueprintRng::new();
308 let challenge = generate_challenge(&mut rng);
309 let result = verify_challenge(&challenge, &[0u8; 64], &[0u8; 33], KeyType::Unknown);
310 assert!(matches!(result, Err(Error::UnknownKeyType)));
311 }
312
313 #[test]
314 fn test_verify_challenge_integration() {
315 let mut rng = blueprint_std::BlueprintRng::new();
316
317 let challenge = generate_challenge(&mut rng);
319
320 let signing_key = SigningKey::random(&mut rng);
322 let verification_key = signing_key.verifying_key();
323 let public_key = verification_key.to_sec1_bytes();
324
325 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
327
328 let result = verify_challenge(
330 &challenge,
331 signature.to_bytes().as_slice(),
332 &public_key,
333 KeyType::Ecdsa,
334 );
335 assert!(result.is_ok());
336 assert!(result.unwrap());
337 }
338
339 #[test]
340 fn test_verify_challenge_sr25519_error_handling() {
341 let mut rng = blueprint_std::BlueprintRng::new();
342 let challenge = generate_challenge(&mut rng);
343
344 let invalid_signature = [0u8; 64];
346 let invalid_pub_key = [0u8; 32];
347
348 let result = verify_challenge_sr25519(&challenge, &invalid_signature, &invalid_pub_key);
350 assert!(result.is_err());
351
352 match result {
354 Err(Error::Schnorrkel(_)) => {}
355 _ => panic!("Expected Schnorrkel error"),
356 }
357 }
358
359 #[test]
360 fn js_compat_ecdsa() {
361 let data = serde_json::json!({
364 "pub_key": "020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
365 "key_type": "Ecdsa",
366 "challenge": "0000000000000000000000000000000000000000000000000000000000000000",
367 "signature": "26138be19cfc76e800bdcbba5e3bbc5bd79168cd06ea6afd5be6860d23d5e0340c728508ca0b47b49627b5560fbca6cdd92cbf6ac402d0941bba7e42b9d7a20c",
368 "expires_at": 0
369 });
370
371 let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
372 let result = verify_challenge(
373 &req.challenge,
374 &req.signature,
375 &req.challenge_request.pub_key,
376 req.challenge_request.key_type,
377 );
378 assert!(
379 result.is_ok(),
380 "Failed to verify ECDSA challenge: {}",
381 result.err().unwrap()
382 );
383 assert!(result.is_ok(), "ECDSA verification failed");
384 }
385
386 #[test]
387 fn js_compat_sr25519() {
388 let data = serde_json::json!({
391 "pub_key": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
392 "key_type": "Sr25519",
393 "challenge": "0000000000000000000000000000000000000000000000000000000000000000",
394 "signature": "f05fa2a2074d5295a34aae0d5383792a6cc34304c9cb4f6a0c577df4b374fe7bab051bd7570415578ba2da67e056d8f89b420d2e5b82412dc0f0e02877b9e48c",
395 "expires_at": 0
396 });
397
398 let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
399 let result = verify_challenge(
400 &req.challenge,
401 &req.signature,
402 &req.challenge_request.pub_key,
403 req.challenge_request.key_type,
404 );
405 assert!(
406 result.is_ok(),
407 "Failed to verify Sr25519 challenge: {}",
408 result.err().unwrap()
409 );
410 assert!(result.is_ok(), "Sr25519 verification failed");
411 }
412}