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