Skip to content

Commit 793c15c

Browse files
authored
feat: Add signer crate (#6)
1 parent 5b1c515 commit 793c15c

File tree

8 files changed

+2721
-119
lines changed

8 files changed

+2721
-119
lines changed

Cargo.lock

Lines changed: 2459 additions & 118 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7+
alloy-signer = "0.15.9"
8+
alloy-signer-local = "0.15.9"
79
sha256 = "1.6.0"
810
sha3 = "0.10.8"
11+
thiserror = "2.0.12"
12+
13+
[dev-dependencies]
14+
temp-env = "0.3.6"

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
mod hash_utils;
1+
mod post_compute;
2+
mod utils;
23

34
fn main() {
45
println!("Hello, world!");

src/post_compute.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod errors;
2+
pub mod signer;

src/post_compute/errors.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use thiserror::Error;
2+
3+
#[derive(Debug, PartialEq, Error)]
4+
pub enum ReplicateStatusCause {
5+
#[error("Failed to verify TeeEnclaveChallenge signature (exiting)")]
6+
PostComputeInvalidTeeSignature,
7+
#[error("Invalid enclave challenge private key")]
8+
PostComputeInvalidEnclaveChallengePrivateKey,
9+
#[error("Worker address related environment variable is missing")]
10+
PostComputeWorkerAddressMissing,
11+
#[error("Tee challenge private key related environment variable is missing")]
12+
PostComputeTeeChallengePrivateKeyMissing,
13+
}
14+
15+
#[derive(Debug, Error)]
16+
#[error("Post-compute failed: {exit_cause}")]
17+
pub struct PostComputeError {
18+
pub exit_cause: ReplicateStatusCause,
19+
}
20+
21+
impl PostComputeError {
22+
pub fn new(cause: ReplicateStatusCause) -> Self {
23+
Self { exit_cause: cause }
24+
}
25+
26+
pub fn exit_cause(&self) -> &ReplicateStatusCause {
27+
&self.exit_cause
28+
}
29+
}

src/post_compute/signer.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
use crate::post_compute::errors::{PostComputeError, ReplicateStatusCause::*};
2+
use crate::utils::hash_utils::{concatenate_and_hash, hex_string_to_byte_array};
3+
use alloy_signer::{Signature, SignerSync};
4+
use alloy_signer_local::PrivateKeySigner;
5+
use std::env;
6+
7+
const SIGN_WORKER_ADDRESS: &str = "SIGN_WORKER_ADDRESS";
8+
const SIGN_TEE_CHALLENGE_PRIVATE_KEY: &str = "SIGN_TEE_CHALLENGE_PRIVATE_KEY";
9+
10+
/// Signs a message hash using the provided enclave challenge private key.
11+
///
12+
/// This function takes a message hash in hexadecimal string format, converts it to a byte array,
13+
/// and signs it using the provided private key. The resulting signature is then converted back
14+
/// to a string representation.
15+
///
16+
/// # Arguments
17+
///
18+
/// * `message_hash` - A hexadecimal string representing the hash to be signed
19+
/// * `enclave_challenge_private_key` - A string containing the private key used for signing
20+
///
21+
/// # Returns
22+
///
23+
/// * `Ok(String)` - The signature as a hexadecimal string if successful
24+
/// * `Err(PostComputeError)` - An error if the private key is invalid or if signing fails
25+
///
26+
/// # Errors
27+
///
28+
/// This function will return an error in the following situations:
29+
/// * The provided private key cannot be parsed as a valid `PrivateKeySigner` (returns `PostComputeInvalidEnclaveChallengePrivateKey`)
30+
/// * The signing operation fails (returns `PostComputeInvalidTeeSignature`)
31+
///
32+
/// # Example
33+
///
34+
/// ```
35+
/// let message_hash = "0x5cd0e9c5180dd35e2b8285d0db4ded193a9b4be6fbfab90cbadccecab130acad";
36+
/// let private_key = "0xdd3b993ec21c71c1f6d63a5240850e0d4d8dd83ff70d29e49247958548c1d479";
37+
///
38+
/// match sign_enclave_challenge(message_hash, private_key) {
39+
/// Ok(signature) => println!("Signature: {}", signature),
40+
/// Err(e) => eprintln!("Error: {:?}", e),
41+
/// }
42+
/// ```
43+
pub fn sign_enclave_challenge(
44+
message_hash: &str,
45+
enclave_challenge_private_key: &str,
46+
) -> Result<String, PostComputeError> {
47+
let signer: PrivateKeySigner = enclave_challenge_private_key
48+
.parse::<PrivateKeySigner>()
49+
.map_err(|_| PostComputeError::new(PostComputeInvalidEnclaveChallengePrivateKey))?;
50+
51+
let signature: Signature = signer
52+
.sign_message_sync(&hex_string_to_byte_array(message_hash))
53+
.map_err(|_| PostComputeError::new(PostComputeInvalidTeeSignature))?;
54+
55+
Ok(signature.to_string())
56+
}
57+
58+
/// Generates a challenge signature for a given chain task ID.
59+
///
60+
/// This function retrieves the worker address and TEE challenge private key from the environment,
61+
/// then creates a message hash by concatenating and hashing the chain task ID and worker address.
62+
/// Finally, it signs this message hash with the private key.
63+
///
64+
/// # Arguments
65+
///
66+
/// * `chain_task_id` - A string identifier for the chain task
67+
///
68+
/// # Returns
69+
///
70+
/// * `Ok(String)` - The challenge signature as a hexadecimal string if successful
71+
/// * `Err(PostComputeError)` - An error if required environment variables are missing or if signing fails
72+
///
73+
/// # Errors
74+
///
75+
/// This function will return an error in the following situations:
76+
/// * The worker address environment variable is missing (returns `PostComputeWorkerAddressMissing`)
77+
/// * The TEE challenge private key environment variable is missing (returns `PostComputeTeeChallengePrivateKeyMissing`)
78+
/// * The signing operation fails (returns `PostComputeInvalidTeeSignature`)
79+
///
80+
/// # Environment Variables
81+
///
82+
/// * `SIGN_WORKER_ADDRESS` - The worker's address used in message hash calculation
83+
/// * `SIGN_TEE_CHALLENGE_PRIVATE_KEY` - The private key used for signing the challenge
84+
///
85+
/// # Example
86+
///
87+
/// ```
88+
/// // Assuming the necessary environment variables are set:
89+
/// // SIGN_WORKER_ADDRESS=0xabcdef123456789
90+
/// // SIGN_TEE_CHALLENGE_PRIVATE_KEY=0xdd3b993ec21c71c1f6d63a5240850e0d4d8dd83ff70d29e49247958548c1d479
91+
///
92+
/// let chain_task_id = "0x123456789abcdef";
93+
///
94+
/// match challenge(chain_task_id) {
95+
/// Ok(signature) => println!("Challenge signature: {}", signature),
96+
/// Err(e) => eprintln!("Error generating challenge: {:?}", e),
97+
/// }
98+
/// ```
99+
pub fn get_challenge(chain_task_id: &str) -> Result<String, PostComputeError> {
100+
let worker_address: String = match env::var(SIGN_WORKER_ADDRESS) {
101+
Ok(val) => val,
102+
Err(_) => Err(PostComputeError::new(PostComputeWorkerAddressMissing))?,
103+
};
104+
let tee_challenge_private_key = match env::var(SIGN_TEE_CHALLENGE_PRIVATE_KEY) {
105+
Ok(val) => val,
106+
Err(_) => Err(PostComputeError::new(
107+
PostComputeTeeChallengePrivateKeyMissing,
108+
))?,
109+
};
110+
let message_hash: String = concatenate_and_hash(&[chain_task_id, &worker_address]);
111+
sign_enclave_challenge(&message_hash, &tee_challenge_private_key)
112+
}
113+
114+
#[cfg(test)]
115+
mod tests {
116+
use super::*;
117+
use temp_env::with_vars;
118+
119+
const CHAIN_TASK_ID: &str = "0x123456789abcdef";
120+
const WORKER_ADDRESS: &str = "0xabcdef123456789";
121+
const ENCLAVE_CHALLENGE_PRIVATE_KEY: &str =
122+
"0xdd3b993ec21c71c1f6d63a5240850e0d4d8dd83ff70d29e49247958548c1d479";
123+
const MESSAGE_HASH: &str = "0x5cd0e9c5180dd35e2b8285d0db4ded193a9b4be6fbfab90cbadccecab130acad";
124+
const EXPECTED_SIGNATURE: &str = "0xfcc6bce5eb04284c2eb1ed14405b943574343b1abda33628fbf94a374b18dd16541c6ebf63c6943d8643ff03c7aa17f1cb17b0a8d297d0fd95fc914bdd0e85f81b";
125+
126+
#[test]
127+
fn should_sign_enclave_challenge() {
128+
let result = sign_enclave_challenge(MESSAGE_HASH, ENCLAVE_CHALLENGE_PRIVATE_KEY);
129+
assert!(result.is_ok(), "Signing should succeed with valid inputs");
130+
assert_eq!(
131+
result.unwrap(),
132+
EXPECTED_SIGNATURE,
133+
"The signature should match the expected value exactly"
134+
);
135+
}
136+
137+
#[test]
138+
fn should_not_sign_enclave_challenge_with_invalid_key() {
139+
let invalid_key = "invalid_private_key";
140+
let result = sign_enclave_challenge(MESSAGE_HASH, invalid_key);
141+
assert!(
142+
matches!(
143+
result,
144+
Err(ref err) if err.exit_cause == PostComputeInvalidEnclaveChallengePrivateKey
145+
),
146+
"Should return missing TEE challenge private key error"
147+
);
148+
}
149+
150+
#[test]
151+
fn should_get_challenge() {
152+
with_vars(
153+
vec![
154+
(SIGN_WORKER_ADDRESS, Some(WORKER_ADDRESS)),
155+
(
156+
SIGN_TEE_CHALLENGE_PRIVATE_KEY,
157+
Some(ENCLAVE_CHALLENGE_PRIVATE_KEY),
158+
),
159+
],
160+
|| {
161+
let expected_message_hash = concatenate_and_hash(&[CHAIN_TASK_ID, WORKER_ADDRESS]);
162+
let expected_signature =
163+
sign_enclave_challenge(&expected_message_hash, ENCLAVE_CHALLENGE_PRIVATE_KEY)
164+
.unwrap();
165+
166+
let result = get_challenge(CHAIN_TASK_ID);
167+
assert!(
168+
result.is_ok(),
169+
"get_challenge should succeed with valid environment variables"
170+
);
171+
let signature = result.unwrap();
172+
assert_eq!(
173+
signature, expected_signature,
174+
"The challenge signature should match expected value"
175+
);
176+
},
177+
);
178+
}
179+
180+
#[test]
181+
fn should_fail_on_missing_worker_address_env_var() {
182+
with_vars(
183+
vec![
184+
(SIGN_WORKER_ADDRESS, None),
185+
(
186+
SIGN_TEE_CHALLENGE_PRIVATE_KEY,
187+
Some(ENCLAVE_CHALLENGE_PRIVATE_KEY),
188+
),
189+
],
190+
|| {
191+
let result = get_challenge(CHAIN_TASK_ID);
192+
assert!(
193+
matches!(
194+
result,
195+
Err(ref err) if err.exit_cause == PostComputeWorkerAddressMissing
196+
),
197+
"Should return missing worker address error"
198+
);
199+
},
200+
);
201+
}
202+
203+
#[test]
204+
fn should_fail_on_missing_private_key_env_var() {
205+
with_vars(
206+
vec![
207+
(SIGN_WORKER_ADDRESS, Some(WORKER_ADDRESS)),
208+
(SIGN_TEE_CHALLENGE_PRIVATE_KEY, None),
209+
],
210+
|| {
211+
let result = get_challenge(CHAIN_TASK_ID);
212+
assert!(
213+
matches!(
214+
result,
215+
Err(ref err) if err.exit_cause == PostComputeTeeChallengePrivateKeyMissing
216+
),
217+
"Should return missing private key error"
218+
);
219+
},
220+
);
221+
}
222+
}

src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod hash_utils;
File renamed without changes.

0 commit comments

Comments
 (0)