1use std::io;
6
7use bitcoin::hashes::{sha256, Hash, HashEngine};
8use bitcoin::secp256k1::{ecdh, schnorr, Keypair, Message, PublicKey};
9use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
10
11use crate::SECP;
12use crate::encode::{ProtocolDecodingError, ProtocolEncoding, ReadExt, WriteExt};
13
14#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct MailboxIdentifier([u8; PUBLIC_KEY_SIZE]);
19
20impl_byte_newtype!(MailboxIdentifier, PUBLIC_KEY_SIZE);
21
22impl MailboxIdentifier {
23 pub fn as_pubkey(&self) -> PublicKey {
25 PublicKey::from_slice(&self.0).expect("invalid pubkey")
26 }
27
28 pub fn from_pubkey(pubkey: PublicKey) -> Self {
30 Self(pubkey.serialize())
31 }
32
33 pub fn to_blinded(
35 &self,
36 server_pubkey: PublicKey,
37 vtxo_key: &Keypair,
38 ) -> BlindedMailboxIdentifier {
39 BlindedMailboxIdentifier::new(*self, server_pubkey, vtxo_key)
40 }
41
42 pub fn from_blinded(
44 blinded: BlindedMailboxIdentifier,
45 vtxo_pubkey: PublicKey,
46 server_key: &Keypair,
47 ) -> MailboxIdentifier {
48 let dh = ecdh::shared_secret_point(&vtxo_pubkey, &server_key.secret_key());
49 let neg_dh_pk = point_to_pubkey(&dh).negate(&SECP);
50 let ret = PublicKey::combine_keys(&[&blinded.as_pubkey(), &neg_dh_pk])
51 .expect("error adding DH secret to mailbox key");
52 Self(ret.serialize())
53 }
54}
55
56impl From<PublicKey> for MailboxIdentifier {
57 fn from(pk: PublicKey) -> Self {
58 Self::from_pubkey(pk)
59 }
60}
61
62impl ProtocolEncoding for MailboxIdentifier {
63 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
64 w.emit_slice(self.as_ref())
65 }
66
67 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
68 Ok(Self(r.read_byte_array()?))
69 }
70}
71
72#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80pub struct BlindedMailboxIdentifier([u8; PUBLIC_KEY_SIZE]);
81
82impl_byte_newtype!(BlindedMailboxIdentifier, PUBLIC_KEY_SIZE);
83
84impl BlindedMailboxIdentifier {
85 pub fn new(
86 mailbox_id: MailboxIdentifier,
87 server_pubkey: PublicKey,
88 vtxo_key: &Keypair,
89 ) -> BlindedMailboxIdentifier {
90 let dh = ecdh::shared_secret_point(&server_pubkey, &vtxo_key.secret_key());
91 let dh_pk = point_to_pubkey(&dh);
92 let ret = PublicKey::combine_keys(&[&mailbox_id.as_pubkey(), &dh_pk])
93 .expect("error adding DH secret to mailbox key");
94 Self(ret.serialize())
95 }
96
97 pub fn as_pubkey(&self) -> PublicKey {
99 PublicKey::from_slice(&self.0).expect("invalid pubkey")
100 }
101
102 pub fn from_pubkey(pubkey: PublicKey) -> Self {
104 Self(pubkey.serialize())
105 }
106}
107
108impl From<PublicKey> for BlindedMailboxIdentifier {
109 fn from(pk: PublicKey) -> Self {
110 Self::from_pubkey(pk)
111 }
112}
113
114impl ProtocolEncoding for BlindedMailboxIdentifier {
115 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
116 w.emit_slice(self.as_ref())
117 }
118
119 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
120 Ok(Self(r.read_byte_array()?))
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct MailboxAuthorization {
129 id: MailboxIdentifier,
130 expiry: i64,
131 sig: schnorr::Signature,
132}
133
134impl MailboxAuthorization {
135 const CHALENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark VTXO mailbox authorization: ";
136
137 fn signable_message(expiry: i64) -> Message {
138 let mut eng = sha256::Hash::engine();
139 eng.input(Self::CHALENGE_MESSAGE_PREFIX);
140 eng.input(&expiry.to_le_bytes());
141 Message::from_digest(sha256::Hash::from_engine(eng).to_byte_array())
142 }
143
144 pub fn new(
145 mailbox_key: &Keypair,
146 expiry: chrono::DateTime<chrono::Local>,
147 ) -> MailboxAuthorization {
148 let expiry = expiry.timestamp();
149 let msg = Self::signable_message(expiry);
150 MailboxAuthorization {
151 id: MailboxIdentifier::from_pubkey(mailbox_key.public_key()),
152 expiry: expiry,
153 sig: SECP.sign_schnorr_with_aux_rand(&msg, mailbox_key, &rand::random()),
154 }
155 }
156
157 pub fn mailbox(&self) -> MailboxIdentifier {
159 self.id
160 }
161
162 pub fn expiry(&self) -> chrono::DateTime<chrono::Local> {
164 chrono::DateTime::from_timestamp_secs(self.expiry)
165 .expect("we guarantee valid timestamp")
166 .with_timezone(&chrono::Local)
167 }
168
169 pub fn verify(&self) -> bool {
171 let msg = Self::signable_message(self.expiry);
172 SECP.verify_schnorr(&self.sig, &msg, &self.id.as_pubkey().into()).is_ok()
173 }
174
175 pub fn is_expired(&self) -> bool {
176 self.expiry() < chrono::Local::now()
177 }
178}
179
180impl ProtocolEncoding for MailboxAuthorization {
181 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
182 self.id.encode(w)?;
183 w.emit_slice(&self.expiry.to_le_bytes())?;
184 self.sig.encode(w)?;
185 Ok(())
186 }
187
188 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
189 Ok(Self {
190 id: ProtocolEncoding::decode(r)?,
191 expiry: {
192 let timestamp = i64::from_le_bytes(r.read_byte_array()?);
193 let _ = chrono::DateTime::from_timestamp_secs(timestamp)
195 .ok_or_else(|| ProtocolDecodingError::invalid("invalid timestamp"))?;
196 timestamp
197 },
198 sig: ProtocolEncoding::decode(r)?,
199 })
200 }
201}
202
203fn point_to_pubkey(point: &[u8; 64]) -> PublicKey {
205 let mut uncompressed = [0u8; 65];
207 uncompressed[0] = 0x04;
208 uncompressed[1..].copy_from_slice(point);
209 PublicKey::from_slice(&uncompressed).expect("invalid uncompressed pk")
210}
211
212#[cfg(test)]
213mod test {
214 use std::time::Duration;
215 use bitcoin::secp256k1::rand;
216 use super::*;
217
218 #[test]
219 fn mailbox_blinding() {
220 let mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
221 let server_mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
222 let vtxo_key = Keypair::new(&SECP, &mut rand::thread_rng());
223
224 let mailbox = MailboxIdentifier::from_pubkey(mailbox_key.public_key());
225
226 let blinded = mailbox.to_blinded(server_mailbox_key.public_key(), &vtxo_key);
227
228 let unblinded = MailboxIdentifier::from_blinded(
229 blinded, vtxo_key.public_key(), &server_mailbox_key,
230 );
231
232 assert_eq!(unblinded, mailbox);
233 }
234
235 #[test]
236 fn mailbox_authorization() {
237 let mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
238 let mailbox = MailboxIdentifier::from_pubkey(mailbox_key.public_key());
239
240 let expiry = chrono::Local::now() + Duration::from_secs(60);
241 let auth = MailboxAuthorization::new(&mailbox_key, expiry);
242 assert_eq!(auth.mailbox(), mailbox);
243 assert!(auth.verify());
244
245 assert_eq!(auth, MailboxAuthorization::deserialize(&auth.serialize()).unwrap());
246
247 let decoded = MailboxAuthorization::deserialize_hex("023f6712126b93bd479baec93fa4b6e6eb7aa8100b2e818954a351e2eb459ccbeac3380369000000000163b3184156804eb26ffbad964a70840229c4ac80da5da9f9a7557874c45259af48671aa26f567c3c855092c51a1ceeb8a17c7540abe0a50e89866bdb90ece9").unwrap();
249 assert_eq!(decoded.expiry, 1761818819);
250 assert_eq!(decoded.id.to_string(), "023f6712126b93bd479baec93fa4b6e6eb7aa8100b2e818954a351e2eb459ccbea");
251 assert!(decoded.verify());
252 }
253}
254