update job to include signatures

This commit is contained in:
Timur Gordon
2025-10-24 10:44:36 +02:00
parent 2e19ed09df
commit 90754cc4ac
5 changed files with 526 additions and 1 deletions

153
examples/sign_job.rs Normal file
View File

@@ -0,0 +1,153 @@
/// Example: Creating and Signing a Job
///
/// This example demonstrates how to:
/// 1. Create a job with signatories
/// 2. Sign the job with secp256k1 private keys
/// 3. Verify the signatures
///
/// Usage:
/// ```bash
/// cargo run --example sign_job --features crypto
/// ```
use runner_rust::job::{Job, JobSignature};
use secp256k1::{Secp256k1, SecretKey, Message, PublicKey};
use sha2::{Sha256, Digest};
use rand::rngs::OsRng;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
println!("🔐 Job Signing Example\n");
println!("======================\n");
// Step 1: Generate secp256k1 keypairs for signatories
println!("Step 1: Generating keypairs for signatories...");
let secp = Secp256k1::new();
let (secret_key1, public_key1) = secp.generate_keypair(&mut OsRng);
let (secret_key2, public_key2) = secp.generate_keypair(&mut OsRng);
let pubkey1_hex = hex::encode(public_key1.serialize());
let pubkey2_hex = hex::encode(public_key2.serialize());
println!(" Signatory 1 public key: {}", pubkey1_hex);
println!(" Signatory 2 public key: {}\n", pubkey2_hex);
// Step 2: Create a job
println!("Step 2: Creating job...");
let mut job = Job::new(
"alice".to_string(),
"shared_context".to_string(),
"print('Hello from signed job!')".to_string(),
"runner1".to_string(),
"rhai".to_string(),
);
println!(" Job ID: {}\n", job.id);
// Step 3: Get canonical representation and hash it
println!("Step 3: Creating canonical representation...");
let canonical = job.canonical_representation();
println!(" Canonical: {}", canonical);
let mut hasher = Sha256::new();
hasher.update(canonical.as_bytes());
let hash = hasher.finalize();
let message = Message::from_digest_slice(&hash)?;
println!(" Hash: {}\n", hex::encode(hash));
// Step 4: Sign with both private keys
println!("Step 4: Signing job with both signatories...");
let sig1 = secp.sign_ecdsa(&message, &secret_key1);
let sig2 = secp.sign_ecdsa(&message, &secret_key2);
job.signatures = vec![
JobSignature {
public_key: pubkey1_hex.clone(),
signature: hex::encode(sig1.serialize_compact()),
},
JobSignature {
public_key: pubkey2_hex.clone(),
signature: hex::encode(sig2.serialize_compact()),
},
];
println!(" ✓ Signed by signatory 1");
println!(" ✓ Signed by signatory 2");
println!(" Signatories: {:?}\n", job.signatories());
// Step 5: Verify signatures
println!("Step 5: Verifying signatures...");
match job.verify_signatures() {
Ok(()) => {
println!(" ✅ All signatures verified successfully!\n");
}
Err(e) => {
println!(" ❌ Signature verification failed: {}\n", e);
return Err(e.into());
}
}
// Step 6: Serialize to JSON
println!("Step 6: Serializing job to JSON...");
let job_json = serde_json::to_string_pretty(&job)?;
println!("{}\n", job_json);
// Step 7: Test with missing signature
println!("Step 7: Testing with missing signature...");
let mut incomplete_job = job.clone();
incomplete_job.signatures.pop(); // Remove one signature
match incomplete_job.verify_signatures() {
Ok(()) => {
println!(" ❌ Should have failed!\n");
}
Err(e) => {
println!(" ✅ Correctly rejected: {}\n", e);
}
}
// Step 8: Test with wrong signature
println!("Step 8: Testing with wrong signature...");
let (wrong_secret, _) = secp.generate_keypair(&mut OsRng);
let wrong_sig = secp.sign_ecdsa(&message, &wrong_secret);
let mut tampered_job = job.clone();
tampered_job.signatures[0].signature = hex::encode(wrong_sig.serialize_compact());
match tampered_job.verify_signatures() {
Ok(()) => {
println!(" ❌ Should have failed!\n");
}
Err(e) => {
println!(" ✅ Correctly rejected: {}\n", e);
}
}
// Step 9: Test with unauthorized signer
println!("Step 9: Testing with unauthorized signer...");
let (unauthorized_secret, unauthorized_public) = secp.generate_keypair(&mut OsRng);
let unauthorized_sig = secp.sign_ecdsa(&message, &unauthorized_secret);
let mut unauthorized_job = job.clone();
unauthorized_job.signatures.push(JobSignature {
public_key: hex::encode(unauthorized_public.serialize()),
signature: hex::encode(unauthorized_sig.serialize_compact()),
});
match unauthorized_job.verify_signatures() {
Ok(()) => {
println!(" ❌ Should have failed!\n");
}
Err(e) => {
println!(" ✅ Correctly rejected: {}\n", e);
}
}
println!("======================");
println!("✅ All tests passed!");
Ok(())
}