use blueprint_std::rand::{CryptoRng, Rng};
pub mod api_keys;
pub mod api_tokens;
pub mod auth_token;
pub mod certificate_authority;
pub mod db;
pub mod models;
pub mod oauth;
pub mod paseto_tokens;
pub mod proxy;
pub mod request_auth;
pub mod request_extensions;
pub mod tls_assets;
pub mod tls_client;
pub mod tls_envelope;
pub mod tls_listener;
pub mod types;
pub mod validation;
#[cfg(test)]
mod test_client;
#[cfg(test)]
mod tests;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("k256 error: {0}")]
K256(k256::ecdsa::Error),
#[error("Schnorrkel error: {0}")]
Schnorrkel(schnorrkel::SignatureError),
#[error("BN254 BLS error: {0}")]
Bn254Bls(String),
#[error(transparent)]
RocksDB(#[from] rocksdb::Error),
#[error("Invalid database compaction style: {0}")]
InvalidDBCompactionStyle(String),
#[error("Invalid database compression type: {0}")]
InvalidDBCompressionType(String),
#[error("unknown database column family: {0}")]
UnknownColumnFamily(&'static str),
#[error(transparent)]
ProtobufDecode(#[from] prost::DecodeError),
#[error("Unknown key type")]
UnknownKeyType,
#[error(transparent)]
Uri(#[from] axum::http::uri::InvalidUri),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("TLS envelope error: {0}")]
TlsEnvelope(#[from] crate::tls_envelope::TlsEnvelopeError),
#[error("Certificate generation error: {0}")]
Certificate(#[from] rcgen::Error),
#[error("TLS error: {0}")]
Tls(String),
#[error("Service not found: {0}")]
ServiceNotFound(crate::types::ServiceId),
}
pub fn generate_challenge<R: Rng + CryptoRng>(rng: &mut R) -> [u8; 32] {
let mut challenge = [0u8; 32];
rng.fill(&mut challenge);
challenge
}
pub fn verify_challenge(
challenge: &[u8; 32],
signature: &[u8],
pub_key: &[u8],
key_type: types::KeyType,
) -> Result<bool, Error> {
match key_type {
types::KeyType::Unknown => Err(Error::UnknownKeyType),
types::KeyType::Ecdsa => verify_challenge_ecdsa(challenge, signature, pub_key),
types::KeyType::Sr25519 => verify_challenge_sr25519(challenge, signature, pub_key),
types::KeyType::Bn254Bls => verify_challenge_bn254_bls(challenge, signature, pub_key),
}
}
fn verify_challenge_ecdsa(
challenge: &[u8; 32],
signature: &[u8],
pub_key: &[u8],
) -> Result<bool, Error> {
use k256::ecdsa::signature::hazmat::PrehashVerifier;
let pub_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(pub_key).map_err(Error::K256)?;
let signature = k256::ecdsa::Signature::try_from(signature).map_err(Error::K256)?;
Ok(pub_key.verify_prehash(challenge, &signature).is_ok())
}
fn verify_challenge_sr25519(
challenge: &[u8; 32],
signature: &[u8],
pub_key: &[u8],
) -> Result<bool, Error> {
const CTX: &[u8] = b"substrate";
let pub_key = schnorrkel::PublicKey::from_bytes(pub_key).map_err(Error::Schnorrkel)?;
let signature = schnorrkel::Signature::from_bytes(signature).map_err(Error::Schnorrkel)?;
Ok(pub_key.verify_simple(CTX, challenge, &signature).is_ok())
}
fn verify_challenge_bn254_bls(
challenge: &[u8; 32],
signature: &[u8],
pub_key: &[u8],
) -> Result<bool, Error> {
use blueprint_crypto::BytesEncoding;
use blueprint_crypto::bn254::{ArkBlsBn254Public, ArkBlsBn254Signature};
let public_key = ArkBlsBn254Public::from_bytes(pub_key)
.map_err(|e| Error::Bn254Bls(format!("Invalid public key: {e:?}")))?;
let sig = ArkBlsBn254Signature::from_bytes(signature)
.map_err(|e| Error::Bn254Bls(format!("Invalid signature: {e:?}")))?;
Ok(blueprint_crypto::bn254::verify(
public_key.0,
challenge,
sig.0,
))
}
#[cfg(test)]
mod lib_tests {
use super::*;
use crate::types::{KeyType, VerifyChallengeRequest};
use k256::ecdsa::SigningKey;
#[test]
fn test_generate_challenge() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge1 = generate_challenge(&mut rng);
let challenge2 = generate_challenge(&mut rng);
assert_ne!(challenge1, challenge2);
assert_ne!(challenge1, [0u8; 32]);
}
#[test]
fn test_verify_challenge_ecdsa_valid() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let signing_key = SigningKey::random(&mut rng);
let verification_key = signing_key.verifying_key();
let public_key = verification_key.to_sec1_bytes();
let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
let result =
verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_verify_challenge_ecdsa_invalid_signature() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let different_challenge = generate_challenge(&mut rng);
let signing_key = SigningKey::random(&mut rng);
let verification_key = signing_key.verifying_key();
let public_key = verification_key.to_sec1_bytes();
let signature = &signing_key
.sign_prehash_recoverable(&different_challenge)
.unwrap()
.0;
let result =
verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_verify_challenge_ecdsa_invalid_key() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let signing_key = SigningKey::random(&mut rng);
let different_signing_key = SigningKey::random(&mut rng);
let different_verification_key = different_signing_key.verifying_key();
let different_public_key = different_verification_key.to_sec1_bytes();
let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
let result = verify_challenge_ecdsa(
&challenge,
signature.to_bytes().as_slice(),
&different_public_key,
);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_verify_challenge_unknown_key_type() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let result = verify_challenge(&challenge, &[0u8; 64], &[0u8; 33], KeyType::Unknown);
assert!(matches!(result, Err(Error::UnknownKeyType)));
}
#[test]
fn test_verify_challenge_integration() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let signing_key = SigningKey::random(&mut rng);
let verification_key = signing_key.verifying_key();
let public_key = verification_key.to_sec1_bytes();
let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
let result = verify_challenge(
&challenge,
signature.to_bytes().as_slice(),
&public_key,
KeyType::Ecdsa,
);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_verify_challenge_sr25519_error_handling() {
let mut rng = blueprint_std::BlueprintRng::new();
let challenge = generate_challenge(&mut rng);
let invalid_signature = [0u8; 64];
let invalid_pub_key = [0u8; 32];
let result = verify_challenge_sr25519(&challenge, &invalid_signature, &invalid_pub_key);
assert!(result.is_err());
match result {
Err(Error::Schnorrkel(_)) => {}
_ => panic!("Expected Schnorrkel error"),
}
}
#[test]
fn js_compat_ecdsa() {
let data = serde_json::json!({
"pub_key": "020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
"key_type": "Ecdsa",
"challenge": "0000000000000000000000000000000000000000000000000000000000000000",
"signature": "26138be19cfc76e800bdcbba5e3bbc5bd79168cd06ea6afd5be6860d23d5e0340c728508ca0b47b49627b5560fbca6cdd92cbf6ac402d0941bba7e42b9d7a20c",
"expires_at": 0
});
let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
let result = verify_challenge(
&req.challenge,
&req.signature,
&req.challenge_request.pub_key,
req.challenge_request.key_type,
);
assert!(
result.is_ok(),
"Failed to verify ECDSA challenge: {}",
result.err().unwrap()
);
assert!(result.is_ok(), "ECDSA verification failed");
}
#[test]
fn js_compat_sr25519() {
let data = serde_json::json!({
"pub_key": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
"key_type": "Sr25519",
"challenge": "0000000000000000000000000000000000000000000000000000000000000000",
"signature": "f05fa2a2074d5295a34aae0d5383792a6cc34304c9cb4f6a0c577df4b374fe7bab051bd7570415578ba2da67e056d8f89b420d2e5b82412dc0f0e02877b9e48c",
"expires_at": 0
});
let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
let result = verify_challenge(
&req.challenge,
&req.signature,
&req.challenge_request.pub_key,
req.challenge_request.key_type,
);
assert!(
result.is_ok(),
"Failed to verify Sr25519 challenge: {}",
result.err().unwrap()
);
assert!(result.is_ok(), "Sr25519 verification failed");
}
}