use crate::{Algorithm, Error};
#[cfg(feature = "algo-secp256k1")]
use k256::ecdsa::{
Signature as K256Sig, SigningKey, VerifyingKey,
signature::hazmat::{PrehashSigner, PrehashVerifier},
};
#[cfg(feature = "algo-secp256k1")]
use sha2::{Digest, Sha256};
#[cfg(feature = "algo-secp256k1")]
#[derive(Debug, Clone)]
pub struct Secp256k1Signer {
sk: SigningKey,
}
#[cfg(feature = "algo-secp256k1")]
impl Secp256k1Signer {
pub fn new(mut secret: [u8; 32]) -> Result<Self, Error> {
use zeroize::Zeroize;
let result =
SigningKey::from_bytes((&secret).into()).map_err(|_| Error::Secp256k1KeyInvalid);
secret.zeroize();
let sk = result?;
Ok(Self { sk })
}
pub fn from_seed_zeroizing(secret: &zeroize::Zeroizing<[u8; 32]>) -> Result<Self, Error> {
use zeroize::Zeroize;
let mut tmp = [0u8; 32];
tmp.copy_from_slice(secret.as_slice());
let result = SigningKey::from_bytes((&tmp).into()).map_err(|_| Error::Secp256k1KeyInvalid);
tmp.zeroize();
let sk = result?;
Ok(Self { sk })
}
pub fn from_der_pkcs8(bytes: &[u8]) -> Result<Self, Error> {
use k256::pkcs8::DecodePrivateKey;
let sk = SigningKey::from_pkcs8_der(bytes).map_err(|_| Error::Secp256k1KeyInvalid)?;
Ok(Self { sk })
}
#[must_use]
pub fn public_key_sec1(&self) -> Vec<u8> {
self.sk
.verifying_key()
.to_sec1_point(true)
.as_bytes()
.to_vec()
}
#[must_use]
pub fn algorithm(&self) -> Algorithm {
Algorithm::Secp256k1
}
#[must_use]
pub fn keyid_string(&self) -> String {
let pk = self.public_key_sec1();
format!("secp256k1:{}", hex_lower(&pk))
}
pub fn sign_dsse(&self, pae: &[u8]) -> Result<Vec<u8>, Error> {
let mut h = Sha256::new();
h.update(pae);
let sig: K256Sig = self
.sk
.sign_prehash(&h.finalize())
.map_err(|_| Error::Secp256k1SignatureInvalid)?;
Ok(sig.to_bytes().to_vec())
}
}
#[cfg(feature = "algo-secp256k1")]
impl crate::signer::Signer for Secp256k1Signer {
fn algorithm(&self) -> Algorithm {
Algorithm::Secp256k1
}
fn keyid(&self) -> Result<String, Error> {
Ok(self.keyid_string())
}
fn sign(&mut self, pae: &[u8]) -> Result<Vec<u8>, Error> {
self.sign_dsse(pae)
}
}
#[cfg(feature = "algo-secp256k1")]
pub fn verify_secp256k1(pubkey_sec1: &[u8], msg: &[u8], sig_compact: &[u8]) -> Result<(), Error> {
let vk = VerifyingKey::from_sec1_bytes(pubkey_sec1).map_err(|_| Error::Secp256k1KeyInvalid)?;
if sig_compact.len() != 64 {
return Err(Error::Secp256k1SignatureInvalid);
}
let sig = K256Sig::from_slice(sig_compact).map_err(|_| Error::Secp256k1SignatureInvalid)?;
if sig.normalize_s() != sig {
return Err(Error::Secp256k1VerifyFailed);
}
let mut h = Sha256::new();
h.update(msg);
let digest = h.finalize();
vk.verify_prehash(&digest, &sig)
.map_err(|_| Error::Secp256k1VerifyFailed)?;
Ok(())
}
#[cfg(feature = "algo-secp256k1")]
fn hex_lower(b: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(b.len() * 2);
for byte in b {
s.push(HEX[(byte >> 4) as usize] as char);
s.push(HEX[(byte & 0x0F) as usize] as char);
}
s
}
#[cfg(all(test, feature = "algo-secp256k1"))]
mod tests {
use super::*;
use crate::signer::Signer;
fn fixed_secret() -> [u8; 32] {
let mut k = [0u8; 32];
k[31] = 1;
k
}
const FIXED_PAE: &[u8] = b"DSSEv1 28 application/vnd.in-toto+json 2 {}";
#[test]
fn sign_verify_roundtrip_secp256k1() {
let signer = Secp256k1Signer::new(fixed_secret()).unwrap();
let sig = signer.sign_dsse(FIXED_PAE).unwrap();
assert_eq!(sig.len(), 64, "compact ES256K signature must be 64 bytes");
let pk = signer.public_key_sec1();
assert_eq!(pk.len(), 33, "compressed SEC1 pubkey must be 33 bytes");
verify_secp256k1(&pk, FIXED_PAE, &sig).expect("roundtrip must verify");
}
#[test]
fn verify_rejects_tampered_signature() {
let signer = Secp256k1Signer::new(fixed_secret()).unwrap();
let mut sig = signer.sign_dsse(FIXED_PAE).unwrap();
sig[10] ^= 0x01;
let pk = signer.public_key_sec1();
assert!(matches!(
verify_secp256k1(&pk, FIXED_PAE, &sig),
Err(Error::Secp256k1VerifyFailed | Error::Secp256k1SignatureInvalid)
));
}
#[test]
fn verify_rejects_wrong_pubkey() {
let signer = Secp256k1Signer::new(fixed_secret()).unwrap();
let sig = signer.sign_dsse(FIXED_PAE).unwrap();
let other = {
let mut k = [0u8; 32];
k[31] = 2;
k
};
let other_pk = Secp256k1Signer::new(other).unwrap().public_key_sec1();
assert!(matches!(
verify_secp256k1(&other_pk, FIXED_PAE, &sig),
Err(Error::Secp256k1VerifyFailed)
));
}
#[test]
fn keyid_has_secp256k1_prefix() {
let signer = Secp256k1Signer::new(fixed_secret()).unwrap();
let kid = signer.keyid_string();
assert!(kid.starts_with("secp256k1:"), "keyid = {kid}");
assert_eq!(kid.len(), 76);
}
#[test]
fn signer_trait_dispatch() {
let mut signer: Box<dyn Signer> = Box::new(Secp256k1Signer::new(fixed_secret()).unwrap());
let kid = signer.keyid().unwrap();
assert!(kid.starts_with("secp256k1:"));
let sig = signer.sign(FIXED_PAE).unwrap();
assert_eq!(sig.len(), 64);
}
#[test]
fn rfc6979_determinism() {
let s1 = Secp256k1Signer::new(fixed_secret())
.unwrap()
.sign_dsse(FIXED_PAE)
.unwrap();
let s2 = Secp256k1Signer::new(fixed_secret())
.unwrap()
.sign_dsse(FIXED_PAE)
.unwrap();
assert_eq!(s1, s2, "RFC 6979 output must be deterministic");
}
#[test]
fn low_s_enforced_on_output() {
let sig_bytes = Secp256k1Signer::new(fixed_secret())
.unwrap()
.sign_dsse(FIXED_PAE)
.unwrap();
let sig = K256Sig::from_slice(&sig_bytes).unwrap();
assert_eq!(
sig.normalize_s(),
sig,
"signer emitted a high-S signature; low-S normalization regressed"
);
}
#[test]
fn verify_rejects_wrong_length_signature() {
let signer = Secp256k1Signer::new(fixed_secret()).unwrap();
let pk = signer.public_key_sec1();
assert!(matches!(
verify_secp256k1(&pk, FIXED_PAE, &[0u8; 63]),
Err(Error::Secp256k1SignatureInvalid)
));
}
#[test]
fn zero_secret_rejected() {
assert!(matches!(
Secp256k1Signer::new([0u8; 32]),
Err(Error::Secp256k1KeyInvalid)
));
}
}