mirror of
https://github.com/aljazceru/cdk.git
synced 2026-01-06 14:35:31 +01:00
receive
This commit is contained in:
35
README.md
35
README.md
@@ -1,28 +1,27 @@
|
||||
|
||||
|
||||
⚠️ **Don't be reckless:** This project is in early development, it does however work with real sats! Always use amounts you don't mind loosing.
|
||||
> **Warning**
|
||||
> This project is in early development, it does however work with real sats! Always use amounts you don't mind loosing.
|
||||
|
||||
Cashu RS is a rust library for [Cashu](https://github.com/cashubtc) wallets written in Rust.
|
||||
|
||||
Implemented [NUTs](https://github.com/cashubtc/nuts/):
|
||||
## Implemented [NUTs](https://github.com/cashubtc/nuts/):
|
||||
|
||||
- [x] [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md)
|
||||
- [x] [NUT-01](https://github.com/cashubtc/nuts/blob/main/01.md)
|
||||
- [x] [NUT-02](https://github.com/cashubtc/nuts/blob/main/02.md)
|
||||
- [x] [NUT-03](https://github.com/cashubtc/nuts/blob/main/03.md)
|
||||
- [x] [NUT-04](https://github.com/cashubtc/nuts/blob/main/04.md)
|
||||
- [x] [NUT-05](https://github.com/cashubtc/nuts/blob/main/05.md)
|
||||
- [x] [NUT-06](https://github.com/cashubtc/nuts/blob/main/06.md)
|
||||
- [x] [NUT-07](https://github.com/cashubtc/nuts/blob/main/07.md)
|
||||
- [x] [NUT-08](https://github.com/cashubtc/nuts/blob/main/08.md)
|
||||
- [x] [NUT-09](https://github.com/cashubtc/nuts/blob/main/09.md)
|
||||
- ✅ [NUT-00](https://github.com/cashubtc/nuts/blob/main/00.md)
|
||||
- ✅ [NUT-01](https://github.com/cashubtc/nuts/blob/main/01.md)
|
||||
- ✅ [NUT-02](https://github.com/cashubtc/nuts/blob/main/02.md)
|
||||
- ✅ [NUT-03](https://github.com/cashubtc/nuts/blob/main/03.md)
|
||||
- ✅ [NUT-04](https://github.com/cashubtc/nuts/blob/main/04.md)
|
||||
- ✅ [NUT-05](https://github.com/cashubtc/nuts/blob/main/05.md)
|
||||
- ✅ [NUT-06](https://github.com/cashubtc/nuts/blob/main/06.md)
|
||||
- ✅ [NUT-07](https://github.com/cashubtc/nuts/blob/main/07.md)
|
||||
- ✅ [NUT-08](https://github.com/cashubtc/nuts/blob/main/08.md)
|
||||
- ✅ [NUT-09](https://github.com/cashubtc/nuts/blob/main/09.md)
|
||||
|
||||
|
||||
Supported token formats:
|
||||
## Supported token formats:
|
||||
|
||||
- [ ] v1 read
|
||||
- [ ] v2 read (deprecated)
|
||||
- [ ] v3 read/write
|
||||
- ❌ v1 read (deprecated)
|
||||
- ❌ v2 read (deprecated)
|
||||
- ✅ [v3](https://github.com/cashubtc/nuts/blob/main/00.md#023---v3-tokens) read/write
|
||||
|
||||
|
||||
## License
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use bitcoin::Amount;
|
||||
use lightning_invoice::Invoice;
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
@@ -13,7 +14,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct CashuMint {
|
||||
url: Url,
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
impl CashuMint {
|
||||
@@ -98,24 +99,18 @@ impl CashuMint {
|
||||
}
|
||||
|
||||
/// Split Token [NUT-06]
|
||||
pub async fn split(
|
||||
&self,
|
||||
amount: Amount,
|
||||
proofs: Vec<Proof>,
|
||||
outputs: Vec<BlindedMessage>,
|
||||
) -> Result<SplitResponse, Error> {
|
||||
pub async fn split(&self, split_request: SplitRequest) -> Result<SplitResponse, Error> {
|
||||
let url = self.url.join("split")?;
|
||||
|
||||
let request = SplitRequest {
|
||||
amount,
|
||||
proofs,
|
||||
outputs,
|
||||
};
|
||||
|
||||
Ok(minreq::post(url)
|
||||
.with_json(&request)?
|
||||
let res = minreq::post(url)
|
||||
.with_json(&split_request)?
|
||||
.send()?
|
||||
.json::<SplitResponse>()?)
|
||||
.json::<Value>()?;
|
||||
|
||||
// TODO: need to handle response error
|
||||
// specfically token already spent
|
||||
|
||||
Ok(serde_json::from_value(res).unwrap())
|
||||
}
|
||||
|
||||
/// Spendable check [NUT-07]
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::Amount;
|
||||
|
||||
use crate::{
|
||||
cashu_mint::CashuMint,
|
||||
dhke::construct_proof,
|
||||
error::Error,
|
||||
types::{MintKeys, Proof, ProofsStatus, RequestMintResponse},
|
||||
types::{
|
||||
BlindedMessages, MintKeys, Proof, ProofsStatus, RequestMintResponse, SplitPayload,
|
||||
SplitRequest, TokenData,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct CashuWallet {
|
||||
@@ -20,6 +26,7 @@ impl CashuWallet {
|
||||
pub async fn check_proofs_spent(&self, proofs: Vec<Proof>) -> Result<ProofsStatus, Error> {
|
||||
let spendable = self.mint.check_spendable(&proofs).await?;
|
||||
|
||||
// Seperate proofs in spent and unspent based on mint response
|
||||
let (spendable, spent): (Vec<_>, Vec<_>) = proofs
|
||||
.iter()
|
||||
.zip(spendable.spendable.iter())
|
||||
@@ -40,4 +47,85 @@ impl CashuWallet {
|
||||
pub async fn check_fee(&self, invoice: lightning_invoice::Invoice) -> Result<Amount, Error> {
|
||||
Ok(self.mint.check_fees(invoice).await?.fee)
|
||||
}
|
||||
|
||||
/// Receive
|
||||
pub async fn receive(&self, encoded_token: &str) -> Result<Vec<Proof>, Error> {
|
||||
let token_data = TokenData::from_str(encoded_token)?;
|
||||
|
||||
let mut proofs = vec![];
|
||||
for token in token_data.token {
|
||||
if token.proofs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let keys = if token.mint.eq(&self.mint.url) {
|
||||
self.keys.clone()
|
||||
} else {
|
||||
// TODO:
|
||||
println!("No match");
|
||||
self.keys.clone()
|
||||
// CashuMint::new(token.mint).get_keys().await.unwrap()
|
||||
};
|
||||
|
||||
// Sum amount of all proofs
|
||||
let amount = token
|
||||
.proofs
|
||||
.iter()
|
||||
.fold(Amount::ZERO, |acc, p| acc + p.amount);
|
||||
|
||||
let split_payload = self
|
||||
.create_split(Amount::ZERO, amount, token.proofs)
|
||||
.await?;
|
||||
|
||||
let split_response = self.mint.split(split_payload.split_payload).await?;
|
||||
|
||||
// Proof to keep
|
||||
let keep_proofs = construct_proof(
|
||||
split_response.fst,
|
||||
split_payload.keep_blinded_messages.rs,
|
||||
split_payload.keep_blinded_messages.secrets,
|
||||
&keys,
|
||||
)?;
|
||||
|
||||
// Proofs to send
|
||||
let send_proofs = construct_proof(
|
||||
split_response.snd,
|
||||
split_payload.send_blinded_messages.rs,
|
||||
split_payload.send_blinded_messages.secrets,
|
||||
&keys,
|
||||
)?;
|
||||
|
||||
proofs.push(keep_proofs);
|
||||
proofs.push(send_proofs);
|
||||
}
|
||||
|
||||
Ok(proofs.iter().flatten().cloned().collect())
|
||||
}
|
||||
|
||||
pub async fn create_split(
|
||||
&self,
|
||||
keep_amount: Amount,
|
||||
send_amount: Amount,
|
||||
proofs: Vec<Proof>,
|
||||
) -> Result<SplitPayload, Error> {
|
||||
let keep_blinded_messages = BlindedMessages::random(keep_amount)?;
|
||||
let send_blinded_messages = BlindedMessages::random(send_amount)?;
|
||||
|
||||
let outputs = {
|
||||
let mut outputs = keep_blinded_messages.blinded_messages.clone();
|
||||
outputs.extend(send_blinded_messages.blinded_messages.clone());
|
||||
outputs
|
||||
};
|
||||
let split_payload = SplitRequest {
|
||||
amount: send_amount,
|
||||
proofs,
|
||||
outputs,
|
||||
};
|
||||
|
||||
Ok(SplitPayload {
|
||||
keep_blinded_messages,
|
||||
send_blinded_messages,
|
||||
split_payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
33
src/dhke.rs
33
src/dhke.rs
@@ -1,14 +1,16 @@
|
||||
//! Diffie-Hellmann key exchange
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin_hashes::sha256;
|
||||
use bitcoin_hashes::Hash;
|
||||
use secp256k1::rand::rngs::OsRng;
|
||||
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
|
||||
|
||||
use crate::error::Error;
|
||||
// use crate::types::MintKeys;
|
||||
// use crate::types::Promise;
|
||||
// use crate::types::Proof;
|
||||
use crate::types::MintKeys;
|
||||
use crate::types::Promise;
|
||||
use crate::types::Proof;
|
||||
|
||||
/// Hash to Curve
|
||||
pub fn hash_to_curve(secret_message: &[u8]) -> Result<PublicKey, Error> {
|
||||
@@ -62,17 +64,32 @@ pub fn unblind_message(
|
||||
Ok(unblinded_key)
|
||||
}
|
||||
|
||||
/*
|
||||
/// Construct Proof
|
||||
pub fn construct_proof(
|
||||
promises: Vec<Promise>,
|
||||
rs: Vec<SecretKey>,
|
||||
secrets: Vec<String>,
|
||||
keys: MintKeys,
|
||||
secrets: Vec<Vec<u8>>,
|
||||
keys: &MintKeys,
|
||||
) -> Result<Vec<Proof>, Error> {
|
||||
todo!()
|
||||
let mut proofs = vec![];
|
||||
for (i, promise) in promises.into_iter().enumerate() {
|
||||
let blinded_c = PublicKey::from_str(&promise.c)?;
|
||||
let a: PublicKey = PublicKey::from_str(keys.0.get(&promise.amount.to_sat()).unwrap())?;
|
||||
let unblinded_signature = unblind_message(blinded_c, rs[i], a)?;
|
||||
|
||||
let proof = Proof {
|
||||
id: Some(promise.id),
|
||||
amount: promise.amount,
|
||||
secret: hex::encode(&secrets[i]),
|
||||
c: unblinded_signature.to_string(),
|
||||
script: None,
|
||||
};
|
||||
|
||||
proofs.push(proof);
|
||||
}
|
||||
|
||||
Ok(proofs)
|
||||
}
|
||||
*/
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
11
src/types.rs
11
src/types.rs
@@ -79,6 +79,13 @@ impl BlindedMessages {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SplitPayload {
|
||||
pub keep_blinded_messages: BlindedMessages,
|
||||
pub send_blinded_messages: BlindedMessages,
|
||||
pub split_payload: SplitRequest,
|
||||
}
|
||||
|
||||
/// Promise (BlindedSignature) [NIP-00]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Promise {
|
||||
@@ -187,9 +194,9 @@ pub struct SplitRequest {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SplitResponse {
|
||||
/// Promises to keep
|
||||
pub fst: Vec<BlindedMessage>,
|
||||
pub fst: Vec<Promise>,
|
||||
/// Promises to send
|
||||
pub snd: Vec<BlindedMessage>,
|
||||
pub snd: Vec<Promise>,
|
||||
}
|
||||
|
||||
/// Check spendabale request [NUT-07]
|
||||
|
||||
@@ -6,7 +6,7 @@ use bitcoin::Amount;
|
||||
use lightning_invoice::Invoice;
|
||||
use url::Url;
|
||||
|
||||
use cashu_rs::{cashu_mint::CashuMint, types::BlindedMessages};
|
||||
use cashu_rs::{cashu_mint::CashuMint, cashu_wallet::CashuWallet, types::BlindedMessages};
|
||||
|
||||
const MINTURL: &str = "https://legend.lnbits.com/cashu/api/v1/SKvHRus9dmjWHhstHrsazW/";
|
||||
|
||||
@@ -68,6 +68,21 @@ async fn test_check_fees() {
|
||||
println!("{fee:?}");
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test_receive() {
|
||||
let url = Url::from_str(MINTURL).unwrap();
|
||||
let mint = CashuMint::new(url);
|
||||
let mint_keys = mint.get_keys().await.unwrap();
|
||||
|
||||
let wallet = CashuWallet::new(mint, mint_keys);
|
||||
// FIXME: Have to manully paste an unspent token
|
||||
let token = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAzNmY1NTU0ZDMyZDg3MGFjMzZjMDIwOGNiMDlkZmJmZjNhN2RkZTUyNzMwOTNjYzk3ZjE2NDBkNjYyZTgyMmMyMCIsInNlY3JldCI6ImtuRlhvelpjUG5YK1l4dytIcmV3VVlXRHU2ZFVFbkY0KzRUTkRIN010V289In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
|
||||
|
||||
let prom = wallet.receive(token).await.unwrap();
|
||||
println!("{:?}", prom);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test_get_mint_info() {
|
||||
|
||||
Reference in New Issue
Block a user