Skip to main content

fedimint_ln_common/contracts/
incoming.rs

1use std::io::Error;
2
3use bitcoin::hashes::sha256::Hash as Sha256;
4use bitcoin::hashes::{Hash as BitcoinHash, hash_newtype};
5use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
6use fedimint_core::module::registry::ModuleDecoderRegistry;
7use fedimint_core::{Amount, OutPoint, secp256k1};
8use serde::{Deserialize, Serialize};
9
10use crate::LightningInput;
11use crate::contracts::{ContractId, DecryptedPreimage, EncryptedPreimage, IdentifiableContract};
12
13#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
14pub struct IncomingContractOffer {
15    /// Amount for which the user is willing to sell the preimage
16    pub amount: fedimint_core::Amount,
17    pub hash: bitcoin::hashes::sha256::Hash,
18    pub encrypted_preimage: EncryptedPreimage,
19    pub expiry_time: Option<u64>,
20}
21
22impl IncomingContractOffer {
23    pub fn id(&self) -> OfferId {
24        OfferId::from_raw_hash(self.hash)
25    }
26
27    /// Id of the incoming contract that funding this offer creates.
28    ///
29    /// An incoming contract's id commits to the payment hash alone, so it is
30    /// fully determined by the offer.
31    pub fn contract_id(&self) -> ContractId {
32        ContractId::from_raw_hash(self.hash)
33    }
34}
35
36// FIXME: the protocol currently envisions the use of a pub key as preimage.
37// This is bad for privacy though since pub keys are distinguishable from
38// randomness and the payer would learn the recipient is using a federated mint.
39// Probably best to just hash the key before.
40
41// FIXME: encrypt preimage to LN gateway?
42
43/// Specialized smart contract for incoming payments
44///
45/// A user generates a private/public keypair that can later be used to claim
46/// the incoming funds. The public key is defined as the preimage of a
47/// payment hash and threshold-encrypted to the federation's public key. They
48/// then put up the encrypted preimage for sale by creating an
49/// [`IncomingContractOffer`].
50///
51/// A lightning gateway wanting to claim an incoming HTLC can now use the offer
52/// to buy the preimage by transferring funds into the corresponding contract.
53/// This activates the threshold decryption process inside the federation. Since
54/// the user could have threshold-encrypted useless data there are two possible
55/// outcomes:
56///
57///   1. The decryption results in a valid preimage which is given to the
58///      lightning gateway. The user can in return claim the funds from the
59///      contract. For this they need to be able to sign with the private key
60///      corresponding to the public key which they used as preimage.
61///   2. The decryption results in an invalid preimage, the gateway can claim
62///      back the money. For this to work securely they have to specify a public
63///      key when creating the actual contract.
64// TODO: don't duplicate offer, include id instead and fetch offer on mint side
65#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
66pub struct IncomingContract {
67    /// Payment hash which's corresponding preimage is being sold
68    pub hash: bitcoin::hashes::sha256::Hash,
69    /// Encrypted preimage as specified in offer
70    pub encrypted_preimage: EncryptedPreimage,
71    /// Status of preimage decryption, will either end in failure or contain the
72    /// preimage eventually. In case decryption was successful the preimage
73    /// is also the public key locking the contract, allowing the offer
74    /// creator to redeem their money.
75    pub decrypted_preimage: DecryptedPreimage,
76    /// Key that can unlock contract in case the decrypted preimage was invalid
77    pub gateway_key: secp256k1::PublicKey,
78}
79
80/// The funded version of an [`IncomingContract`] contains the [`OutPoint`] of
81/// it's creation. Since this kind of contract can only be funded once this out
82/// point is unambiguous. The out point is used to update the output outcome
83/// once decryption finishes.
84#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)]
85pub struct FundedIncomingContract {
86    pub contract: IncomingContract,
87    /// Incoming contracts are funded exactly once, so they have an associated
88    /// out-point. We use it to report the outcome of the preimage
89    /// decryption started by the funding in the output's outcome (This can
90    /// already be queried by users, making an additional way of querying
91    /// contract states unnecessary for now).
92    pub out_point: OutPoint,
93}
94
95hash_newtype!(
96    /// The hash of a LN incoming contract offer
97    pub struct OfferId(Sha256);
98);
99
100impl IdentifiableContract for IncomingContract {
101    fn contract_id(&self) -> ContractId {
102        ContractId::from_raw_hash(self.hash)
103    }
104}
105
106impl Encodable for OfferId {
107    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
108        self.to_byte_array().consensus_encode(writer)
109    }
110}
111
112impl Decodable for OfferId {
113    fn consensus_decode_partial<D: std::io::Read>(
114        d: &mut D,
115        modules: &ModuleDecoderRegistry,
116    ) -> Result<Self, DecodeError> {
117        Ok(OfferId::from_byte_array(
118            Decodable::consensus_decode_partial(d, modules)?,
119        ))
120    }
121}
122
123#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)]
124pub struct IncomingContractAccount {
125    pub amount: Amount,
126    pub contract: IncomingContract,
127}
128
129impl IncomingContractAccount {
130    pub fn claim(&self) -> LightningInput {
131        LightningInput::new_v0(self.contract.contract_id(), self.amount, None)
132    }
133}