Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split and cleanup client #9

Merged
merged 44 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
42c3806
splitting up client
f321x Jul 16, 2024
2a3b524
merge
f321x Jul 21, 2024
b6fc274
separate client logic, cleanup
f321x Jul 21, 2024
3b9d16c
remaining changes
f321x Jul 21, 2024
71efb91
add comments
f321x Jul 21, 2024
29aabb1
put trade mode in metadata
f321x Jul 21, 2024
e181c89
minor improvements
f321x Jul 21, 2024
9d6ad91
Create ecash wallet at the parse input phase, the trade parner needs …
rodant Jul 25, 2024
c9bb1cf
Fix npubs by usong to_bech32.
rodant Jul 26, 2024
b7288cf
rewrite in a more idiomatic form.
rodant Jul 31, 2024
64923f7
Move EscrowClient struct to its module.
rodant Aug 2, 2024
236346c
Inline init_ttrade in main.
rodant Aug 2, 2024
6b10695
Extract wallet creation from RawCliInput
rodant Aug 5, 2024
7e28154
Start introducing the escrow client as state machine for better testa…
rodant Aug 6, 2024
0cf6574
Remove dependency from cli in escrow client.
rodant Aug 6, 2024
7cef1c7
Some clean up.
rodant Aug 6, 2024
d32dc48
Move trade mode to the escrow client.
rodant Aug 6, 2024
3ffe54f
Split escrow metadata in the contract and escrow registration parts.
rodant Aug 9, 2024
7e1b21a
Use PublicKey type in trade contract.
rodant Aug 10, 2024
71af4c2
Replace tuple though the registration message struct.
rodant Aug 13, 2024
af5121c
Remove utils modules.
rodant Aug 13, 2024
2a88dbc
Remove PubkeyMessage and rearrange models.
rodant Aug 14, 2024
7ee5b40
Fix remaining warnings.
rodant Aug 14, 2024
a27ea9a
bump up nostr_sdk version and revert to nip04 direct messages, nip17 …
rodant Aug 15, 2024
85ce4d0
working version with nip17 private direct messages.
rodant Aug 16, 2024
a9714b7
Make func sync after review comment.
rodant Aug 18, 2024
13082e7
Introduce timeout and improvements in receive_escrow_message
rodant Aug 20, 2024
542a2be
Better having a get public key method than a get npub.
rodant Aug 20, 2024
f001e9c
Remove dependency to cli in ClientNostrClient.
rodant Aug 20, 2024
5014664
Hide nostr client in ClientNostrInstance.
rodant Aug 20, 2024
9ae1ba4
Improve the coordinator loop for running forever.
rodant Aug 21, 2024
0d7a2a2
Improvement for error situations.
rodant Aug 21, 2024
645158c
Bumb up nostr_sdk version.
rodant Aug 21, 2024
5e20036
Handle some error cases.
rodant Aug 22, 2024
6a0e061
Avoid unneeded async func.
rodant Aug 22, 2024
5aaa656
Remove nostr instance and clean up escrow client.
rodant Aug 22, 2024
3bcfde4
Fix race condition in receiving registration and introduce escrow cli…
rodant Aug 26, 2024
d038b12
remove unused imports.
rodant Aug 26, 2024
8d79e1c
Deposit enough funds and proceed to a delivery.
rodant Aug 30, 2024
09396a7
Fix typos.
rodant Sep 1, 2024
f4b75cb
Update coordinator/src/escrow_coordinator/mod.rs
rodant Sep 1, 2024
e2e4382
Initialize the notifications receiver at creation and pull events on …
rodant Sep 2, 2024
e1d295e
Define nostr client as field of the escrow client and pass all its fi…
rodant Sep 2, 2024
94d2424
Handle receice errors from a subscription.
rodant Sep 3, 2024
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
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