Skip to main content

coreason_meta_engineering_rust/
signer.rs

1// Copyright (c) 2026 CoReason, Inc.
2// All rights reserved.
3// SPDX-License-Identifier: LicenseRef-Prosperity-3.0
4
5use std::env;
6use std::fs;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use ed25519_dalek::{SigningKey, Signer};
10use rand::rngs::OsRng;
11
12/// Format formatting-free and comment-free source logic using the Python validation worker.
13pub fn canonicalize_source(source_code: &str) -> String {
14    // Generate a temp file name in the system temp directory
15    let temp_name = format!("coresig_temp_{}.py", rand::random::<u64>());
16    let temp_path = env::temp_dir().join(temp_name);
17    
18    if let Err(e) = fs::write(&temp_path, source_code) {
19        log::error!("Failed to write temp file for canonicalization: {}", e);
20        return fallback_canonicalization(source_code);
21    }
22    
23    let python_exe = env::var("PYTHON").unwrap_or_else(|_| "python".to_string());
24    
25    // Find workspace root
26    let workspace_root = env::var("COREASON_WORKSPACE_ROOT").unwrap_or_else(|_| ".".to_string());
27    let worker_module = "coreason_meta_engineering.validation_worker";
28    
29    let mut cmd = Command::new(&python_exe);
30    cmd.arg("-m")
31       .arg(worker_module)
32       .arg("canonicalize")
33       .arg("--code-path")
34       .arg(&temp_path);
35
36    // Construct PYTHONPATH containing workspace root and src
37    let python_path = format!("{}{}{}/src", workspace_root, if cfg!(windows) { ";" } else { ":" }, workspace_root);
38    cmd.env("PYTHONPATH", python_path);
39    
40    let output = cmd.output();
41    let _ = fs::remove_file(&temp_path);
42    
43    match output {
44        Ok(out) if out.status.success() => {
45            String::from_utf8_lossy(&out.stdout).into_owned()
46        }
47        _ => {
48            log::warn!("Python canonicalization failed; falling back to basic cleaning.");
49            fallback_canonicalization(source_code)
50        }
51    }
52}
53
54fn fallback_canonicalization(source_code: &str) -> String {
55    let mut clean_lines = Vec::new();
56    for line in source_code.lines() {
57        if line.trim().starts_with("# CORESIG:") {
58            continue;
59        }
60        clean_lines.push(line);
61    }
62    clean_lines.join("\n").replace("\r\n", "\n")
63}
64
65/// Signs the canonical representation of the source code with the given private key (hex)
66/// and appends a '# CORESIG: <signature>' comment to the original source.
67pub fn sign_source_code(source_code: &str, private_key_hex: &str) -> String {
68    let private_bytes = match hex::decode(private_key_hex.trim()) {
69        Ok(b) => b,
70        Err(e) => {
71            log::error!("Failed to decode private key hex: {}", e);
72            return source_code.to_string();
73        }
74    };
75
76    if private_bytes.len() != 32 {
77        log::error!("Ed25519 private key must be exactly 32 bytes (64 hex characters), got {}", private_bytes.len());
78        return source_code.to_string();
79    }
80
81    let mut key_arr = [0u8; 32];
82    key_arr.copy_from_slice(&private_bytes);
83    let signing_key = SigningKey::from_bytes(&key_arr);
84
85    let canonical_source = canonicalize_source(source_code);
86    let signature = signing_key.sign(canonical_source.as_bytes());
87    let signature_hex = hex::encode(signature.to_bytes());
88
89    let base_code = source_code.trim_end();
90    format!("{}\n\n# CORESIG: {}\n", base_code, signature_hex)
91}
92
93/// Attempts to resolve the Ed25519 private key from environment variables, Vault,
94/// or falls back to a developer key-pair locally under ~/.coreason/dev_signing_key.hex.
95pub fn resolve_signing_key() -> Option<String> {
96    // 1. Check environment variable
97    if let Ok(key_env) = env::var("COREASON_SIGNING_KEY") {
98        let key_env = key_env.trim();
99        // Check if environment variable points to a file path
100        let path = Path::new(key_env);
101        if path.is_file() {
102            if let Ok(content) = fs::read_to_string(path) {
103                return Some(content.trim().to_string());
104            }
105        }
106        return Some(key_env.to_string());
107    }
108
109    // 2. Check Vault
110    let vault_url = env::var("VAULT_ADDR");
111    let vault_token = env::var("VAULT_TOKEN");
112    if let (Ok(url), Ok(token)) = (vault_url, vault_token) {
113        log::info!("Attempting to fetch signing key from Vault...");
114        let client = reqwest::blocking::Client::new();
115        let api_url = format!("{}/v1/secret/data/coreason/identity", url.trim_end_matches('/'));
116        let response = client.get(&api_url)
117            .header("X-Vault-Token", token)
118            .send();
119
120        match response {
121            Ok(res) if res.status().is_success() => {
122                if let Ok(json) = res.json::<serde_json::Value>() {
123                    if let Some(private_key) = json["data"]["data"]["private_key"].as_str() {
124                        return Some(private_key.trim().to_string());
125                    }
126                }
127            }
128            Ok(res) => log::warn!("Vault query returned non-success status: {}", res.status()),
129            Err(e) => log::warn!("Failed to query Vault: {}", e),
130        }
131    }
132
133    // 3. Fallback: local developer signing key
134    let home_dir = env::var("COREASON_HOME")
135        .map(PathBuf::from)
136        .ok()
137        .or_else(|| dirs::home_dir());
138
139    if let Some(home) = home_dir {
140        let dev_dir = home.join(".coreason");
141        let dev_key_path = dev_dir.join("dev_signing_key.hex");
142        let dev_pub_path = dev_dir.join("dev_signing_key.pub");
143
144        if dev_key_path.is_file() {
145            if let Ok(content) = fs::read_to_string(&dev_key_path) {
146                return Some(content.trim().to_string());
147            }
148        }
149
150        // Generate new developer key pair
151        if let Err(e) = fs::create_dir_all(&dev_dir) {
152            log::warn!("Failed to create dev dir {:?}: {}", dev_dir, e);
153            return None;
154        }
155
156        let mut rng = OsRng;
157        let signing_key = SigningKey::generate(&mut rng);
158        let private_hex = hex::encode(signing_key.to_bytes());
159        let public_hex = hex::encode(signing_key.verifying_key().to_bytes());
160
161        if fs::write(&dev_key_path, &private_hex).is_ok() && fs::write(&dev_pub_path, &public_hex).is_ok() {
162            log::warn!(
163                "No COREASON_SIGNING_KEY found. Generated a new developer key-pair at:\n\
164                   Private: {:?}\n\
165                   Public:  {:?}\n\
166                 Please register the public key with your runtime verification engine.",
167                dev_key_path,
168                dev_pub_path
169            );
170            return Some(private_hex);
171        }
172    }
173
174    None
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_sign_source_code() {
183        let code = "print('hello')\n";
184        let private_bytes = [1u8; 32];
185        let private_hex = hex::encode(private_bytes);
186        
187        let signed = sign_source_code(code, &private_hex);
188        assert!(signed.contains("print('hello')"));
189        assert!(signed.contains("# CORESIG:"));
190    }
191}