Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/cashu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ strum_macros.workspace = true
nostr-sdk = { workspace = true, optional = true }
zeroize = "1"
web-time.workspace = true
corepc-types = "0.11.0"
jsonrpc = "0.19.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
uuid = { workspace = true, features = ["js"], optional = true }
Expand Down
7 changes: 7 additions & 0 deletions crates/cashu/src/nuts/nut00/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::nuts::nut01::SecretKey;
use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness};
use crate::nuts::nut12::BlindSignatureDleq;
use crate::nuts::nut14::{serde_htlc_witness, HTLCWitness};
use crate::nuts::nutxx::{serde_btcfee_witness, Witness as BTCFEEWitness};
use crate::nuts::{Id, ProofDleq};
use crate::secret::Secret;
use crate::Amount;
Expand Down Expand Up @@ -287,6 +288,9 @@ pub enum Witness {
/// P2PK Witness
#[serde(with = "serde_p2pk_witness")]
P2PKWitness(P2PKWitness),
/// BTCFEE Witness
#[serde(with = "serde_btcfee_witness")]
BTCFEEWitness(BTCFEEWitness),
}

impl From<P2PKWitness> for Witness {
Expand All @@ -310,6 +314,7 @@ impl Witness {
Some(sigs) => sigs.extend(signatures),
None => htlc_witness.signatures = Some(signatures),
},
Self::BTCFEEWitness(_btcfee_witness) => {}
}
}

Expand All @@ -318,6 +323,7 @@ impl Witness {
match self {
Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
Self::HTLCWitness(witness) => witness.signatures.clone(),
Self::BTCFEEWitness(_witness) => None,
}
}

Expand All @@ -326,6 +332,7 @@ impl Witness {
match self {
Self::P2PKWitness(_witness) => None,
Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
Self::BTCFEEWitness(witness) => Some(witness.preimage.clone()),
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions crates/cashu/src/nuts/nutxx/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use thiserror::Error;

/// BTCFEE Errors
#[derive(Debug, Error)]
pub enum Error {
// Witness Errors
/// Invalid BTCFEE Witness
#[error("Invalid BTCFEE witness data")]
InvalidWitness,
/// Invalid BTCFEE Witness Blockhash
#[error("Invalid BTCFEE witness blockhash data")]
InvalidWitnessBlockhash,
/// Invalid BTCFEE Witness Preimage
#[error("Invalid BTCFEE witness preimage data")]
InvalidWitnessPreimage,
}
17 changes: 17 additions & 0 deletions crates/cashu/src/nuts/nutxx/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! NUT-XX: Bitcoin External Fee
//!
//! <https://github.com/TheMhv/nuts/blob/feat/external-fee/xx.md>

/// BTCFEE Error module
pub mod error;
pub use error::Error;

/// BTCFEE Witness module
pub mod witness;
pub use witness::Witness;

/// BTCFEE Spending Conditions module
pub mod spending_conditions;
pub use spending_conditions::SpendingConditions;

pub mod serde_btcfee_witness;
21 changes: 21 additions & 0 deletions crates/cashu/src/nuts/nutxx/serde_btcfee_witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Serde helpers for BTCFEE Witness

use crate::nutxx::Witness;
use serde::{de, ser, Deserialize, Deserializer, Serializer};

/// Serialize [BTCFEEWitness] as stringified JSON
pub fn serialize<S>(x: &Witness, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&serde_json::to_string(&x).map_err(ser::Error::custom)?)
}

/// Deserialize [BTCFEEWitness] from stringified JSON
pub fn deserialize<'de, D>(deserializer: D) -> Result<Witness, D::Error>
where
D: Deserializer<'de>,
{
let s: String = String::deserialize(deserializer)?;
serde_json::from_str(&s).map_err(de::Error::custom)
}
26 changes: 26 additions & 0 deletions crates/cashu/src/nuts/nutxx/spending_conditions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};

use crate::Conditions;

