use hkdf::Hkdf;
use k256::ecdsa::{RecoveryId, Signature, SigningKey, VerifyingKey};
use k256::SecretKey;
use sha2::Sha256;
use zeroize::Zeroizing;
use crate::error::{Result, TeeError};
const HKDF_INFO: &[u8] = b"tenzro/sealed-secp256k1/v1";
pub struct SealedSecp256k1Key {
signing_key: SigningKey,
verifying_key: VerifyingKey,
address: [u8; 20],
}
impl SealedSecp256k1Key {
#[cfg(feature = "amd-sev-snp")]
pub fn derive_from_snp(label: &[u8]) -> Result<Self> {
use crate::amd_sev_snp::{guest_field_select, AmdSevSnpProvider};
let provider = AmdSevSnpProvider::new();
let ikm = provider.derived_key(
0, guest_field_select::MEASUREMENT
| guest_field_select::IMAGE_ID
| guest_field_select::GUEST_SVN,
0, 0, 0, )?;
let ikm = Zeroizing::new(ikm);
Self::from_ikm(label, ikm.as_slice())
}
#[cfg(feature = "intel-tdx")]
pub async fn derive_from_tdx(label: &[u8]) -> Result<Self> {
let provider = crate::intel_tdx::IntelTdxProvider::new();
let mr_td = provider.platform_measurement().await?;
let ikm = Zeroizing::new(mr_td);
Self::from_ikm(label, ikm.as_slice())
}
pub async fn derive_auto(label: &[u8]) -> Result<Self> {
#[cfg(feature = "amd-sev-snp")]
{
if std::path::Path::new("/dev/sev-guest").exists() {
return Self::derive_from_snp(label);
}
}
#[cfg(feature = "intel-tdx")]
{
if std::path::Path::new("/dev/tdx_guest").exists()
|| std::path::Path::new("/sys/kernel/config/tsm/report").exists()
{
return Self::derive_from_tdx(label).await;
}
}
Err(TeeError::not_available(
"No TEE available for sealed secp256k1 key derivation",
))
}
fn from_ikm(label: &[u8], ikm: &[u8]) -> Result<Self> {
let hk = Hkdf::<Sha256>::new(Some(label), ikm);
let mut scalar_bytes = Zeroizing::new([0u8; 32]);
hk.expand(HKDF_INFO, scalar_bytes.as_mut())
.map_err(|e| TeeError::CryptoError(format!("HKDF expand failed: {}", e)))?;
let secret = SecretKey::from_bytes((&*scalar_bytes).into()).map_err(|e| {
TeeError::CryptoError(format!(
"HKDF output is not a valid secp256k1 scalar: {}",
e
))
})?;
let signing_key = SigningKey::from(&secret);
let verifying_key = *signing_key.verifying_key();
let address = derive_eth_address(&verifying_key);
Ok(Self {
signing_key,
verifying_key,
address,
})
}
pub fn address(&self) -> [u8; 20] {
self.address
}
pub fn pubkey_uncompressed(&self) -> [u8; 65] {
let point = self.verifying_key.to_sec1_point(false);
let bytes = point.as_bytes();
debug_assert_eq!(bytes.len(), 65);
let mut out = [0u8; 65];
out.copy_from_slice(bytes);
out
}
pub fn sign_prehash(&self, digest: &[u8; 32]) -> Result<(Signature, RecoveryId)> {
self.signing_key
.sign_prehash_recoverable(digest)
.map_err(|e| TeeError::CryptoError(format!("ECDSA sign failed: {}", e)))
}
}
fn derive_eth_address(vk: &VerifyingKey) -> [u8; 20] {
use sha3::{Digest as Sha3Digest, Keccak256};
let point = vk.to_sec1_point(false);
let pubkey_bytes = point.as_bytes();
let hash = Keccak256::digest(&pubkey_bytes[1..]);
let mut out = [0u8; 20];
out.copy_from_slice(&hash[12..32]);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_ikm_deterministic() {
let ikm = [0x42u8; 64];
let label = b"tenzro/test/v1";
let k1 = SealedSecp256k1Key::from_ikm(label, &ikm).unwrap();
let k2 = SealedSecp256k1Key::from_ikm(label, &ikm).unwrap();
assert_eq!(k1.address(), k2.address());
assert_eq!(k1.pubkey_uncompressed(), k2.pubkey_uncompressed());
}
#[test]
fn test_label_provides_domain_separation() {
let ikm = [0x42u8; 64];
let k1 = SealedSecp256k1Key::from_ikm(b"label-a", &ikm).unwrap();
let k2 = SealedSecp256k1Key::from_ikm(b"label-b", &ikm).unwrap();
assert_ne!(k1.address(), k2.address());
}
#[test]
fn test_ikm_provides_isolation() {
let label = b"tenzro/test/v1";
let k1 = SealedSecp256k1Key::from_ikm(label, &[0x11u8; 64]).unwrap();
let k2 = SealedSecp256k1Key::from_ikm(label, &[0x22u8; 64]).unwrap();
assert_ne!(k1.address(), k2.address());
}
#[test]
fn test_address_format() {
let key = SealedSecp256k1Key::from_ikm(b"test", &[0x11u8; 64]).unwrap();
let addr = key.address();
assert_eq!(addr.len(), 20);
let recomputed = derive_eth_address(&key.verifying_key);
assert_eq!(addr, recomputed);
}
#[test]
fn test_sign_and_verify_prehash() {
use k256::ecdsa::signature::hazmat::PrehashVerifier;
let key = SealedSecp256k1Key::from_ikm(b"test", &[0x33u8; 64]).unwrap();
let digest = [0x99u8; 32];
let (sig, _recovery) = key.sign_prehash(&digest).unwrap();
key.verifying_key
.verify_prehash(&digest, &sig)
.expect("self-verification");
}
#[test]
fn test_pubkey_uncompressed_starts_with_0x04() {
let key = SealedSecp256k1Key::from_ikm(b"test", &[0x44u8; 64]).unwrap();
let pk = key.pubkey_uncompressed();
assert_eq!(pk[0], 0x04);
assert_eq!(pk.len(), 65);
}
#[test]
fn test_derive_auto_off_hardware_returns_not_available() {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(SealedSecp256k1Key::derive_auto(b"test"));
match result {
Err(TeeError::NotAvailable(_)) => {}
Ok(_) => {
let on_sev = std::path::Path::new("/dev/sev-guest").exists();
let on_tdx = std::path::Path::new("/dev/tdx_guest").exists()
|| std::path::Path::new("/sys/kernel/config/tsm/report").exists();
assert!(
on_sev || on_tdx,
"derive_auto returned Ok off TEE hardware — must not happen"
);
}
Err(other) => panic!("unexpected error: {:?}", other),
}
}
}