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
36const ENVELOPE_DOMAIN: &[u8] = b"mur-bridge-envelope-v1";
45
46fn signing_bytes(payload: &[u8], key_version: u32) -> Vec<u8> {
50 let mut out = Vec::with_capacity(ENVELOPE_DOMAIN.len() + 4 + payload.len());
51 out.extend_from_slice(ENVELOPE_DOMAIN);
52 out.extend_from_slice(&key_version.to_le_bytes());
53 out.extend_from_slice(payload);
54 out
55}
56
57pub fn sign_payload(
64 payload: Vec<u8>,
65 identity: &crate::identity::AgentIdentity,
66 key_version: u32,
67) -> SignedEnvelope {
68 let sig = identity
69 .signing_key()
70 .sign(&signing_bytes(&payload, key_version));
71 SignedEnvelope {
72 payload,
73 sig: sig.to_bytes().to_vec(),
74 key_version,
75 bridge_pubkey_multibase: crate::identity::encode_pubkey(&identity.verifying_key()),
76 }
77}
78
79pub fn verify_envelope_with_pubkey(
85 env: &SignedEnvelope,
86 expected_pubkey: &str,
87) -> Result<(), EnvelopeError> {
88 use ed25519_dalek::{Signature, VerifyingKey};
89 if env.sig.len() != 64 {
90 return Err(EnvelopeError::BadSigLen(env.sig.len()));
91 }
92 let pub_bytes = crate::identity::decode_pubkey(expected_pubkey)?;
93 let vk = VerifyingKey::from_bytes(&pub_bytes).map_err(|_| EnvelopeError::SignatureMismatch)?;
94 let sig_arr: [u8; 64] = env.sig.as_slice().try_into().unwrap();
95 let sig = Signature::from_bytes(&sig_arr);
96 let msg = signing_bytes(&env.payload, env.key_version);
100 vk.verify_strict(&msg, &sig)
101 .map_err(|_| EnvelopeError::SignatureMismatch)?;
102 Ok(())
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 #[test]
109 fn canonical_payload_is_passthrough() {
110 let payload = serde_json::json!({"a": 1}).to_string().into_bytes();
111 let e = SignedEnvelope {
112 payload: payload.clone(),
113 sig: vec![0u8; 64],
114 key_version: 1,
115 bridge_pubkey_multibase: "z".into(),
116 };
117 assert_eq!(e.canonical_payload_for_signing(), payload.as_slice());
118 }
119
120 #[test]
121 fn sign_then_verify_round_trips() {
122 use crate::identity::AgentIdentity;
123 let id = AgentIdentity::generate();
124 let env = sign_payload(b"hello".to_vec(), &id, 7);
125 assert_eq!(env.key_version, 7);
126 verify_envelope_with_pubkey(&env, &env.bridge_pubkey_multibase).unwrap();
127 }
128
129 #[test]
130 fn verify_with_wrong_pubkey_fails() {
131 use crate::identity::{AgentIdentity, encode_pubkey};
132 let a = AgentIdentity::generate();
133 let b = AgentIdentity::generate();
134 let env = sign_payload(b"x".to_vec(), &a, 0);
135 let pub_b = encode_pubkey(&b.verifying_key());
136 assert!(matches!(
137 verify_envelope_with_pubkey(&env, &pub_b).unwrap_err(),
138 EnvelopeError::SignatureMismatch
139 ));
140 }
141
142 #[test]
143 fn tampered_payload_fails() {
144 use crate::identity::AgentIdentity;
145 let id = AgentIdentity::generate();
146 let mut env = sign_payload(b"orig".to_vec(), &id, 0);
147 env.payload = b"tamper".to_vec();
148 let pub_ = env.bridge_pubkey_multibase.clone();
149 assert!(matches!(
150 verify_envelope_with_pubkey(&env, &pub_).unwrap_err(),
151 EnvelopeError::SignatureMismatch
152 ));
153 }
154
155 #[test]
156 fn tampered_key_version_fails() {
157 use crate::identity::AgentIdentity;
160 let id = AgentIdentity::generate();
161 let mut env = sign_payload(b"orig".to_vec(), &id, 3);
162 let pub_ = env.bridge_pubkey_multibase.clone();
163 verify_envelope_with_pubkey(&env, &pub_).expect("untampered verifies");
164 env.key_version = 4;
165 assert!(matches!(
166 verify_envelope_with_pubkey(&env, &pub_).unwrap_err(),
167 EnvelopeError::SignatureMismatch
168 ));
169 }
170}