#protocols #pool #bitcoin

app joinstr

Rust implementation of the joinstr protocol

1 unstable release

Uses old Rust 2015

0.0.0 Dec 26, 2024

#366 in #pool

Download history 106/week @ 2024-12-23 19/week @ 2024-12-30

125 downloads per month

MIT license

2KB

Disclaimer

This library is at (very) experimental stage, we do not advice to use it on mainnet. There is still some minor differences at the protocol level between this implementation and the python & kotlin implementations, but we should fix it soon.

Sponsorship

R&D Sponsored By Bull Bitcoin

Joinstr protocol

Transaction Inputs/Outputs types

As of now, we have only implemented the protocol for using Segwitv0 inputs & outputs.

VPN/Tor

For now there is no plan to implement VPN or Tor support in this lib, as it's expected to be handled at consumer or OS level.

Build

Install Rust toolchain (see here)

and run this from this repo:

cargo run --release

Run the tests

cargo tests

Usage

Run a standalone coordinator

    let mut coordinator = Joinstr::new_initiator(
        Keys::generate(),
        &vec!["wss://relay.nostr".into()],
        ("127.0.0.1", 2121),
        Network::Regtest,
        "initiator",
    )
    .await
    .unwrap()
    .denomination(0.01)
    .unwrap()
    .fee(10)
    .unwrap()
    .simple_timeout(now() + 3 * 60 * 60)
    .unwrap()
    .min_peers(5)
    .unwrap();
    coordinator
        .start_coinjoin(None, Option::<&WpkhHotSigner>::None)
        .await
        .unwrap();

Initiate a pool

    // create an electrum client
    let client = Client::new_local("127.0.0.1", 2121).unwrap();

    // create the signer
    let mnemonic =
        "define jealous drill wrap item shallow chest balcony domain dignity runway year";
    let mut signer = WpkhHotSigner::new_from_mnemonics(Network::Regtest, mnemonic).unwrap();
    signer.set_client(client);

    // fetch the coin you want to add to the pool
    let coins = signer
        .get_coins_at(CoinPath {
            depth: 0,
            index: Some(0),
        })
        .unwrap();
    assert_eq!(coins, 1);
    let coin = signer.list_coins().into_iter().next().unwrap();

    // generate the output address
    let address = signer
        .address_at(&CoinPath {
            depth: 0,
            index: Some(100),
        })
        .unwrap()
        .as_unchecked()
        .clone();

    // create a peer that will also be a coordinator
    let mut peer = Joinstr::new_peer(
        &vec!["wss://relay.nostr".into()],
        &pool,
        coin.1,
        address,
        Network::Regtest,
        "peer_a",
    )
    .await
    .unwrap()
    .denomination(0.01)
    .unwrap()
    .fee(10)
    .unwrap()
    .simple_timeout(now() + 3 * 60 * 60)
    .unwrap()
    .min_peers(5)
    .unwrap();

    // try to run the coinjoin
    peer.start_coinjoin(None, Some(&signer))
        .await
        .unwrap();

Join a pool as peer

    // create a nostr client and listen for pool notification
    let mut pool_listener = NostrClient::new("pool_listener")
        .relays(&vec!["wss://relay.nostr".into()])
        .unwrap()
        .keys(Keys::generate())
        .unwrap();
    pool_listener.connect_nostr().await.unwrap();
    // subscribe to pool notifications that have been initiated 2 hours back in time
    pool_listener.subscribe_pools(2 * 60 * 60).await.unwrap();

    // wait to receive notifications
    sleep(Duration::from_millis(3000)).await;

    // list received notifications
    let mut pools = Vec::new();

    while let Some(pool) = pool_listener.receive_pool_notification().unwrap() {
        pools.push(pool)
    }

    // select the pool you want to join (like by pool denomination and network config)
    let pool = pools.into_iter().next().unwrap();

    // create an electrum client
    let client = Client::new_local("127.0.0.1", 2121).unwrap();

    // create the signer
    let mnemonic =
        "define jealous drill wrap item shallow chest balcony domain dignity runway year";
    let mut signer = WpkhHotSigner::new_from_mnemonics(Network::Regtest, mnemonic).unwrap();
    signer.set_client(client);

    // fetch the coin you want to add to the pool
    let coins = signer
        .get_coins_at(CoinPath {
            depth: 0,
            index: Some(0),
        })
        .unwrap();
    assert_eq!(coins, 1);
    let coin = signer.list_coins().into_iter().next().unwrap();

    // generate the output address
    let address = signer
        .address_at(&CoinPath {
            depth: 0,
            index: Some(100),
        })
        .unwrap()
        .as_unchecked()
        .clone();

    // create a peer that will also be a coordinator
    let mut peer = Joinstr::new_peer(
        &vec!["wss://relay.nostr".into()],
        &pool,
        coin.1,
        address,
        Network::Regtest,
        "peer_a",
    )
    .await
    .unwrap();

    // try to run the coinjoin
    peer.start_coinjoin(Some(pool), Some(&signer))
        .await
        .unwrap();

No runtime deps