fedimint_lnv2_common/
contracts.rs

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}