Skip to content

Commit

Permalink
Merge pull request #9 from f321x/splitting_structures
Browse files Browse the repository at this point in the history
LGTM
  • Loading branch information
f321x authored Sep 3, 2024
2 parents e455afa + 94d2424 commit 7e15f9e
Show file tree
Hide file tree
Showing 16 changed files with 742 additions and 450 deletions.
269 changes: 168 additions & 101 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ members = [
"client",
"coordinator",
"common",
]
]

[profile.release]
lto = true
opt-level = 3
strip = true
6 changes: 4 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
nostr-sdk = { version = "0.32.0", features = ["nip04"] }
nostr-sdk = { version = "0.34.0", features = [] }
cdk = "0.1.1"
dotenv = "0.15.0"
anyhow = "1.0.86"
Expand All @@ -16,4 +16,6 @@ serde = "1.0.203"
serde_json = "1.0.117"
sha2 = "0.10.8"

cashu_escrow_common = { path = "../common" }
cashu_escrow_common = { path = "../common" }
log = "0.4.22"
env_logger = "0.11.3"
89 changes: 89 additions & 0 deletions client/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
pub mod trade_contract;

use super::*;
use cdk::nuts::nut01::PublicKey as EcashPubkey;
use nostr_sdk::Keys as NostrKeys;
use nostr_sdk::PublicKey as NostrPubkey;
use std::str::FromStr;

#[derive(Debug)]
struct RawCliInput {
buyer_npub: String,
seller_npub: String,
partner_ecash_pubkey: String,
coordinator_npub: String,
nostr_nsec: String,
mode: TradeMode,
}

#[derive(Debug)]
pub struct ClientCliInput {
pub mode: TradeMode,
pub trader_nostr_keys: NostrKeys,
pub ecash_pubkey_partner: EcashPubkey,
pub coordinator_nostr_pubkey: NostrPubkey,
pub trade_partner_nostr_pubkey: NostrPubkey,
}

impl RawCliInput {
async fn parse() -> anyhow::Result<Self> {
// information would be communicated OOB in production
let buyer_npub: String = env::var("BUYER_NPUB")?;
let seller_npub: String = env::var("SELLER_NPUB")?;
let coordinator_npub: String = env::var("ESCROW_NPUB")?;

let partner_ecash_pubkey: String;
let nostr_nsec: String;

let mode = match get_user_input("Select mode: (1) buyer, (2) seller: ")
.await?
.as_str()
{
"1" => {
nostr_nsec = env::var("BUYER_NSEC")?;
partner_ecash_pubkey = get_user_input("Enter seller's ecash pubkey: ").await?;
TradeMode::Buyer
}
"2" => {
nostr_nsec = env::var("SELLER_NSEC")?;
partner_ecash_pubkey = get_user_input("Enter buyer's ecash pubkey: ").await?;
TradeMode::Seller
}
_ => {
panic!("Wrong trading mode selected. Select either (1) buyer or (2) seller");
}
};
Ok(Self {
buyer_npub,
seller_npub,
partner_ecash_pubkey,
coordinator_npub,
nostr_nsec,
mode,
})
}
}

impl ClientCliInput {
pub async fn parse() -> anyhow::Result<Self> {
let raw_input = RawCliInput::parse().await?;
debug!("Raw parsed CLI input: {:?}", raw_input);

let ecash_pubkey_partner = EcashPubkey::from_str(&raw_input.partner_ecash_pubkey)?;

let trader_nostr_keys = NostrKeys::from_str(&raw_input.nostr_nsec)?;
let coordinator_nostr_pubkey = NostrPubkey::from_str(&raw_input.coordinator_npub)?;
let trade_partner_nostr_pubkey = match raw_input.mode {
TradeMode::Buyer => NostrPubkey::from_bech32(&raw_input.seller_npub)?,
TradeMode::Seller => NostrPubkey::from_bech32(&raw_input.buyer_npub)?,
};

Ok(Self {
mode: raw_input.mode,
trader_nostr_keys,
ecash_pubkey_partner,
coordinator_nostr_pubkey,
trade_partner_nostr_pubkey,
})
}
}
47 changes: 47 additions & 0 deletions client/src/cli/trade_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use super::*;

pub trait FromClientCliInput {
fn from_client_cli_input(
cli_input: &ClientCliInput,
trade_pubkey: String,
) -> anyhow::Result<TradeContract>;
}

impl FromClientCliInput for TradeContract {
fn from_client_cli_input(
cli_input: &ClientCliInput,
trade_pubkey: String,
) -> anyhow::Result<Self> {
debug!("Constructing hard coded client trade contract...");
let npubkey_seller: PublicKey;
let npubkey_buyer: PublicKey;

match cli_input.mode {
TradeMode::Buyer => {
npubkey_seller = cli_input.trade_partner_nostr_pubkey;
npubkey_buyer = cli_input.trader_nostr_keys.public_key();
}
TradeMode::Seller => {
npubkey_buyer = cli_input.trade_partner_nostr_pubkey;
npubkey_seller = cli_input.trader_nostr_keys.public_key();
}
}

let (ecash_pubkey_seller, ecash_pubkey_buyer) = match cli_input.mode {
TradeMode::Seller => (trade_pubkey, cli_input.ecash_pubkey_partner.to_string()),
TradeMode::Buyer => (cli_input.ecash_pubkey_partner.to_string(), trade_pubkey),
};
// hardcoded trade contract
Ok(TradeContract {
trade_description:
"Purchase of one Watermelon for 5000 satoshi. 3 days delivery to ...".to_string(),
trade_amount_sat: 5000,
npubkey_seller,
npubkey_buyer,
npubkey_coordinator: cli_input.coordinator_nostr_pubkey,
time_limit: 3 * 24 * 60 * 60,
seller_ecash_public_key: ecash_pubkey_seller,
buyer_ecash_public_key: ecash_pubkey_buyer,
})
}
}
63 changes: 37 additions & 26 deletions client/src/ecash/mod.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,58 @@
use super::*;

