#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]
use super::traits::{EcKeyPair, EcSignature, sealed};
use crate::prelude::error::{LatticeArcError, Result};
use crate::primitives::resource_limits::validate_signature_size;
use k256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Signer, signature::Verifier};
use rand_core_0_6::OsRng; use subtle::ConstantTimeEq;
use zeroize::Zeroizing;
pub struct Secp256k1KeyPair {
public_key: VerifyingKey,
secret_bytes: Zeroizing<[u8; 32]>,
}
impl std::fmt::Debug for Secp256k1KeyPair {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Secp256k1KeyPair")
.field("public_key", &self.public_key)
.field("secret_bytes", &"[REDACTED]")
.finish()
}
}
impl ConstantTimeEq for Secp256k1KeyPair {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.secret_bytes.ct_eq(&*other.secret_bytes)
}
}
impl Secp256k1KeyPair {
fn signing_key(&self) -> Result<SigningKey> {
SigningKey::from_bytes((&self.secret_bytes[..]).into()).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 signing key reconstruction failed");
LatticeArcError::KeyGenerationError("invalid secp256k1 secret key".to_string())
})
}
}
impl EcKeyPair for Secp256k1KeyPair {
fn generate() -> Result<Self> {
let sk = SigningKey::random(&mut OsRng {});
let public_key = VerifyingKey::from(&sk);
let mut secret_bytes = Zeroizing::new([0u8; 32]);
secret_bytes.copy_from_slice(&sk.to_bytes());
drop(sk);
let keypair = Self { public_key, secret_bytes };
crate::primitives::pct::pct_secp256k1(&keypair).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 keygen PCT failed");
LatticeArcError::KeyGenerationError("secp256k1 keypair PCT failed".to_string())
})?;
Ok(keypair)
}
fn from_secret_key(secret_key_bytes: &[u8]) -> Result<Self> {
if secret_key_bytes.len() != 32 {
return Err(LatticeArcError::InvalidKeyLength {
expected: 32,
actual: secret_key_bytes.len(),
});
}
let sk = SigningKey::from_bytes(secret_key_bytes.into()).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 from_secret_key parse failed");
LatticeArcError::InvalidKey("invalid secp256k1 secret key".to_string())
})?;
let public_key = VerifyingKey::from(&sk);
drop(sk);
let mut secret_bytes = Zeroizing::new([0u8; 32]);
secret_bytes.copy_from_slice(secret_key_bytes);
let keypair = Self { public_key, secret_bytes };
crate::primitives::pct::pct_secp256k1(&keypair).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 from_secret_key PCT failed");
LatticeArcError::KeyGenerationError("secp256k1 keypair PCT failed".to_string())
})?;
Ok(keypair)
}
fn public_key_bytes(&self) -> Vec<u8> {
self.public_key.to_encoded_point(false).as_bytes().to_vec()
}
fn secret_key_bytes(&self) -> Zeroizing<Vec<u8>> {
Zeroizing::new(self.secret_bytes.to_vec())
}
}
pub struct Secp256k1Signature;
impl sealed::Sealed for Secp256k1KeyPair {}
impl sealed::Sealed for Secp256k1Signature {}
impl EcSignature for Secp256k1Signature {
type Signature = Signature;
fn verify(public_key_bytes: &[u8], message: &[u8], signature: &Self::Signature) -> Result<()> {
if let Err(e) = validate_signature_size(message.len()) {
tracing::debug!(error = %e, msg_len = message.len(), "secp256k1 verify rejected: message exceeds resource limit");
return Err(LatticeArcError::SignatureVerificationError(
"secp256k1 verification failed".to_string(),
));
}
if let Some(_normalized) = signature.normalize_s() {
tracing::debug!("secp256k1 verify rejected: high-S signature (BIP-146/EIP-2)");
return Err(LatticeArcError::SignatureVerificationError(
"secp256k1 verification failed".to_string(),
));
}
let canonical_pk = public_key_bytes.len() == 65 && public_key_bytes.first() == Some(&0x04);
if !canonical_pk {
tracing::debug!(
pk_len = public_key_bytes.len(),
"secp256k1 verify rejected: non-canonical SEC1 encoding"
);
return Err(LatticeArcError::InvalidKey("invalid public key".to_string()));
}
let public_key = VerifyingKey::from_sec1_bytes(public_key_bytes).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 verify rejected: PK parse");
LatticeArcError::InvalidKey("invalid public key".to_string())
})?;
public_key.verify(message, signature).map_err(|_e| {
LatticeArcError::SignatureVerificationError("secp256k1 verification failed".to_string())
})
}
fn signature_len() -> usize {
64
}
fn signature_bytes(signature: &Self::Signature) -> Vec<u8> {
signature.to_bytes().to_vec()
}
fn signature_from_bytes(bytes: &[u8]) -> Result<Self::Signature> {
if bytes.len() != Self::signature_len() {
return Err(LatticeArcError::InvalidSignatureLength {
expected: Self::signature_len(),
got: bytes.len(),
});
}
let signature = Signature::from_bytes(bytes.into()).map_err(|e| {
tracing::debug!(error = %e, "secp256k1 signature parse failed");
LatticeArcError::InvalidSignature("invalid secp256k1 signature".to_string())
})?;
if signature.normalize_s().is_some() {
tracing::debug!("secp256k1 signature_from_bytes rejected: high-S (BIP-146/EIP-2)");
return Err(LatticeArcError::InvalidSignature(
"invalid secp256k1 signature".to_string(),
));
}
Ok(signature)
}
}
impl Secp256k1KeyPair {
pub fn sign(&self, message: &[u8]) -> Result<Signature> {
if let Err(e) = validate_signature_size(message.len()) {
tracing::debug!(error = %e, msg_len = message.len(), "secp256k1 sign rejected: message exceeds resource limit");
return Err(LatticeArcError::MessageTooLong);
}
let sk = self.signing_key()?;
let signature: Signature = sk.sign(message);
Ok(signature.normalize_s().unwrap_or(signature))
}
}
#[cfg(test)]
#[expect(clippy::panic_in_result_fn, reason = "Tests use assertions for verification")]
mod tests {
use super::*;
use crate::prelude::error::Result;
#[test]
fn test_secp256k1_keypair_generation_succeeds() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
assert_eq!(keypair.secret_key_bytes().len(), 32);
assert_eq!(keypair.public_key_bytes().len(), 65);
Ok(())
}
#[test]
fn test_secp256k1_keypair_from_secret_succeeds() -> Result<()> {
let original = Secp256k1KeyPair::generate()?;
let secret_bytes = original.secret_key_bytes();
let reconstructed = Secp256k1KeyPair::from_secret_key(&secret_bytes)?;
assert_eq!(original.public_key_bytes(), reconstructed.public_key_bytes());
Ok(())
}
#[test]
fn test_secp256k1_sign_verify_roundtrip() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
let message = b"Hello, secp256k1!";
let signature = keypair.sign(message)?;
let public_key_bytes = keypair.public_key_bytes();
Secp256k1Signature::verify(&public_key_bytes, message, &signature)?;
let wrong_message = b"Wrong message";
assert!(Secp256k1Signature::verify(&public_key_bytes, wrong_message, &signature).is_err());
Ok(())
}
#[test]
fn test_secp256k1_signature_serialization_succeeds() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
let message = b"Test message";
let signature = keypair.sign(message)?;
let sig_bytes = Secp256k1Signature::signature_bytes(&signature);
assert_eq!(sig_bytes.len(), 64);
let reconstructed_sig = Secp256k1Signature::signature_from_bytes(&sig_bytes)?;
assert_eq!(signature, reconstructed_sig);
Ok(())
}
#[test]
fn test_secp256k1_from_secret_key_invalid_length_fails() {
let too_short = vec![0u8; 16];
let result = Secp256k1KeyPair::from_secret_key(&too_short);
assert!(result.is_err());
let too_long = vec![0u8; 64];
let result = Secp256k1KeyPair::from_secret_key(&too_long);
assert!(result.is_err());
}
#[test]
fn test_secp256k1_signature_from_bytes_invalid_length_fails() {
let too_short = vec![0u8; 32];
let result = Secp256k1Signature::signature_from_bytes(&too_short);
assert!(result.is_err());
let too_long = vec![0u8; 128];
let result = Secp256k1Signature::signature_from_bytes(&too_long);
assert!(result.is_err());
}
#[test]
fn test_secp256k1_byte_accessors_return_correct_lengths_has_correct_size() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
assert_eq!(keypair.public_key_bytes().len(), 65);
assert_eq!(keypair.secret_key_bytes().len(), 32);
Ok(())
}
#[test]
fn test_secp256k1_signature_len_is_64_bytes_succeeds() {
assert_eq!(Secp256k1Signature::signature_len(), 64);
}
#[test]
fn test_secp256k1_verify_invalid_public_key_fails() {
let invalid_pk = vec![0u8; 10];
let sig = Signature::from_bytes(&[0u8; 64].into());
if let Ok(sig) = sig {
let result = Secp256k1Signature::verify(&invalid_pk, b"test", &sig);
assert!(result.is_err());
}
}
const SECP256K1_ORDER_BE: [u8; 32] = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36,
0x41, 0x41,
];
fn negate_scalar_mod_n(s: [u8; 32]) -> [u8; 32] {
let mut result = [0u8; 32];
let mut borrow: i16 = 0;
for i in (0..32).rev() {
let n_byte = SECP256K1_ORDER_BE[i] as i16;
let s_byte = s[i] as i16;
let mut diff = n_byte - s_byte - borrow;
if diff < 0 {
diff += 256;
borrow = 1;
} else {
borrow = 0;
}
result[i] = diff as u8;
}
result
}
#[test]
fn test_secp256k1_high_s_signature_from_bytes_rejected() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
let message = b"high-S parse rejection test";
let sig = keypair.sign(message)?;
let mut sig_bytes = Secp256k1Signature::signature_bytes(&sig);
let mut s_le = [0u8; 32];
s_le.copy_from_slice(&sig_bytes[32..64]);
let high_s = negate_scalar_mod_n(s_le);
sig_bytes[32..64].copy_from_slice(&high_s);
let parsed = Secp256k1Signature::signature_from_bytes(&sig_bytes);
assert!(
parsed.is_err(),
"high-S signature must be rejected by signature_from_bytes (BIP-146/EIP-2)"
);
Ok(())
}
#[test]
fn test_secp256k1_high_s_signature_verify_rejected() -> Result<()> {
let keypair = Secp256k1KeyPair::generate()?;
let message = b"high-S verify rejection test";
let sig = keypair.sign(message)?;
let sig_bytes = Secp256k1Signature::signature_bytes(&sig);
let mut s_le = [0u8; 32];
s_le.copy_from_slice(&sig_bytes[32..64]);
let high_s = negate_scalar_mod_n(s_le);
let mut high_sig_bytes = [0u8; 64];
high_sig_bytes[..32].copy_from_slice(&sig_bytes[..32]);
high_sig_bytes[32..].copy_from_slice(&high_s);
let high_sig = Signature::from_slice(&high_sig_bytes)
.map_err(|e| LatticeArcError::InvalidSignature(format!("test setup: {e}")))?;
assert!(
high_sig.normalize_s().is_some(),
"test setup bug: constructed signature is not high-S"
);
let pk_bytes = keypair.public_key_bytes();
let result = Secp256k1Signature::verify(&pk_bytes, message, &high_sig);
assert!(result.is_err(), "high-S signature must be rejected by verify (BIP-146/EIP-2)");
Ok(())
}
#[test]
fn test_negate_scalar_mod_n_is_involutive() {
let s: [u8; 32] = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67,
0x89, 0xAB, 0xCD, 0xEF,
];
let high = negate_scalar_mod_n(s);
let back = negate_scalar_mod_n(high);
assert_eq!(back, s, "negate is not involutive — helper is broken");
}
}