heddle_api/
request_proof.rs1use ed25519_dalek::{Signature, VerifyingKey};
6
7use crate::{heddle::api::common::CallContext, v2::MethodDescriptor};
8
9pub const PROOF_WINDOW_MILLIS: u64 = 60_000;
10
11#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
12pub enum RequestProofError {
13 #[error("request and context operation IDs differ or are missing")]
14 OperationId,
15 #[error("request proof is missing, malformed, or outside its time window")]
16 InvalidProof,
17 #[error("request signing identity differs from verified capability key")]
18 Identity,
19 #[error("request signature is invalid")]
20 Signature,
21 #[error("request operation ID could not be decoded")]
22 RequestMetadata,
23}
24
25pub struct VerifiedRequestProof<'a> {
26 pub identity: &'a str,
27 pub nonce: &'a [u8],
28}
29
30pub fn verify_native_request_proof<'a>(
33 context: &'a CallContext,
34 method: &'static MethodDescriptor,
35 body: &[u8],
36 effective_key: &[u8; 32],
37 now_millis: i64,
38) -> Result<VerifiedRequestProof<'a>, RequestProofError> {
39 let operation_id = method
40 .client_operation_id(body)
41 .map_err(|_| RequestProofError::RequestMetadata)?
42 .unwrap_or_default();
43 if operation_id != context.client_operation_id
44 || (method.client_operation_id_required && operation_id.is_empty())
45 {
46 return Err(RequestProofError::OperationId);
47 }
48 let proof = context
49 .request_proof
50 .as_ref()
51 .ok_or(RequestProofError::InvalidProof)?;
52 if proof.algorithm != "ed25519"
53 || proof.nonce.len() != 16
54 || now_millis.abs_diff(proof.timestamp_millis) > PROOF_WINDOW_MILLIS
55 {
56 return Err(RequestProofError::InvalidProof);
57 }
58 let identity = format!("principal:device-key:{}", hex::encode(effective_key));
59 if proof.signing_identity != identity {
60 return Err(RequestProofError::Identity);
61 }
62 let verifying_key =
63 VerifyingKey::from_bytes(effective_key).map_err(|_| RequestProofError::Signature)?;
64 let signature: [u8; 64] = proof
65 .signature
66 .as_slice()
67 .try_into()
68 .map_err(|_| RequestProofError::Signature)?;
69 verifying_key
70 .verify_strict(
71 &crate::signing::unary_bytes(
72 &identity,
73 method.path,
74 proof.timestamp_millis,
75 &proof.nonce,
76 body,
77 ),
78 &Signature::from_bytes(&signature),
79 )
80 .map_err(|_| RequestProofError::Signature)?;
81 Ok(VerifiedRequestProof {
82 identity: &proof.signing_identity,
83 nonce: &proof.nonce,
84 })
85}