use cdk::secp256k1::rand::Rng;
use crate::common::model::EscrowRegistration;
use cdk::nuts::PublicKey;
use cdk::{
amount::SplitTarget,
cdk_database::WalletMemoryDatabase,
nuts::{Conditions, CurrencyUnit, PublicKey, SecretKey, SigFlag, SpendingConditions, Token},
nuts::{Conditions, CurrencyUnit, SecretKey, SigFlag, SpendingConditions, Token},
secp256k1::rand::Rng,
wallet::Wallet,
};
use escrow_client::EscrowUser;
use std::str::FromStr;
use std::sync::Arc;

pub struct EcashWallet {
secret: SecretKey,
#[derive(Debug)]
pub struct ClientEcashWallet {
_secret: SecretKey,
pub wallet: Wallet,
pub trade_pubkey: String,
}

impl EcashWallet {
impl ClientEcashWallet {
pub async fn new(mint_url: &str) -> anyhow::Result<Self> {
let localstore = WalletMemoryDatabase::default();
let secret = SecretKey::generate();
let trade_pubkey: String = secret.public_key().to_string();
let _secret = SecretKey::generate();
let trade_pubkey: String = _secret.public_key().to_string();
let seed = rand::thread_rng().gen::<[u8; 32]>();
println!("Trade ecash pubkey: {}", trade_pubkey);
info!("Trade ecash pubkey: {}", trade_pubkey);

let wallet = Wallet::new(mint_url, CurrencyUnit::Sat, Arc::new(localstore), &seed);

Ok(Self {
secret,
_secret,
wallet,
trade_pubkey,
})
}

async fn assemble_escrow_conditions(
fn assemble_escrow_conditions(
&self,
user: &EscrowUser,
contract: &TradeContract,
escrow_registration: &EscrowRegistration,
) -> anyhow::Result<SpendingConditions> {
let buyer_pubkey = PublicKey::from_str(user.contract.buyer_ecash_public_key.as_str())?;
let seller_pubkey = PublicKey::from_str(user.contract.seller_ecash_public_key.as_str())?;
let escrow_pubkey_ts = user.escrow_pk_ts.clone();
let seller_pubkey = PublicKey::from_str(&contract.seller_ecash_public_key)?;
let buyer_pubkey = PublicKey::from_str(&contract.buyer_ecash_public_key)?;
let coordinator_escrow_pubkey = escrow_registration.coordinator_escrow_pubkey;
let start_timestamp = escrow_registration.escrow_start_time;

let locktime = start_timestamp.as_u64() + contract.time_limit;

let spending_conditions = SpendingConditions::new_p2pk(
seller_pubkey,
Some(Conditions::new(
Some(user.escrow_pk_ts.1.as_u64() + user.contract.time_limit),
Some(vec![buyer_pubkey, escrow_pubkey_ts.0]),
Some(locktime),
Some(vec![buyer_pubkey, coordinator_escrow_pubkey]),
Some(vec![buyer_pubkey]),
Some(2),
Some(SigFlag::SigAll),
Expand All @@ -55,27 +61,32 @@ impl EcashWallet {
Ok(spending_conditions)
}

pub async fn create_escrow_token(&self, user: &EscrowUser) -> anyhow::Result<String> {
let spending_conditions = self.assemble_escrow_conditions(user).await?;
pub async fn create_escrow_token(
&self,
contract: &TradeContract,
escrow_registration: &EscrowRegistration,
) -> anyhow::Result<String> {
let spending_conditions = self.assemble_escrow_conditions(contract, escrow_registration)?;
let token = self
.wallet
.send(
user.contract.trade_amount_sat.into(),
Some(user.contract.trade_description.clone()),
contract.trade_amount_sat.into(),
Some(contract.trade_description.clone()),
Some(spending_conditions),
&SplitTarget::None,
)
.await?;
Ok(token)
}

pub async fn validate_escrow_token(
pub fn validate_escrow_token(
&self,
token: &String,
user: &EscrowUser,
escrow_token: &str,
contract: &TradeContract,
escrow_registration: &EscrowRegistration,
) -> anyhow::Result<Token> {
let spending_conditions = self.assemble_escrow_conditions(user).await?;
let token = Token::from_str(&token)?;
let spending_conditions = self.assemble_escrow_conditions(contract, escrow_registration)?;
let token = Token::from_str(escrow_token)?;
self.wallet.verify_token_p2pk(&token, spending_conditions)?;
Ok(token)
}
Expand Down
Loading

0 comments on commit 7e15f9e

Please sign in to comment.