use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommercialOverrideReceipt {
pub license_tier: String,
pub signer_did: String,
pub signature_algorithm: String,
pub credential_format: String,
pub distr_license_cid: String,
pub hardware_zk_proof: Option<String>,
pub issued_at_epoch: u64,
pub expires_at_epoch: u64,
pub exp: u64,
pub iat: u64,
pub entitlements: Vec<String>,
pub network_mode: String,
pub federation_enabled: bool,
pub signature: Option<String>,
#[serde(default = "default_tenant_cid")]
pub tenant_cid: String,
}
fn default_tenant_cid() -> String {
"coreason-tenant-default".to_string()
}
impl CommercialOverrideReceipt {
pub fn verify_signature(&self, public_key_hex: &str) -> Result<bool, &'static str> {
let signature_hex = match &self.signature {
Some(sig) => sig,
None => return Err("Missing signature in receipt"),
};
let message = format!("{}:{}", self.tenant_cid, self.expires_at_epoch);
let public_bytes = hex::decode(public_key_hex).map_err(|_| "Invalid hex in public key")?;
let signature_bytes = hex::decode(signature_hex).map_err(|_| "Invalid hex in signature")?;
let public_array: [u8; 32] = public_bytes
.as_slice()
.try_into()
.map_err(|_| "Public key must be 32 bytes")?;
let signature_array: [u8; 64] = signature_bytes
.as_slice()
.try_into()
.map_err(|_| "Signature must be 64 bytes")?;
let verifying_key =
VerifyingKey::from_bytes(&public_array).map_err(|_| "Invalid public key")?;
let signature = Signature::from_bytes(&signature_array);
Ok(verifying_key.verify(message.as_bytes(), &signature).is_ok())
}
pub fn is_valid_epoch(&self) -> bool {
let start = SystemTime::now();
let since_the_epoch = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
self.expires_at_epoch > since_the_epoch && since_the_epoch >= self.issued_at_epoch
}
}
#[derive(Debug, Clone)]
pub enum LicenseError {
TemporalExpiration(String),
CryptographicVerification(String),
HardwareFingerprint(String),
ProductionGuard(String),
}
impl std::fmt::Display for LicenseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LicenseError::TemporalExpiration(msg) => write!(f, "TemporalExpiration: {}", msg),
LicenseError::CryptographicVerification(msg) => {
write!(f, "CryptographicVerification: {}", msg)
}
LicenseError::HardwareFingerprint(msg) => {
write!(f, "HardwareFingerprint: {}", msg)
}
LicenseError::ProductionGuard(msg) => write!(f, "ProductionGuard: {}", msg),
}
}
}
impl std::error::Error for LicenseError {}
pub struct LicenseVerifier {
local_fingerprint: Option<String>,
}
impl LicenseVerifier {
pub fn new(local_cluster_fingerprint_hash: Option<String>) -> Self {
Self {
local_fingerprint: local_cluster_fingerprint_hash,
}
}
fn verify_zk_snark(&self, proof: &str) -> Result<bool, LicenseError> {
if proof.is_empty() {
return Ok(true); }
let local_fp = self.local_fingerprint.as_deref().ok_or_else(|| {
LicenseError::HardwareFingerprint(
"Receipt requires hardware binding, but no local fingerprint provided.".into(),
)
})?;
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(local_fp.as_bytes());
let expected_hash = format!("{:x}", hasher.finalize());
if proof.trim() == expected_hash {
Ok(true)
} else {
Err(LicenseError::HardwareFingerprint(
"Hardware fingerprint mismatch: proof does not match local fingerprint hash."
.into(),
))
}
}
pub fn verify_and_apply(
&self,
receipt: &CommercialOverrideReceipt,
public_key_hex: &str,
) -> Vec<String> {
let coreason_env =
std::env::var("COREASON_ENV").unwrap_or_else(|_| "development".to_string());
if coreason_env == "production" {
let root_ca_key = std::env::var("COREASON_ROOT_CA_KEY").unwrap_or_default();
if root_ca_key.is_empty() {
eprintln!(
"CRITICAL: COREASON_ROOT_CA_KEY is not set in production. \
License verification fail-closed. Defaulting to Prosperity 3.0."
);
return Vec::new();
}
let dev_key = std::env::var("COREASON_DEV_KEY").unwrap_or_default();
if !dev_key.is_empty() && root_ca_key == dev_key {
eprintln!(
"CRITICAL: COREASON_ROOT_CA_KEY matches COREASON_DEV_KEY in production. \
This is a security violation. Fail-closed."
);
return Vec::new();
}
}
if !receipt.is_valid_epoch() {
eprintln!(
"License expired at {}. Falling back to Prosperity 3.0.",
receipt.expires_at_epoch
);
return Vec::new();
}
match receipt.verify_signature(public_key_hex) {
Ok(true) => {}
Ok(false) => {
eprintln!(
"License signature verification returned false. Defaulting to Prosperity 3.0."
);
return Vec::new();
}
Err(e) => {
eprintln!(
"License signature verification failed: {}. Defaulting to Prosperity 3.0.",
e
);
return Vec::new();
}
}
if let Some(ref proof) = receipt.hardware_zk_proof {
if let Err(e) = self.verify_zk_snark(proof) {
eprintln!(
"Hardware fingerprint verification failed: {}. Defaulting to Prosperity 3.0.",
e
);
return Vec::new();
}
}
if receipt.credential_format == "sd-jwt" {
}
receipt.entitlements.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_receipt(expires_at_epoch: u64) -> CommercialOverrideReceipt {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
CommercialOverrideReceipt {
license_tier: "enterprise".into(),
signer_did: "did:coreason:test".into(),
signature_algorithm: "Ed25519".into(),
credential_format: "jwt".into(),
distr_license_cid: "test-cid".into(),
hardware_zk_proof: None,
issued_at_epoch: now - 3600,
expires_at_epoch,
exp: expires_at_epoch,
iat: now - 3600,
entitlements: vec!["COMMERCIAL_USE".into()],
network_mode: "sovereign".into(),
federation_enabled: false,
signature: None,
tenant_cid: "test-tenant".into(),
}
}
#[test]
fn test_is_valid_epoch_unexpired() {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let receipt = make_receipt(now + 86400);
assert!(receipt.is_valid_epoch());
}
#[test]
fn test_is_valid_epoch_expired() {
let receipt = make_receipt(1000);
assert!(!receipt.is_valid_epoch());
}
#[test]
fn test_verify_and_apply_expired() {
let receipt = make_receipt(1000); let verifier = LicenseVerifier::new(None);
let entitlements = verifier.verify_and_apply(&receipt, "dummy");
assert!(entitlements.is_empty());
}
#[test]
fn test_zk_snark_empty_proof() {
let verifier = LicenseVerifier::new(None);
assert!(verifier.verify_zk_snark("").is_ok());
}
#[test]
fn test_zk_snark_no_fingerprint() {
let verifier = LicenseVerifier::new(None);
let result = verifier.verify_zk_snark("some_proof");
assert!(result.is_err());
}
#[test]
fn test_zk_snark_matching_fingerprint() {
use sha2::{Digest, Sha256};
let fp = "test-fingerprint";
let mut hasher = Sha256::new();
hasher.update(fp.as_bytes());
let proof = format!("{:x}", hasher.finalize());
let verifier = LicenseVerifier::new(Some(fp.to_string()));
assert!(verifier.verify_zk_snark(&proof).is_ok());
}
#[test]
fn test_zk_snark_mismatched_fingerprint() {
let verifier = LicenseVerifier::new(Some("test-fp".to_string()));
let result = verifier.verify_zk_snark("bad_proof");
assert!(result.is_err());
}
}