mur_common/bridge/
envelope.rs1use ed25519_dalek::Signer;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct SignedEnvelope {
10 #[serde(with = "serde_bytes")]
11 pub payload: Vec<u8>,
12 #[serde(with = "serde_bytes")]
13 pub sig: Vec<u8>,
14 pub key_version: u32,
15 pub bridge_pubkey_multibase: String,
16}
17
18impl SignedEnvelope {
19 pub fn canonical_payload_for_signing(&self) -> &[u8] {
20 &self.payload
21 }
22}
23
24#[derive(thiserror::Error, Debug)]
25pub enum EnvelopeError {
26 #[error("multibase decode: {0}")]
27 Multibase(#[from] crate::identity::IdentityError),
28 #[error("bad sig length: expected 64, got {0}")]
29 BadSigLen(usize),
30 #[error("signature does not verify")]
31 SignatureMismatch,
32 #[error("untrusted peer")]
33 UntrustedPeer,
34}
35
36pub fn sign_payload(
43 payload: Vec<u8>,
44 identity: &crate::identity::AgentIdentity,
45 key_version: u32,
46) -> SignedEnvelope {
47 let sig = identity.signing_key().sign(&payload);
48 SignedEnvelope {
49 payload,
50 sig: sig.to_bytes().to_vec(),
51 key_version,
52 bridge_pubkey_multibase: crate::identity::encode_pubkey(&identity.verifying_key()),
53 }
54}
55
56pub fn verify_envelope_with_pubkey(
62 env: &SignedEnvelope,
63 expected_pubkey: &str,
64) -> Result<(), EnvelopeError> {
65 use ed25519_dalek::{Signature, VerifyingKey};
66 if env.sig.len() != 64 {
67 return Err(EnvelopeError::BadSigLen(env.sig.len()));
68 }
69 let pub_bytes = crate::identity::decode_pubkey(expected_pubkey)?;
70 let vk = VerifyingKey::from_bytes(&pub_bytes).map_err(|_| EnvelopeError::SignatureMismatch)?;
71 let sig_arr: [u8; 64] = env.sig.as_slice().try_into().unwrap();
72 let sig = Signature::from_bytes(&sig_arr);
73 vk.verify_strict(&env.payload, &sig)
74 .map_err(|_| EnvelopeError::SignatureMismatch)?;
75 Ok(())
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 #[test]
82 fn canonical_payload_is_passthrough() {
83 let payload = serde_json::json!({"a": 1}).to_string().into_bytes();
84 let e = SignedEnvelope {
85 payload: payload.clone(),
86 sig: vec![0u8; 64],
87 key_version: 1,
88 bridge_pubkey_multibase: "z".into(),
89 };
90 assert_eq!(e.canonical_payload_for_signing(), payload.as_slice());
91 }
92
93 #[test]
94 fn sign_then_verify_round_trips() {
95 use crate::identity::AgentIdentity;
96 let id = AgentIdentity::generate();
97 let env = sign_payload(b"hello".to_vec(), &id, 7);
98 assert_eq!(env.key_version, 7);
99 verify_envelope_with_pubkey(&env, &env.bridge_pubkey_multibase).unwrap();
100 }
101
102 #[test]
103 fn verify_with_wrong_pubkey_fails() {
104 use crate::identity::{AgentIdentity, encode_pubkey};
105 let a = AgentIdentity::generate();
106 let b = AgentIdentity::generate();
107 let env = sign_payload(b"x".to_vec(), &a, 0);
108 let pub_b = encode_pubkey(&b.verifying_key());
109 assert!(matches!(
110 verify_envelope_with_pubkey(&env, &pub_b).unwrap_err(),
111 EnvelopeError::SignatureMismatch
112 ));
113 }
114
115 #[test]
116 fn tampered_payload_fails() {
117 use crate::identity::AgentIdentity;
118 let id = AgentIdentity::generate();
119 let mut env = sign_payload(b"orig".to_vec(), &id, 0);
120 env.payload = b"tamper".to_vec();
121 let pub_ = env.bridge_pubkey_multibase.clone();
122 assert!(matches!(
123 verify_envelope_with_pubkey(&env, &pub_).unwrap_err(),
124 EnvelopeError::SignatureMismatch
125 ));
126 }
127}