/// BTCFEE SpendingConditions
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SpendingConditions {
/// Other spending conditions
#[serde(skip_serializing_if = "Option::is_none")]
pub conditions: Option<Conditions>,
/// Number of block confirmations required
///
/// Default is 1
#[serde(skip_serializing_if = "Option::is_none")]
pub confirmations: Option<u64>,
}

impl SpendingConditions {
/// Create a BTCFEE spending conditions
pub fn new(conditions: Option<Conditions>, confirmations: Option<u64>) -> Self {
Self {
conditions,
confirmations,
}
}
}
32 changes: 32 additions & 0 deletions crates/cashu/src/nuts/nutxx/witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use bitcoin::{consensus::encode::deserialize_hex, BlockHash};
use serde::{Deserialize, Serialize};

use crate::{nutxx::Error, util::hex};

/// BTCFEE Witness
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct Witness {
/// Blockhash
pub blockhash: String,
/// Preimage
pub preimage: String,
}

impl Witness {
/// Decode the blockhash from hex
///
/// Returns the blockhash
pub fn blockhash_data(&self) -> Result<BlockHash, Error> {
let blockhash: BlockHash =
deserialize_hex(&self.blockhash).map_err(|_| Error::InvalidWitnessBlockhash)?;
Ok(blockhash)
}

/// Decode the preimage from hex
///
/// Returns the preimage bytes data
pub fn preimage_data(&self) -> Result<Vec<u8>, Error> {
hex::decode(&self.preimage).map_err(|_| Error::InvalidWitnessPreimage)
}
}
85 changes: 85 additions & 0 deletions crates/cashu/src/util/bitcoin_rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use bitcoin::{Block, BlockHash, Transaction, Txid};
use corepc_types::model::{GetBlockVerboseZero, GetRawTransaction};
use jsonrpc::{simple_http::SimpleHttpTransport, Client};
use serde_json::json;
use strum_macros::Display;
use thiserror::Error;

/// Bitcoin RPC Client Errors
#[derive(Debug, Error, Display)]
pub enum Error {
/// Cannot connect with Bitcoin JSON RPC client
CannotConnect,
/// Invalid URL from JSON RPC
InvalidURL,
/// Unable to parse params
InvalidParams,
/// Unable to send request
RequestFailed,
/// Unable to get response
ResponseFailed,
}

/// BitcoinRPC
#[derive(Debug)]
pub struct BitcoinRPC {
client: Client,
}

impl BitcoinRPC {
/// Create a BitcoinRPC with Client
pub fn new(url: &str, user: Option<String>, pass: Option<String>) -> Result<Self, Error> {
let transport = SimpleHttpTransport::builder()
.url(url)
.map_err(|_| Error::InvalidURL)?
.auth(user.unwrap_or("".into()), pass)
.build();

Ok(Self {
client: Client::with_transport(transport),
})
}

/// Send a JSON RPC call
fn call_rpc<T>(
&self,
method: &str,
params: Option<&serde_json::value::RawValue>,
) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
let request = self.client.build_request(method, params);
let response = self
.client
.send_request(request)
.map_err(|_| Error::ResponseFailed)?;

Ok(response.result().map_err(|_| Error::ResponseFailed)?)
}

/// Send `getblock` method to JSON RPC
///
/// Return the Block
pub fn get_block(&self, blockhash: BlockHash) -> Result<Block, Error> {
let params = serde_json::value::to_raw_value(&json!([blockhash.to_string(), 0]))
.map_err(|_| Error::InvalidParams)?;

let response: GetBlockVerboseZero = self.call_rpc("getblock", Some(&params))?;

Ok(response.0)
}

/// Send `getrawtransaction` method to JSON RPC
///
/// Return the Transaction
pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
let params = serde_json::value::to_raw_value(&json!([txid.to_string(), false]))
.map_err(|_| Error::InvalidParams)?
.clone();

let response: GetRawTransaction = self.call_rpc("getrawtransaction", Some(&params))?;

Ok(response.0)
}
}
1 change: 1 addition & 0 deletions crates/cashu/src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Cashu utils

pub mod bitcoin_rpc;
pub mod hex;
pub mod serde_helpers;

Expand Down