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)]
130pub struct MailboxAuthorization {
131 id: MailboxIdentifier,
132 expiry: i64,
133 sig: schnorr::Signature,
134}
135
136impl MailboxAuthorization {
137 const CHALENGE_MESSAGE_PREFIX: &'static [u8; 32] = b"Ark VTXO mailbox authorization: ";
138
139 fn signable_message(expiry: i64) -> Message {
140 let mut eng = sha256::Hash::engine();
141 eng.input(Self::CHALENGE_MESSAGE_PREFIX);
142 eng.input(&expiry.to_le_bytes());
143 Message::from_digest(sha256::Hash::from_engine(eng).to_byte_array())
144 }
145
146 pub fn new(
147 mailbox_key: &Keypair,
148 expiry: chrono::DateTime<chrono::Local>,
149 ) -> MailboxAuthorization {
150 let expiry = expiry.timestamp();
151 let msg = Self::signable_message(expiry);
152 MailboxAuthorization {
153 id: MailboxIdentifier::from_pubkey(mailbox_key.public_key()),
154 expiry: expiry,
155 sig: SECP.sign_schnorr(&msg, mailbox_key),
156 }
157 }
158
159 pub fn mailbox(&self) -> MailboxIdentifier {
161 self.id
162 }
163
164 pub fn expiry(&self) -> chrono::DateTime<chrono::Local> {
166 chrono::DateTime::from_timestamp_secs(self.expiry)
167 .expect("we guarantee valid timestamp")
168 .with_timezone(&chrono::Local)
169 }
170
171 pub fn verify(&self) -> bool {
173 let msg = Self::signable_message(self.expiry);
174 SECP.verify_schnorr(&self.sig, &msg, &self.id.as_pubkey().into()).is_ok()
175 }
176}
177
178impl ProtocolEncoding for MailboxAuthorization {
179 fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
180 self.id.encode(w)?;
181 w.emit_slice(&self.expiry.to_le_bytes())?;
182 self.sig.encode(w)?;
183 Ok(())
184 }
185
186 fn decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, ProtocolDecodingError> {
187 Ok(Self {
188 id: ProtocolEncoding::decode(r)?,
189 expiry: {
190 let timestamp = i64::from_le_bytes(r.read_byte_array()?);
191 let _ = chrono::DateTime::from_timestamp_secs(timestamp)
193 .ok_or_else(|| ProtocolDecodingError::invalid("invalid timestamp"))?;
194 timestamp
195 },
196 sig: ProtocolEncoding::decode(r)?,
197 })
198 }
199}
200
201fn point_to_pubkey(point: &[u8; 64]) -> PublicKey {
203 let mut uncompressed = [0u8; 65];
205 uncompressed[0] = 0x04;
206 uncompressed[1..].copy_from_slice(point);
207 PublicKey::from_slice(&uncompressed).expect("invalid uncompressed pk")
208}
209
210#[cfg(test)]
211mod test {
212 use std::time::Duration;
213 use bitcoin::secp256k1::rand;
214 use super::*;
215
216 #[test]
217 fn mailbox_blinding() {
218 let mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
219 let server_key = Keypair::new(&SECP, &mut rand::thread_rng());
220 let vtxo_key = Keypair::new(&SECP, &mut rand::thread_rng());
221
222 let mailbox = MailboxIdentifier::from_pubkey(mailbox_key.public_key());
223
224 let blinded = mailbox.to_blinded(server_key.public_key(), &vtxo_key);
225
226 let unblinded = MailboxIdentifier::from_blinded(
227 blinded, vtxo_key.public_key(), &server_key,
228 );
229
230 assert_eq!(unblinded, mailbox);
231 }
232
233 #[test]
234 fn mailbox_authorization() {
235 let mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
236 let mailbox = MailboxIdentifier::from_pubkey(mailbox_key.public_key());
237
238 let expiry = chrono::Local::now() + Duration::from_secs(60);
239 let auth = MailboxAuthorization::new(&mailbox_key, expiry);
240 assert_eq!(auth.mailbox(), mailbox);
241 assert!(auth.verify());
242
243 assert_eq!(auth, MailboxAuthorization::deserialize(&auth.serialize()).unwrap());
244
245 let decoded = MailboxAuthorization::deserialize_hex("023f6712126b93bd479baec93fa4b6e6eb7aa8100b2e818954a351e2eb459ccbeac3380369000000000163b3184156804eb26ffbad964a70840229c4ac80da5da9f9a7557874c45259af48671aa26f567c3c855092c51a1ceeb8a17c7540abe0a50e89866bdb90ece9").unwrap();
247 assert_eq!(decoded.expiry, 1761818819);
248 assert_eq!(decoded.id.to_string(), "023f6712126b93bd479baec93fa4b6e6eb7aa8100b2e818954a351e2eb459ccbea");
249 assert!(decoded.verify());
250 }
251}
252