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}