#[cfg(not(feature = "std"))]
use alloc::string::ToString;
use alloc::{format, vec::Vec};
use k256::ecdsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use k256::ecdsa::{Signature, SigningKey, VerifyingKey};
use zeroize::ZeroizeOnDrop;
use crate::{SignError, SignOutput};
pub(crate) const DIGEST_LEN: usize = 32;
pub struct Secp256k1Signer {
key: SigningKey,
}
impl core::fmt::Debug for Secp256k1Signer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Secp256k1Signer")
.field("key", &"[REDACTED]")
.finish()
}
}
impl ZeroizeOnDrop for Secp256k1Signer {}
impl Secp256k1Signer {
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, SignError> {
let key =
SigningKey::from_slice(bytes).map_err(|e| SignError::InvalidKey(e.to_string()))?;
Ok(Self { key })
}
pub fn from_hex(hex_str: &str) -> Result<Self, SignError> {
let stripped = hex_str.strip_prefix("0x").unwrap_or(hex_str);
let decoded = hex::decode(stripped).map_err(|e| SignError::InvalidKey(e.to_string()))?;
let bytes: [u8; 32] = decoded.try_into().map_err(|v: Vec<u8>| {
SignError::InvalidKey(format!("expected 32 bytes, got {}", v.len()))
})?;
Self::from_bytes(&bytes)
}
#[cfg(feature = "getrandom")]
pub fn try_random() -> Result<Self, SignError> {
use zeroize::Zeroize as _;
let mut bytes = [0u8; 32];
let result = getrandom::fill(&mut bytes)
.map_err(|e| SignError::SigningFailed(e.to_string()))
.and_then(|()| {
SigningKey::from_slice(&bytes).map_err(|e| SignError::InvalidKey(e.to_string()))
});
bytes.zeroize();
result.map(|key| Self { key })
}
#[cfg(feature = "getrandom")]
#[must_use]
#[allow(
clippy::expect_used,
reason = "panicking wrapper — callers needing graceful handling use try_random"
)]
pub fn random() -> Self {
Self::try_random().expect("Secp256k1Signer::random: entropy source failed")
}
#[must_use]
pub const fn signing_key(&self) -> &SigningKey {
&self.key
}
#[must_use]
pub fn verifying_key(&self) -> &VerifyingKey {
self.key.verifying_key()
}
#[must_use]
pub fn compressed_public_key(&self) -> Vec<u8> {
self.key
.verifying_key()
.to_encoded_point(true)
.as_bytes()
.to_vec()
}
#[must_use]
pub fn uncompressed_public_key(&self) -> Vec<u8> {
self.key
.verifying_key()
.to_encoded_point(false)
.as_bytes()
.to_vec()
}
pub fn sign_prehash_recoverable(
&self,
hash: &[u8; DIGEST_LEN],
) -> Result<SignOutput, SignError> {
let (sig, rid) = self
.key
.sign_prehash_recoverable(hash)
.map_err(|e| SignError::SigningFailed(e.to_string()))?;
let sig_bytes = sig.to_bytes();
let mut signature = [0u8; 64];
signature.copy_from_slice(&sig_bytes);
Ok(SignOutput::Ecdsa {
signature,
v: rid.to_byte(),
})
}
pub fn sign_prehash_der(&self, hash: &[u8; DIGEST_LEN]) -> Result<SignOutput, SignError> {
let sig: Signature = self
.key
.sign_prehash(hash)
.map_err(|e| SignError::SigningFailed(e.to_string()))?;
Ok(SignOutput::EcdsaDer(sig.to_der().as_bytes().to_vec()))
}
pub fn verify_prehash(
&self,
hash: &[u8; DIGEST_LEN],
signature: &[u8; 64],
) -> Result<(), SignError> {
let sig = Signature::from_slice(signature)
.map_err(|e| SignError::InvalidSignature(e.to_string()))?;
self.key
.verifying_key()
.verify_prehash(hash, &sig)
.map_err(|e| SignError::InvalidSignature(e.to_string()))
}
#[allow(
clippy::indexing_slicing,
reason = "signature has a compile-time size of 65 so [..64] is infallible"
)]
pub fn verify_prehash_recoverable(
&self,
hash: &[u8; DIGEST_LEN],
signature: &[u8; 65],
) -> Result<(), SignError> {
let mut compact = [0u8; 64];
compact.copy_from_slice(&signature[..64]);
self.verify_prehash(hash, &compact)
}
#[allow(
clippy::indexing_slicing,
reason = "length is exhaustively matched before every slice operation"
)]
pub fn verify_prehash_any(
&self,
hash: &[u8; DIGEST_LEN],
signature: &[u8],
) -> Result<(), SignError> {
match signature.len() {
64 => {
let mut buf = [0u8; 64];
buf.copy_from_slice(signature);
self.verify_prehash(hash, &buf)
}
65 => {
let mut buf = [0u8; 65];
buf.copy_from_slice(signature);
self.verify_prehash_recoverable(hash, &buf)
}
n => Err(SignError::InvalidSignature(format!(
"expected 64 or 65 bytes, got {n}"
))),
}
}
pub fn verify_prehash_der(
&self,
hash: &[u8; DIGEST_LEN],
signature_der: &[u8],
) -> Result<(), SignError> {
let sig = Signature::from_der(signature_der)
.map_err(|e| SignError::InvalidSignature(e.to_string()))?;
self.key
.verifying_key()
.verify_prehash(hash, &sig)
.map_err(|e| SignError::InvalidSignature(e.to_string()))
}
}