Skip to main content

rgbinvoice/
invoice.rs

1// RGB wallet library for smart contracts on Bitcoin & Lightning network
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::ops::Deref;
23use std::str::FromStr;
24
25use indexmap::IndexMap;
26use rgb::bitcoin::hashes::{hash160, sha256};
27use rgb::bitcoin::key::{TweakedPublicKey, UntweakedPublicKey};
28use rgb::bitcoin::{KnownHrp, Network, PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash};
29use rgb::{ChainNet, ContractId, Layer1, SchemaId, SecretSeal, StateType};
30use strict_types::FieldName;
31
32use crate::parse::AddressPayload;
33use crate::{Amount, NonFungible};
34
35#[derive(Clone, Eq, PartialEq, Hash, Debug)]
36#[non_exhaustive]
37pub enum RgbTransport {
38    JsonRpc { tls: bool, host: String },
39    RestHttp { tls: bool, host: String },
40    WebSockets { tls: bool, host: String },
41    Storm {/* todo */},
42    UnspecifiedMeans,
43}
44
45#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
46#[display(inner)]
47pub enum InvoiceStateError {
48    #[display(doc_comments)]
49    /// could not parse as amount, data, or attach: {0}.
50    ParseError(String),
51}
52
53#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
54pub enum InvoiceState {
55    #[display("")]
56    Void,
57    #[display("{0}")]
58    Amount(Amount),
59    #[display(inner)]
60    Data(NonFungible),
61}
62
63impl FromStr for InvoiceState {
64    type Err = InvoiceStateError;
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        if s.is_empty() {
67            Ok(InvoiceState::Void)
68        } else if let Ok(amount) = Amount::from_str(s) {
69            Ok(InvoiceState::Amount(amount))
70        } else if let Ok(data) = NonFungible::from_str(s) {
71            Ok(InvoiceState::Data(data))
72        } else {
73            Err(InvoiceStateError::ParseError(s.to_owned()))
74        }
75    }
76}
77
78impl From<InvoiceState> for StateType {
79    fn from(val: InvoiceState) -> Self {
80        match val {
81            InvoiceState::Void => StateType::Void,
82            InvoiceState::Amount(_) => StateType::Fungible,
83            InvoiceState::Data(_) => StateType::Structured,
84        }
85    }
86}
87
88#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
89#[non_exhaustive]
90pub enum XChainNet<T> {
91    BitcoinMainnet(T),
92    BitcoinTestnet3(T),
93    BitcoinTestnet4(T),
94    BitcoinSignet(T),
95    BitcoinSignetCustom(T),
96    BitcoinRegtest(T),
97    LiquidMainnet(T),
98    LiquidTestnet(T),
99}
100
101impl<T> XChainNet<T> {
102    pub fn with(cn: ChainNet, data: T) -> Self {
103        match cn {
104            ChainNet::BitcoinMainnet => XChainNet::BitcoinMainnet(data),
105            ChainNet::BitcoinTestnet3 => XChainNet::BitcoinTestnet3(data),
106            ChainNet::BitcoinTestnet4 => XChainNet::BitcoinTestnet4(data),
107            ChainNet::BitcoinSignet => XChainNet::BitcoinSignet(data),
108            ChainNet::BitcoinSignetCustom => XChainNet::BitcoinSignetCustom(data),
109            ChainNet::BitcoinRegtest => XChainNet::BitcoinRegtest(data),
110            ChainNet::LiquidMainnet => XChainNet::LiquidMainnet(data),
111            ChainNet::LiquidTestnet => XChainNet::LiquidTestnet(data),
112        }
113    }
114
115    pub fn bitcoin(network: Network, data: T) -> Self {
116        match network {
117            Network::Bitcoin => Self::BitcoinMainnet(data),
118            Network::Testnet => Self::BitcoinTestnet3(data),
119            Network::Testnet4 => Self::BitcoinTestnet4(data),
120            Network::Signet => Self::BitcoinSignet(data),
121            Network::Regtest => Self::BitcoinRegtest(data),
122        }
123    }
124
125    pub fn chain_network(&self) -> ChainNet {
126        match self {
127            XChainNet::BitcoinMainnet(_) => ChainNet::BitcoinMainnet,
128            XChainNet::BitcoinTestnet3(_) => ChainNet::BitcoinTestnet3,
129            XChainNet::BitcoinTestnet4(_) => ChainNet::BitcoinTestnet4,
130            XChainNet::BitcoinSignet(_) => ChainNet::BitcoinSignet,
131            XChainNet::BitcoinSignetCustom(_) => ChainNet::BitcoinSignetCustom,
132            XChainNet::BitcoinRegtest(_) => ChainNet::BitcoinRegtest,
133            XChainNet::LiquidMainnet(_) => ChainNet::LiquidMainnet,
134            XChainNet::LiquidTestnet(_) => ChainNet::LiquidTestnet,
135        }
136    }
137
138    pub fn into_inner(self) -> T {
139        match self {
140            XChainNet::BitcoinMainnet(inner)
141            | XChainNet::BitcoinTestnet3(inner)
142            | XChainNet::BitcoinTestnet4(inner)
143            | XChainNet::BitcoinSignet(inner)
144            | XChainNet::BitcoinSignetCustom(inner)
145            | XChainNet::BitcoinRegtest(inner)
146            | XChainNet::LiquidMainnet(inner)
147            | XChainNet::LiquidTestnet(inner) => inner,
148        }
149    }
150
151    pub fn layer1(&self) -> Layer1 { self.chain_network().layer1() }
152
153    pub fn address_network(&self) -> KnownHrp {
154        match self.chain_network() {
155            ChainNet::BitcoinMainnet => KnownHrp::Mainnet,
156            ChainNet::BitcoinTestnet3
157            | ChainNet::BitcoinTestnet4
158            | ChainNet::BitcoinSignet
159            | ChainNet::BitcoinSignetCustom => KnownHrp::Testnets,
160            ChainNet::BitcoinRegtest => KnownHrp::Regtest,
161            ChainNet::LiquidMainnet => KnownHrp::Mainnet,
162            ChainNet::LiquidTestnet => KnownHrp::Testnets,
163        }
164    }
165}
166
167#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
168#[display(doc_comments)]
169pub enum Pay2VoutError {
170    /// unexpected address type byte {0:#04x}.
171    InvalidAddressType(u8),
172    /// invalid taproot output key
173    InvalidTapkey,
174}
175
176#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
177pub struct Pay2Vout(AddressPayload);
178
179impl Pay2Vout {
180    pub fn new(address_payload: AddressPayload) -> Self { Pay2Vout(address_payload) }
181}
182
183impl Deref for Pay2Vout {
184    type Target = AddressPayload;
185
186    fn deref(&self) -> &'_ Self::Target { &self.0 }
187}
188
189impl Pay2Vout {
190    pub(crate) const P2PKH: u8 = 1;
191    pub(crate) const P2SH: u8 = 2;
192    pub(crate) const P2WPKH: u8 = 3;
193    pub(crate) const P2WSH: u8 = 4;
194    pub(crate) const P2TR: u8 = 5;
195}
196
197impl TryFrom<[u8; 33]> for Pay2Vout {
198    type Error = Pay2VoutError;
199
200    fn try_from(data: [u8; 33]) -> Result<Self, Self::Error> {
201        let addr_bytes: [u8; 20] = data[1..21].try_into().unwrap();
202        let address = match data[0] {
203            Self::P2PKH => AddressPayload::Pkh(PubkeyHash::from_raw_hash(
204                *hash160::Hash::from_bytes_ref(&addr_bytes),
205            )),
206            Self::P2SH => AddressPayload::Sh(ScriptHash::from_raw_hash(
207                *hash160::Hash::from_bytes_ref(&addr_bytes),
208            )),
209            Self::P2WPKH => AddressPayload::Wpkh(WPubkeyHash::from_raw_hash(
210                *hash160::Hash::from_bytes_ref(&addr_bytes),
211            )),
212            Self::P2WSH => AddressPayload::Wsh(WScriptHash::from_raw_hash(
213                *sha256::Hash::from_bytes_ref(&data[1..].try_into().unwrap()),
214            )),
215            Self::P2TR => AddressPayload::Tr(TweakedPublicKey::dangerous_assume_tweaked(
216                UntweakedPublicKey::from_slice(&data[1..33])
217                    .map_err(|_| Pay2VoutError::InvalidTapkey)?,
218            )),
219            wrong => return Err(Pay2VoutError::InvalidAddressType(wrong)),
220        };
221        Ok(Pay2Vout(address))
222    }
223}
224
225#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
226pub enum Beneficiary {
227    #[from]
228    BlindedSeal(SecretSeal),
229    WitnessVout(Pay2Vout, Option<UntweakedPublicKey>),
230}
231
232#[derive(Clone, Eq, PartialEq, Debug)]
233#[non_exhaustive]
234pub struct RgbInvoice {
235    pub transports: Vec<RgbTransport>,
236    pub contract: Option<ContractId>,
237    pub schema: Option<SchemaId>,
238    pub assignment_name: Option<FieldName>,
239    pub assignment_state: Option<InvoiceState>,
240    pub beneficiary: XChainNet<Beneficiary>,
241    /// UTC unix timestamp
242    pub expiry: Option<i64>,
243    pub unknown_query: IndexMap<String, String>,
244}
245
246impl RgbInvoice {
247    pub fn chain_network(&self) -> ChainNet { self.beneficiary.chain_network() }
248    pub fn address_network(&self) -> KnownHrp { self.beneficiary.address_network() }
249    pub fn layer1(&self) -> Layer1 { self.beneficiary.layer1() }
250}