A library for ATProtocol identities.
1//! ECDSA signature normalization.
2//!
3//! This module handles signature normalization to the low-S form required by
4//! the AT Protocol attestation specification, preventing signature malleability attacks.
5
6use crate::errors::AttestationError;
7use atproto_identity::key::KeyType;
8use k256::ecdsa::Signature as K256Signature;
9use p256::ecdsa::Signature as P256Signature;
10
11/// Normalize raw signature bytes to the required low-S form.
12///
13/// This helper ensures signatures produced by signing APIs comply with the
14/// specification requirements before embedding them in attestation objects.
15///
16/// # Arguments
17///
18/// * `signature` - The raw signature bytes to normalize
19/// * `key_type` - The type of key used to create the signature
20///
21/// # Returns
22///
23/// The normalized signature bytes in low-S form
24///
25/// # Errors
26///
27/// Returns an error if:
28/// - The signature length is invalid for the key type
29/// - The key type is not supported
30pub fn normalize_signature(
31 signature: Vec<u8>,
32 key_type: &KeyType,
33) -> Result<Vec<u8>, AttestationError> {
34 match key_type {
35 KeyType::P256Private | KeyType::P256Public => normalize_p256(signature),
36 KeyType::K256Private | KeyType::K256Public => normalize_k256(signature),
37 other => Err(AttestationError::UnsupportedKeyType {
38 key_type: (*other).clone(),
39 }),
40 }
41}
42
43/// Normalize a P-256 signature to low-S form.
44fn normalize_p256(signature: Vec<u8>) -> Result<Vec<u8>, AttestationError> {
45 if signature.len() != 64 {
46 return Err(AttestationError::SignatureLengthInvalid {
47 expected: 64,
48 actual: signature.len(),
49 });
50 }
51
52 let parsed = P256Signature::from_slice(&signature).map_err(|_| {
53 AttestationError::SignatureLengthInvalid {
54 expected: 64,
55 actual: signature.len(),
56 }
57 })?;
58
59 let normalized = parsed.normalize_s().unwrap_or(parsed);
60
61 Ok(normalized.to_vec())
62}
63
64/// Normalize a K-256 signature to low-S form.
65fn normalize_k256(signature: Vec<u8>) -> Result<Vec<u8>, AttestationError> {
66 if signature.len() != 64 {
67 return Err(AttestationError::SignatureLengthInvalid {
68 expected: 64,
69 actual: signature.len(),
70 });
71 }
72
73 let parsed = K256Signature::from_slice(&signature).map_err(|_| {
74 AttestationError::SignatureLengthInvalid {
75 expected: 64,
76 actual: signature.len(),
77 }
78 })?;
79
80 let normalized = parsed.normalize_s().unwrap_or(parsed);
81
82 Ok(normalized.to_vec())
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn reject_invalid_signature_length() {
91 let short_signature = vec![0u8; 32];
92 let result = normalize_p256(short_signature);
93 assert!(matches!(
94 result,
95 Err(AttestationError::SignatureLengthInvalid { expected: 64, .. })
96 ));
97 }
98}