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