cast/cmd/tempo/zone/
encryption.rs1use super::abi::DepositPayload;
6use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
7use alloy_primitives::{Address, B256, U256};
8use eyre::{Result, eyre};
9use hkdf::Hkdf;
10use k256::{
11 AffinePoint, ProjectivePoint, PublicKey, SecretKey, elliptic_curve::sec1::ToEncodedPoint,
12};
13use sha2::Sha256;
14
15pub(super) fn encrypt_deposit(
17 x: B256,
18 parity: u8,
19 to: Address,
20 memo: B256,
21 sender: Address,
22 portal: Address,
23 key_index: U256,
24) -> Result<DepositPayload> {
25 let mut compressed = [0u8; 33];
26 compressed[0] = match parity {
27 0 | 1 => parity + 2,
28 2 | 3 => parity,
29 _ => return Err(eyre!("invalid sequencer encryption key parity")),
30 };
31 compressed[1..].copy_from_slice(x.as_slice());
32 let public = PublicKey::from_sec1_bytes(&compressed)
33 .map_err(|_| eyre!("invalid sequencer encryption public key"))?;
34 let ephemeral = SecretKey::random(&mut rand_08::thread_rng());
35 let encoded = ephemeral.public_key().to_encoded_point(true);
36 let ephemeral_x = B256::from_slice(encoded.x().unwrap());
37 let shared = AffinePoint::from(
38 ProjectivePoint::from(*public.as_affine()) * *ephemeral.to_nonzero_scalar(),
39 );
40 let shared = shared.to_encoded_point(true);
41 let mut info = [0u8; 104];
42 info[..20].copy_from_slice(portal.as_slice());
43 info[20..52].copy_from_slice(&key_index.to_be_bytes::<32>());
44 info[52..84].copy_from_slice(ephemeral_x.as_slice());
45 info[84..].copy_from_slice(sender.as_slice());
46 let mut key = [0u8; 32];
47 Hkdf::<Sha256>::new(Some(b"ecies-aes-key"), shared.x().unwrap())
48 .expand(&info, &mut key)
49 .map_err(|_| eyre!("deposit key derivation failed"))?;
50 let mut plaintext = [0u8; 64];
51 plaintext[..20].copy_from_slice(to.as_slice());
52 plaintext[20..52].copy_from_slice(memo.as_slice());
53 let nonce: [u8; 12] = rand_08::random();
54 let mut encrypted = Aes256Gcm::new((&key).into())
55 .encrypt(Nonce::from_slice(&nonce), plaintext.as_ref())
56 .map_err(|_| eyre!("deposit encryption failed"))?;
57 let tag = encrypted.split_off(64);
58 Ok(DepositPayload {
59 ephemeralPubkeyX: ephemeral_x,
60 ephemeralPubkeyYParity: encoded.as_bytes()[0],
61 ciphertext: encrypted.into(),
62 nonce: nonce.into(),
63 tag: tag.as_slice().try_into()?,
64 })
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn deposit_roundtrip_and_context_binding() {
73 let sequencer = SecretKey::from_slice(&[7; 32]).unwrap();
74 let public = sequencer.public_key().to_encoded_point(true);
75 let x = B256::from_slice(public.x().unwrap());
76 let to = Address::repeat_byte(0xbb);
77 let memo = B256::repeat_byte(0xcc);
78 let sender = Address::repeat_byte(0xdd);
79 let portal = Address::repeat_byte(0xaa);
80 let index = U256::from(42);
81 let payload =
82 encrypt_deposit(x, public.as_bytes()[0], to, memo, sender, portal, index).unwrap();
83 assert_eq!(payload.ciphertext.len(), 64);
84 let mut ephemeral = [0u8; 33];
85 ephemeral[0] = payload.ephemeralPubkeyYParity;
86 ephemeral[1..].copy_from_slice(payload.ephemeralPubkeyX.as_slice());
87 let ephemeral = PublicKey::from_sec1_bytes(&ephemeral).unwrap();
88 let shared = AffinePoint::from(
89 ProjectivePoint::from(*ephemeral.as_affine()) * *sequencer.to_nonzero_scalar(),
90 );
91 let shared = shared.to_encoded_point(true);
92 let mut info = [
94 portal.as_slice(),
95 &index.to_be_bytes::<32>(),
96 payload.ephemeralPubkeyX.as_slice(),
97 sender.as_slice(),
98 ]
99 .concat();
100 let mut ciphertext = payload.ciphertext.to_vec();
101 ciphertext.extend_from_slice(payload.tag.as_slice());
102 let mut key = [0u8; 32];
103 Hkdf::<Sha256>::new(Some(b"ecies-aes-key"), shared.x().unwrap())
104 .expand(&info, &mut key)
105 .unwrap();
106 let cipher = Aes256Gcm::new((&key).into());
107 let plaintext = cipher
108 .decrypt(Nonce::from_slice(payload.nonce.as_slice()), ciphertext.as_slice())
109 .unwrap();
110 assert_eq!(&plaintext[..20], to.as_slice());
111 assert_eq!(&plaintext[20..52], memo.as_slice());
112 assert_eq!(&plaintext[52..], &[0; 12]);
113 for offset in [0, 20, 52, 84] {
115 info[offset] ^= 1;
116 Hkdf::<Sha256>::new(Some(b"ecies-aes-key"), shared.x().unwrap())
117 .expand(&info, &mut key)
118 .unwrap();
119 assert!(
120 Aes256Gcm::new((&key).into())
121 .decrypt(Nonce::from_slice(payload.nonce.as_slice()), ciphertext.as_slice())
122 .is_err()
123 );
124 info[offset] ^= 1;
125 }
126 let second =
127 encrypt_deposit(x, public.as_bytes()[0], to, memo, sender, portal, index).unwrap();
128 assert_ne!(payload.ephemeralPubkeyX, second.ephemeralPubkeyX);
129 assert_ne!(payload.nonce, second.nonce);
130 }
131
132 #[test]
133 fn rejects_invalid_encryption_keys() {
134 for parity in [0, 1, 2, 3, 4, 255] {
135 assert!(
136 encrypt_deposit(
137 B256::repeat_byte(0xff),
138 parity,
139 Address::ZERO,
140 B256::ZERO,
141 Address::ZERO,
142 Address::ZERO,
143 U256::ZERO
144 )
145 .is_err()
146 );
147 }
148 }
149}