Skip to main content

fedimint_ln_common/contracts/
mod.rs

1pub mod incoming;
2pub mod outgoing;
3
4use std::fmt::Display;
5use std::io::Error;
6#[cfg(feature = "uniffi")]
7use std::str::FromStr;
8
9use bitcoin::hashes::sha256::Hash as Sha256;
10use bitcoin::hashes::{Hash as BitcoinHash, hash_newtype};
11use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
12use fedimint_core::hex::ToHex;
13use fedimint_core::module::registry::ModuleDecoderRegistry;
14use fedimint_core::{OutPoint, secp256k1};
15use serde::{Deserialize, Serialize};
16
17/// Anything representing a contract which thus has an associated [`ContractId`]
18pub trait IdentifiableContract: Encodable {
19    fn contract_id(&self) -> ContractId;
20}
21
22hash_newtype!(
23    /// The hash of a LN incoming contract
24    pub struct ContractId(Sha256);
25);
26
27#[cfg(feature = "uniffi")]
28uniffi::custom_type!(ContractId, String, {
29    lower: |contract_id| contract_id.to_string(),
30    try_lift: |s| ContractId::from_str(&s).map_err(Into::into),
31});
32
33/// A contract before execution as found in transaction outputs
34// TODO: investigate if this is actually a problem
35#[allow(clippy::large_enum_variant)]
36#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
37pub enum Contract {
38    Incoming(incoming::IncomingContract),
39    Outgoing(outgoing::OutgoingContract),
40}
41
42/// A contract after execution as saved in the database
43#[allow(clippy::large_enum_variant)]
44#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize)]
45pub enum FundedContract {
46    Incoming(incoming::FundedIncomingContract),
47    Outgoing(outgoing::OutgoingContract),
48}
49
50/// Outcome of a contract. Only incoming contracts currently need to communicate
51/// anything back to the user (the decrypted preimage).
52#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
53pub enum ContractOutcome {
54    Incoming(DecryptedPreimage),
55    Outgoing(OutgoingContractOutcome),
56}
57
58impl ContractOutcome {
59    pub fn is_permanent(&self) -> bool {
60        match self {
61            ContractOutcome::Incoming(o) => o.is_permanent(),
62            ContractOutcome::Outgoing(_) => true,
63        }
64    }
65}
66
67#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
68pub struct OutgoingContractOutcome {}
69
70impl IdentifiableContract for Contract {
71    fn contract_id(&self) -> ContractId {
72        match self {
73            Contract::Incoming(c) => c.contract_id(),
74            Contract::Outgoing(c) => c.contract_id(),
75        }
76    }
77}
78
79impl IdentifiableContract for FundedContract {
80    fn contract_id(&self) -> ContractId {
81        match self {
82            FundedContract::Incoming(c) => c.contract.contract_id(),
83            FundedContract::Outgoing(c) => c.contract_id(),
84        }
85    }
86}
87
88impl Contract {
89    /// Creates the initial contract outcome that is created on transaction
90    /// acceptance. Depending on the contract type it is not yet final.
91    pub fn to_outcome(&self) -> ContractOutcome {
92        match self {
93            Contract::Incoming(_) => ContractOutcome::Incoming(DecryptedPreimage::Pending),
94            Contract::Outgoing(_) => ContractOutcome::Outgoing(OutgoingContractOutcome {}),
95        }
96    }
97
98    /// Converts a contract to its executed version.
99    pub fn to_funded(self, out_point: OutPoint) -> FundedContract {
100        match self {
101            Contract::Incoming(incoming) => {
102                FundedContract::Incoming(incoming::FundedIncomingContract {
103                    contract: incoming,
104                    out_point,
105                })
106            }
107            Contract::Outgoing(outgoing) => FundedContract::Outgoing(outgoing),
108        }
109    }
110}
111
112impl Encodable for ContractId {
113    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), Error> {
114        self.to_byte_array().consensus_encode(writer)
115    }
116}
117
118impl Decodable for ContractId {
119    fn consensus_decode_partial<D: std::io::Read>(
120        d: &mut D,
121        modules: &ModuleDecoderRegistry,
122    ) -> Result<Self, DecodeError> {
123        Ok(ContractId::from_byte_array(
124            Decodable::consensus_decode_partial(d, modules)?,
125        ))
126    }
127}
128
129#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
130pub struct Preimage(pub [u8; 32]);
131
132impl Display for Preimage {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        write!(f, "{}", self.0.encode_hex::<String>())
135    }
136}
137
138#[cfg(feature = "uniffi")]
139impl FromStr for Preimage {
140    type Err = anyhow::Error;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        let bytes: [u8; 32] = fedimint_core::hex::FromHex::from_hex(s)?;
144        Ok(Self(bytes))
145    }
146}
147
148#[cfg(feature = "uniffi")]
149uniffi::custom_type!(Preimage, String, {
150    lower: |p| p.to_string(),
151    try_lift: |s| Preimage::from_str(&s).map_err(Into::into),
152});
153
154#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
155pub struct PreimageKey(#[serde(with = "serde_big_array::BigArray")] pub [u8; 33]);
156
157impl PreimageKey {
158    /// Create a Schnorr public key
159    ///
160    /// # Errors
161    ///
162    /// Returns [`secp256k1::Error::InvalidPublicKey`] if the Preimage does not
163    /// represent a valid Secp256k1 point x coordinate.
164    pub fn to_public_key(&self) -> Result<secp256k1::PublicKey, secp256k1::Error> {
165        secp256k1::PublicKey::from_slice(&self.0)
166    }
167}
168
169/// Current status of preimage decryption
170#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
171pub enum DecryptedPreimageStatus {
172    /// There aren't enough decryption shares yet
173    Pending,
174    /// The decrypted preimage was valid
175    Some(Preimage),
176    /// The decrypted preimage was invalid
177    Invalid,
178}
179
180/// Possible outcomes of preimage decryption
181#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
182pub enum DecryptedPreimage {
183    /// There aren't enough decryption shares yet
184    Pending,
185    /// The decrypted preimage was valid
186    Some(PreimageKey),
187    /// The decrypted preimage was invalid
188    Invalid,
189}
190
191impl DecryptedPreimage {
192    pub fn is_permanent(&self) -> bool {
193        match self {
194            DecryptedPreimage::Pending => false,
195            DecryptedPreimage::Some(_) | DecryptedPreimage::Invalid => true,
196        }
197    }
198}
199/// Threshold-encrypted [`Preimage`]
200#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable, Deserialize, Serialize)]
201pub struct EncryptedPreimage(pub threshold_crypto::Ciphertext);
202
203/// Share to decrypt an [`EncryptedPreimage`]
204#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Serialize, Deserialize)]
205pub struct PreimageDecryptionShare(pub threshold_crypto::DecryptionShare);
206
207impl EncryptedPreimage {
208    pub fn new(preimage_key: &PreimageKey, key: &threshold_crypto::PublicKey) -> EncryptedPreimage {
209        EncryptedPreimage(key.encrypt(preimage_key.0))
210    }
211}