1use bitcoin::hashes::sha256;
2use bitcoin::secp256k1;
3use fedimint_core::Amount;
4use fedimint_core::encoding::{Decodable, Encodable};
5use secp256k1::schnorr::Signature;
6use secp256k1::{Message, PublicKey, SecretKey};
7use serde::{Deserialize, Serialize};
8use tpe::{
9 AggregateDecryptionKey, AggregatePublicKey, CipherText, DecryptionKeyShare, PublicKeyShare,
10 SecretKeyShare, create_dk_share, decrypt_preimage, encrypt_preimage, verify_agg_dk,
11 verify_ciphertext, verify_dk_share,
12};
13
14use crate::ContractId;
15
16#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
17pub enum PaymentImage {
18 Hash(sha256::Hash),
19 Point(PublicKey),
20}
21
22#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
23pub struct IncomingContract {
24 pub commitment: Commitment,
25 pub ciphertext: CipherText,
26}
27
28#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
29pub struct Commitment {
30 pub payment_image: PaymentImage,
31 pub amount: Amount,
32 #[serde(rename = "expiration")]
36 pub expiration_or_fee: u64,
37 pub claim_pk: PublicKey,
38 pub refund_pk: PublicKey,
39 pub ephemeral_pk: PublicKey,
40}
41
42pub fn fee_encoded_expiration(fee_msats: u64) -> u64 {
53 u64::MAX - fee_msats
54}
55
56pub fn fee_from_expiration(expiration: u64) -> u64 {
59 u64::MAX - expiration
60}
61
62impl IncomingContract {
63 #[allow(clippy::too_many_arguments)]
64 pub fn new(
65 agg_pk: AggregatePublicKey,
66 encryption_seed: [u8; 32],
67 preimage: [u8; 32],
68 payment_image: PaymentImage,
69 amount: Amount,
70 expiration: u64,
71 claim_pk: PublicKey,
72 refund_pk: PublicKey,
73 ephemeral_pk: PublicKey,
74 ) -> Self {
75 let commitment = Commitment {
76 payment_image,
77 amount,
78 expiration_or_fee: expiration,
79 claim_pk,
80 refund_pk,
81 ephemeral_pk,
82 };
83
84 let ciphertext = encrypt_preimage(
85 &agg_pk,
86 &encryption_seed,
87 &preimage,
88 &commitment.consensus_hash(),
89 );
90
91 IncomingContract {
92 commitment,
93 ciphertext,
94 }
95 }
96
97 pub fn contract_id(&self) -> ContractId {
98 ContractId(self.consensus_hash())
99 }
100
101 pub fn verify(&self) -> bool {
102 verify_ciphertext(&self.ciphertext, &self.commitment.consensus_hash())
103 }
104
105 pub fn verify_decryption_share(
106 &self,
107 pk: &PublicKeyShare,
108 dk_share: &DecryptionKeyShare,
109 ) -> bool {
110 verify_dk_share(
111 pk,
112 dk_share,
113 &self.ciphertext,
114 &self.commitment.consensus_hash(),
115 )
116 }
117
118 pub fn verify_agg_decryption_key(
119 &self,
120 agg_pk: &AggregatePublicKey,
121 agg_decryption_key: &AggregateDecryptionKey,
122 ) -> bool {
123 verify_agg_dk(
124 agg_pk,
125 agg_decryption_key,
126 &self.ciphertext,
127 &self.commitment.consensus_hash(),
128 )
129 }
130
131 pub fn verify_preimage(&self, preimage: &[u8; 32]) -> bool {
132 verify_preimage(&self.commitment.payment_image, preimage)
133 }
134
135 pub fn decrypt_preimage(
136 &self,
137 agg_decryption_key: &AggregateDecryptionKey,
138 ) -> Option<[u8; 32]> {
139 let preimage = decrypt_preimage(&self.ciphertext, agg_decryption_key);
140
141 if self.verify_preimage(&preimage) {
142 Some(preimage)
143 } else {
144 None
145 }
146 }
147
148 pub fn create_decryption_key_share(&self, sk: &SecretKeyShare) -> DecryptionKeyShare {
149 create_dk_share(sk, &self.ciphertext)
150 }
151}
152
153#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
154pub struct OutgoingContract {
155 pub payment_image: PaymentImage,
156 pub amount: Amount,
157 pub expiration: u64,
158 pub claim_pk: PublicKey,
159 pub refund_pk: PublicKey,
160 pub ephemeral_pk: PublicKey,
161}
162
163impl OutgoingContract {
164 pub fn contract_id(&self) -> ContractId {
165 ContractId(self.consensus_hash())
166 }
167
168 pub fn forfeit_message(&self) -> Message {
169 Message::from_digest(*self.contract_id().0.as_ref())
170 }
171
172 pub fn verify_preimage(&self, preimage: &[u8; 32]) -> bool {
173 verify_preimage(&self.payment_image, preimage)
174 }
175
176 pub fn verify_forfeit_signature(&self, signature: &Signature) -> bool {
177 secp256k1::global::SECP256K1
178 .verify_schnorr(
179 signature,
180 &self.forfeit_message(),
181 &self.claim_pk.x_only_public_key().0,
182 )
183 .is_ok()
184 }
185
186 pub fn verify_gateway_response(&self, gateway_response: &Result<[u8; 32], Signature>) -> bool {
187 match gateway_response {
188 Ok(preimage) => self.verify_preimage(preimage),
189 Err(signature) => self.verify_forfeit_signature(signature),
190 }
191 }
192
193 pub fn verify_invoice_auth(&self, message: sha256::Hash, signature: &Signature) -> bool {
194 secp256k1::global::SECP256K1
195 .verify_schnorr(
196 signature,
197 &Message::from_digest(*message.as_ref()),
198 &self.refund_pk.x_only_public_key().0,
199 )
200 .is_ok()
201 }
202}
203
204fn verify_preimage(payment_image: &PaymentImage, preimage: &[u8; 32]) -> bool {
205 match payment_image {
206 PaymentImage::Hash(hash) => preimage.consensus_hash::<sha256::Hash>() == *hash,
207 PaymentImage::Point(pk) => match SecretKey::from_slice(preimage) {
208 Ok(sk) => sk.public_key(secp256k1::SECP256K1) == *pk,
209 Err(..) => false,
210 },
211 }
212}
213
214#[test]
215fn test_verify_preimage() {
216 use bitcoin::hashes::Hash;
217
218 assert!(verify_preimage(
219 &PaymentImage::Hash(bitcoin::hashes::sha256::Hash::hash(&[42; 32])),
220 &[42; 32]
221 ));
222
223 let (secret_key, public_key) = secp256k1::generate_keypair(&mut secp256k1::rand::thread_rng());
224
225 assert!(verify_preimage(
226 &PaymentImage::Point(public_key),
227 &secret_key.secret_bytes()
228 ));
229}