manifoldbt
0.2.0 License Sources
| Source | License | Class |
|---|---|---|
Licensie (detected) | Pending | - |
PyPI (reported) | Not reported | - |
License detection is still in progress for this version.
Loading dependencies…
License File
use base64::Engine as _;
use chrono::{DateTime, Utc};
use ed25519_dalek::{Signature, Verifier, VerifyingKey, PUBLIC_KEY_LENGTH};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
const B64: base64::engine::general_purpose::GeneralPurpose =
base64::engine::general_purpose::URL_SAFE_NO_PAD;
/// The public key used to verify license signatures.
/// Generated once with `bt-keygen generate-keys`, embedded at compile time.
/// Replace this with your actual public key bytes.
const PUBLIC_KEY_BYTES: [u8; PUBLIC_KEY_LENGTH] = [
0xfe, 0x47, 0x60, 0x6b, 0x0b, 0x21, 0x27, 0xf5, 0xe9, 0x90, 0x9b, 0x7e, 0xbb, 0x5b, 0xa4, 0x62,
0xe1, 0xa4, 0x26, 0x01, 0xb7, 0x71, 0xbf, 0xc4, 0x21, 0x3d, 0x0c, 0x09, 0x23, 0x6a, 0xab, 0x3d,
];
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LicenseFeature {
/// Full Pro license — all features
Pro,
}
/// The license payload that gets signed.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LicensePayload {
pub email: String,
pub license_id: String,
pub features: Vec<LicenseFeature>,
pub max_devices: u32,
pub updates_until: DateTime<Utc>,
pub issued_at: DateTime<Utc>,
}
/// A complete license: payload + signature.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct License {
pub payload: LicensePayload,
pub signature: String,
}
impl License {
/// Decode a license from a base64-encoded string.
pub fn from_key(key: &str) -> Result<Self, crate::LicenseError> {
let bytes = B64
.decode(key.trim())
.map_err(|_| crate::LicenseError::InvalidKey)?;
let license: License =
serde_json::from_slice(&bytes).map_err(|_| crate::LicenseError::InvalidKey)?;
license.verify_signature()?;
Ok(license)
}
/// Verify the Ed25519 signature against the embedded public key.
fn verify_signature(&self) -> Result<(), crate::LicenseError> {
let verifying_key = VerifyingKey::from_bytes(&PUBLIC_KEY_BYTES)
.map_err(|_| crate::LicenseError::InvalidKey)?;
let payload_json =
serde_json::to_string(&self.payload).map_err(|_| crate::LicenseError::InvalidKey)?;
let sig_bytes = B64
.decode(&self.signature)
.map_err(|_| crate::LicenseError::InvalidKey)?;
let signature =
Signature::from_slice(&sig_bytes).map_err(|_| crate::LicenseError::InvalidKey)?;
verifying_key
.verify(payload_json.as_bytes(), &signature)
.map_err(|_| crate::LicenseError::InvalidKey)?;
Ok(())
}
/// Check if updates are still covered.
pub fn updates_valid(&self) -> bool {
Utc::now() < self.payload.updates_until
}
/// Check if this license grants a specific feature.
pub fn has_feature(&self, feature: &LicenseFeature) -> bool {
self.payload.features.contains(feature)
}
/// Get a stable hash of the license ID (for telemetry, no PII).
pub fn id_hash(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.payload.license_id.as_bytes());
let result = hasher.finalize();
hex::encode(&result[..8])
}
}
/// List of Pro-only features for gating.
pub const PRO_FEATURES: &[&str] = &[
"resolution_1m",
"resolution_5m",
"resolution_15m",
"resolution_30m",
"resolution_1h",
"monte_carlo_unlimited",
"walk_forward",
"bayesian_optimization",
"data_connectors",
"tearsheets_export",
];
/// Check if a feature name requires Pro.
pub fn requires_pro(feature_name: &str) -> bool {
PRO_FEATURES.contains(&feature_name)
}