This commit is contained in:
thesimplekid
2023-04-25 13:35:44 -04:00
parent 92c3b69c47
commit ee696ba4ab
9 changed files with 310 additions and 89 deletions

View File

@@ -12,13 +12,16 @@ description = "Cashu rust library"
[dependencies]
base64 = "0.21.0"
bitcoin = { version = "0.30.0", features=["serde"] }
bitcoin-private = "0.1.0"
bitcoin_hashes = "0.12.0"
hex = "0.4.3"
k256 = { version = "0.13.1", features=["arithmetic"] }
lightning-invoice = { version = "0.22.0", features=["serde"] }
minreq = { version = "2.7.0", features = ["json-using-serde", "https"] }
rand = "0.8.5"
secp256k1 = { version = "0.27.0", features = ["rand-std", "bitcoin-hashes-std"] }
getrandom = { version = "0.2", features = ["js"] }
serde = { version = "1.0.160", features = ["derive"]}
serde_bytes = "0.11.9"
serde_json = "1.0.96"
thiserror = "1.0.40"
url = "2.3.1"

View File

@@ -13,6 +13,7 @@ use crate::{
},
};
#[derive(Debug, Clone)]
pub struct CashuMint {
pub url: Url,
}

View File

@@ -122,6 +122,7 @@ impl CashuWallet {
proofs,
outputs,
};
println!("splint JSON {:?}", serde_json::to_string(&split_payload));
Ok(SplitPayload {
keep_blinded_messages,
@@ -136,21 +137,23 @@ impl CashuWallet {
let mut send_proofs = SendProofs::default();
for proof in proofs {
amount_avaliable += proof.amount;
if amount_avaliable > amount {
send_proofs.change_proofs.push(proof);
break;
} else {
amount_avaliable += proof.amount;
send_proofs.send_proofs.push(proof);
}
}
if amount_avaliable.lt(&amount) {
println!("Not enough funds");
return Err(Error::InsufficantFunds);
}
// If amount avaliable is EQUAL to send amount no need to split
if amount_avaliable.eq(&amount) {
println!("Equal Proofs: {:#?}", send_proofs);
return Ok(send_proofs);
}
@@ -179,6 +182,9 @@ impl CashuWallet {
&self.keys,
)?;
println!("Send Proofs: {:#?}", send_proofs);
println!("Keep Proofs: {:#?}", keep_proofs);
Ok(SendProofs {
change_proofs: keep_proofs,
send_proofs,

View File

@@ -1,120 +1,167 @@
//! Diffie-Hellmann key exchange
use std::str::FromStr;
use std::ops::{Add, Mul, Neg};
use bitcoin_hashes::sha256;
use bitcoin_hashes::Hash;
use secp256k1::rand::rngs::OsRng;
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
// use secp256k1::rand::rngs::OsRng;
// use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use k256::Scalar;
use k256::{AffinePoint, ProjectivePoint, PublicKey, Secp256k1, SecretKey};
use rand::rngs::OsRng;
use crate::error::Error;
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> {
let mut msg = secret_message.to_vec();
loop {
let hash = sha256::Hash::hash(&msg);
let mut pubkey_bytes = vec![0x02];
pubkey_bytes.extend_from_slice(&hash[..]);
fn hash_to_curve(message: &[u8]) -> PublicKey {
let mut msg_to_hash = message.to_vec();
match PublicKey::from_slice(&pubkey_bytes) {
Ok(pubkey) => return Ok(pubkey),
Err(_) => {
msg = hash.to_byte_array().to_vec();
}
loop {
let hash = sha256::Hash::hash(&msg_to_hash);
match PublicKey::from_sec1_bytes(
&[0x02u8]
.iter()
.chain(&hash.to_byte_array())
.cloned()
.collect::<Vec<u8>>(),
) {
Ok(pubkey) => return pubkey,
Err(_) => msg_to_hash = hash.to_byte_array().to_vec(),
}
}
}
/// Blind Message
/// Blind Message Alice Step one
pub fn blind_message(
secret: &[u8],
blinding_factor: Option<SecretKey>,
) -> Result<(PublicKey, SecretKey), Error> {
let y = hash_to_curve(secret)?;
let y = hash_to_curve(secret);
let secp = Secp256k1::new();
let r: SecretKey = match blinding_factor {
Some(sec_key) => sec_key,
None => {
let (secret_key, _public_key) = secp.generate_keypair(&mut OsRng);
secret_key
}
None => SecretKey::random(&mut rand::thread_rng()),
};
let b = y.combine(&r.public_key(&secp))?;
let b = ProjectivePoint::from(y) + ProjectivePoint::from(&r.public_key());
Ok((b, r))
Ok((PublicKey::try_from(b).unwrap(), r))
}
/// Unblind Message
/// Unblind Message (Alice Step 3)
pub fn unblind_message(
// C_
blinded_key: PublicKey,
r: SecretKey,
a: PublicKey,
// A
mint_pubkey: PublicKey,
) -> Result<PublicKey, Error> {
let secp = Secp256k1::new();
let a_neg = a.negate(&secp);
let blinded_key = blinded_key.combine(&a_neg).unwrap();
let unblinded_key =
blinded_key.mul_tweak(&secp, &Scalar::from_be_bytes(r.secret_bytes()).unwrap())?;
Ok(unblinded_key)
// C
// Unblinded message
let c = ProjectivePoint::from(blinded_key.as_affine())
- mint_pubkey
.as_affine()
.mul(Scalar::from(r.as_scalar_primitive()));
Ok(PublicKey::try_from(c).unwrap())
}
/// Sign Blinded Message (Step2 bob)
// Really only needed for mint
// Used here for testing
fn _sign_message(a: SecretKey, blinded_message: PublicKey) -> Result<PublicKey, Error> {
Ok(PublicKey::try_from(
blinded_message
.as_affine()
.mul(Scalar::from(a.as_scalar_primitive())),
)
.unwrap())
}
/// Verify Message
// Really only needed for mint
// used for testing
fn _verify_message(a: SecretKey, unblinded_message: PublicKey, msg: &str) -> Result<bool, Error> {
// Y
let y = hash_to_curve(msg.as_bytes());
Ok(unblinded_message
== PublicKey::try_from(*y.as_affine() * Scalar::from(a.as_scalar_primitive())).unwrap())
}
/// Construct Proof
pub fn construct_proof(
promises: Vec<Promise>,
rs: Vec<SecretKey>,
secrets: Vec<Vec<u8>>,
secrets: Vec<String>,
keys: &MintKeys,
) -> Result<Vec<Proof>, Error> {
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 blinded_c = promise.c;
let a: PublicKey = PublicKey::from_sec1_bytes(
keys.0
.get(&promise.amount.to_sat())
.unwrap()
.to_owned()
.as_bytes(),
)
.unwrap();
// println!("Construct proof Pub {:?}", serde_json::to_string(&a));
todo!();
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(),
secret: secrets[i].clone(),
c: unblinded_signature,
script: None,
};
proofs.push(proof);
}
println!("proofs: {:?}", proofs);
Ok(proofs)
}
pub fn verify_proof(proof: Proof, keys: &MintKeys) -> Result<(), Error> {
Ok(())
}
#[cfg(test)]
mod tests {
use hex::decode;
use std::str::FromStr;
use k256::elliptic_curve::scalar::ScalarPrimitive;
use super::*;
use crate::utils::generate_secret;
#[test]
fn test_hash_to_curve() {
let secret = "0000000000000000000000000000000000000000000000000000000000000000";
let sec_hex = decode(secret).unwrap();
let y = hash_to_curve(&sec_hex).unwrap();
let expected_y = PublicKey::from_str(
"0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925",
let y = hash_to_curve(&sec_hex);
let expected_y = PublicKey::from_sec1_bytes(
&hex::decode("0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925")
.unwrap(),
)
.unwrap();
assert_eq!(y, expected_y);
let secret = "0000000000000000000000000000000000000000000000000000000000000001";
let sec_hex = decode(secret).unwrap();
let y = hash_to_curve(&sec_hex).unwrap();
let expected_y = PublicKey::from_str(
"02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5",
let y = hash_to_curve(&sec_hex);
let expected_y = PublicKey::from_sec1_bytes(
&hex::decode("02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5")
.unwrap(),
)
.unwrap();
assert_eq!(y, expected_y);
@@ -123,42 +170,96 @@ mod tests {
#[test]
fn test_blind_message() {
let message = "test_message";
let blinding_factor = "0000000000000000000000000000000000000000000000000000000000000001";
let sec = SecretKey::from_str(blinding_factor).unwrap();
let sec = SecretKey::new(ScalarPrimitive::ONE);
let (b, r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
let (b, r) = blind_message(message.as_bytes(), Some(sec.clone())).unwrap();
assert_eq!(
b.to_string(),
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2".to_string()
b,
PublicKey::from_sec1_bytes(
&hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")
.unwrap()
)
.unwrap()
);
assert_eq!(r, sec);
}
#[test]
fn test_sign_message() {
let message = "test_message";
let sec = SecretKey::new(ScalarPrimitive::ONE);
let (blinded_message, _r) = blind_message(message.as_bytes(), Some(sec)).unwrap();
// A
let bob_sec = SecretKey::new(ScalarPrimitive::ONE);
// C_
let signed = _sign_message(bob_sec, blinded_message).unwrap();
assert_eq!(
signed,
PublicKey::from_sec1_bytes(
&hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")
.unwrap()
)
.unwrap()
);
}
#[test]
fn test_unblind_message() {
let blinded_key = PublicKey::from_str(
"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
let blinded_key = PublicKey::from_sec1_bytes(
&hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2")
.unwrap(),
)
.unwrap();
let r =
SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
.unwrap();
let a = PublicKey::from_str(
"020000000000000000000000000000000000000000000000000000000000000001",
let r = SecretKey::new(ScalarPrimitive::ONE);
let a = PublicKey::from_sec1_bytes(
&hex::decode("020000000000000000000000000000000000000000000000000000000000000001")
.unwrap(),
)
.unwrap();
let unblinded = unblind_message(blinded_key, r, a).unwrap();
assert_eq!(
PublicKey::from_str(
"03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd"
PublicKey::from_sec1_bytes(
&hex::decode("03c724d7e6a5443b39ac8acf11f40420adc4f99a02e7cc1b57703d9391f6d129cd")
.unwrap()
)
.unwrap(),
unblinded
);
}
#[test]
fn test_blinded_dhke() {
// a
let bob_sec = SecretKey::random(&mut rand::thread_rng());
// A
let bob_pub = bob_sec.public_key();
// let alice_sec = SecretKey::random(&mut rand::thread_rng());
let x = generate_secret();
// Y
let y = hash_to_curve(x.as_bytes());
// B_
let blinded = blind_message(&y.to_sec1_bytes(), None).unwrap();
// C_
let signed = _sign_message(bob_sec.clone(), blinded.0).unwrap();
// C
let c = unblind_message(signed, blinded.1, bob_pub).unwrap();
assert!(_verify_message(bob_sec, c, &x).unwrap());
}
}

View File

@@ -8,9 +8,6 @@ pub enum Error {
/// Parse Url Error
#[error("minreq error: {0}")]
UrlParseError(#[from] url::ParseError),
/// Secp245k1
#[error("secp256k1 error: {0}")]
Secpk256k1Error(#[from] secp256k1::Error),
/// Unsupported Token
#[error("Unsupported Token")]
UnsupportedToken,

View File

@@ -19,3 +19,49 @@ pub mod serde_url {
Url::parse(&url_string).map_err(serde::de::Error::custom)
}
}
pub mod bytes_base64 {
use base64::{engine::general_purpose, Engine as _};
use serde::Deserialize;
pub fn serialize<S>(my_bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let encoded = general_purpose::STANDARD.encode(my_bytes);
serializer.serialize_str(&encoded)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
let decoded = general_purpose::STANDARD
.decode(encoded)
.map_err(serde::de::Error::custom)?;
Ok(decoded)
}
}
pub mod serde_public_key {
use k256::PublicKey;
use serde::Deserialize;
pub fn serialize<S>(pubkey: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let encoded = hex::encode(pubkey.to_sec1_bytes());
serializer.serialize_str(&encoded)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
where
D: serde::Deserializer<'de>,
{
let encoded = String::deserialize(deserializer)?;
let decoded = hex::decode(encoded).map_err(serde::de::Error::custom)?;
PublicKey::from_sec1_bytes(&decoded).map_err(serde::de::Error::custom)
}
}

View File

@@ -4,13 +4,15 @@ use std::{collections::HashMap, str::FromStr};
use base64::{engine::general_purpose, Engine as _};
use bitcoin::Amount;
use k256::{PublicKey, SecretKey};
use lightning_invoice::Invoice;
use rand::Rng;
use secp256k1::{PublicKey, SecretKey};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use url::Url;
use crate::{dhke::blind_message, error::Error, serde_utils::serde_url, utils::split_amount};
use crate::utils::generate_secret;
use crate::{
dhke::blind_message, error::Error, serde_utils, serde_utils::serde_url, utils::split_amount,
};
/// Blinded Message [NUT-00]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -20,6 +22,7 @@ pub struct BlindedMessage {
pub amount: Amount,
/// encrypted secret message (B_)
#[serde(rename = "B_")]
#[serde(with = "serde_utils::serde_public_key")]
pub b: PublicKey,
}
@@ -29,7 +32,7 @@ pub struct BlindedMessages {
/// Blinded messages
pub blinded_messages: Vec<BlindedMessage>,
/// Secrets
pub secrets: Vec<Vec<u8>>,
pub secrets: Vec<String>,
/// Rs
pub rs: Vec<SecretKey>,
/// Amounts
@@ -40,14 +43,13 @@ impl BlindedMessages {
pub fn random(amount: Amount) -> Result<Self, Error> {
let mut blinded_messages = BlindedMessages::default();
let mut rng = rand::thread_rng();
for amount in split_amount(amount) {
let bytes: [u8; 32] = rng.gen();
let (blinded, r) = blind_message(&bytes, None)?;
let secret = generate_secret();
let (blinded, r) = blind_message(secret.as_bytes(), None)?;
let blinded_message = BlindedMessage { amount, b: blinded };
blinded_messages.secrets.push(bytes.to_vec());
blinded_messages.secrets.push(secret);
blinded_messages.blinded_messages.push(blinded_message);
blinded_messages.rs.push(r);
blinded_messages.amounts.push(amount);
@@ -56,20 +58,23 @@ impl BlindedMessages {
Ok(blinded_messages)
}
/*
pub fn blank() -> Result<Self, Error> {
let mut blinded_messages = BlindedMessages::default();
let mut rng = rand::thread_rng();
for _i in 0..4 {
let bytes: [u8; 32] = rng.gen();
let (blinded, r) = blind_message(&bytes, None)?;
let secret_base64 = general_purpose::STANDARD.encode(bytes);
let (blinded, r) = blind_message(secret_base64.as_bytes(), None)?;
let blinded_message = BlindedMessage {
amount: Amount::ZERO,
b: blinded,
};
blinded_messages.secrets.push(bytes.to_vec());
blinded_messages.secrets.push(secret_base64);
blinded_messages.blinded_messages.push(blinded_message);
blinded_messages.rs.push(r);
blinded_messages.amounts.push(Amount::ZERO);
@@ -77,6 +82,7 @@ impl BlindedMessages {
Ok(blinded_messages)
}
*/
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -86,7 +92,7 @@ pub struct SplitPayload {
pub split_payload: SplitRequest,
}
/// Promise (BlindedSignature) [NIP-00]
/// Promise (BlindedSignature) [NUT-00]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Promise {
pub id: String,
@@ -94,7 +100,8 @@ pub struct Promise {
pub amount: Amount,
/// blinded signature (C_) on the secret message `B_` of [BlindedMessage]
#[serde(rename = "C_")]
pub c: String,
#[serde(with = "serde_utils::serde_public_key")]
pub c: PublicKey,
}
/// Proofs [NUT-00]
@@ -104,21 +111,24 @@ pub struct Proof {
#[serde(with = "bitcoin::amount::serde::as_sat")]
pub amount: Amount,
/// Secret message
// #[serde(with = "crate::serde_utils::bytes_base64")]
pub secret: String,
/// Unblinded signature
#[serde(rename = "C")]
pub c: String,
#[serde(with = "serde_utils::serde_public_key")]
pub c: PublicKey,
/// `Keyset id`
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
/// P2SHScript that specifies the spending condition for this Proof
pub script: Option<String>,
}
/// Mint Keys [NIP-01]
/// Mint Keys [NUT-01]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeys(pub HashMap<u64, String>);
/// Mint Keysets [NIP-02]
/// Mint Keysets [UT-02]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintKeySets {
/// set of public keys that the mint generates
@@ -134,7 +144,7 @@ pub struct RequestMintResponse {
pub hash: String,
}
/// Post Mint Request [NIP-04]
/// Post Mint Request [NUT-04]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintRequest {
pub outputs: Vec<BlindedMessage>,
@@ -266,7 +276,8 @@ pub struct MintInfo {
/// name of the mint and should be recognizable
pub name: String,
/// hex pubkey of the mint
pub pubkey: String,
#[serde(with = "serde_utils::serde_public_key")]
pub pubkey: PublicKey,
/// implementation name and the version running
pub version: MintVersion,
/// short description of the mint

View File

@@ -1,4 +1,6 @@
use base64::{engine::general_purpose, Engine as _};
use bitcoin::Amount;
use rand::prelude::*;
/// Split amount into cashu denominations (powers of 2)
pub fn split_amount(amount: Amount) -> Vec<Amount> {
@@ -13,6 +15,13 @@ pub fn split_amount(amount: Amount) -> Vec<Amount> {
chunks
}
pub fn generate_secret() -> String {
let mut rng = rand::thread_rng();
let mut secret = [0u8; 32];
rng.fill_bytes(&mut secret);
general_purpose::STANDARD.encode(secret)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -6,10 +6,11 @@ use bitcoin::Amount;
use lightning_invoice::Invoice;
use url::Url;
use cashu_rs::{cashu_mint::CashuMint, cashu_wallet::CashuWallet, types::BlindedMessages};
use cashu_rs::{cashu_mint::CashuMint, cashu_wallet::CashuWallet, types::{BlindedMessages, TokenData}};
const MINTURL: &str = "https://legend.lnbits.com/cashu/api/v1/SKvHRus9dmjWHhstHrsazW/";
#[ignore]
#[tokio::test]
async fn test_get_mint_keys() {
let url = Url::from_str(MINTURL).unwrap();
@@ -19,6 +20,7 @@ async fn test_get_mint_keys() {
assert!(mint_keys.0.capacity() > 1);
}
#[ignore]
#[tokio::test]
async fn test_get_mint_keysets() {
let url = Url::from_str(MINTURL).unwrap();
@@ -28,6 +30,7 @@ async fn test_get_mint_keysets() {
assert!(!mint_keysets.keysets.is_empty())
}
#[ignore]
#[tokio::test]
async fn test_request_mint() {
let url = Url::from_str(MINTURL).unwrap();
@@ -49,7 +52,7 @@ async fn test_mint() {
// Since before the mint happens the invoice in the mint req has to be payed this wait is here
// probally some way to simulate this in a better way
// but for now pay it quick
thread::sleep(Duration::from_secs(10));
thread::sleep(Duration::from_secs(30));
let blinded_messages = BlindedMessages::random(Amount::from_sat(21)).unwrap();
let mint_res = mint.mint(blinded_messages, &mint_req.hash).await.unwrap();
@@ -57,6 +60,7 @@ async fn test_mint() {
println!("Mint: {:?}", mint_res);
}
#[ignore]
#[tokio::test]
async fn test_check_fees() {
let invoice = Invoice::from_str("lnbc10n1p3a6s0dsp5n55r506t2fv4r0mjcg30v569nk2u9s40ur4v3r3mgtscjvkvnrqqpp5lzfv8fmjzduelk74y9rsrxrayvhyzcdsh3zkdgv0g50napzalvqsdqhf9h8vmmfvdjn5gp58qengdqxq8p3aaymdcqpjrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7z70cgqqg4sqqqqqqqlgqqqqrucqjq9qyysgqrjky5axsldzhqsjwsc38xa37k6t04le3ws4t26nqej62vst5xkz56qw85r6c4a3tr79588e0ceuuahwgfnkqc6n6269unlwqtvwr5vqqy0ncdq").unwrap();
@@ -64,8 +68,8 @@ async fn test_check_fees() {
let url = Url::from_str(MINTURL).unwrap();
let mint = CashuMint::new(url);
let fee = mint.check_fees(invoice).await.unwrap();
println!("{fee:?}");
let _fee = mint.check_fees(invoice).await.unwrap();
// println!("{fee:?}");
}
#[ignore]
@@ -77,14 +81,56 @@ async fn test_receive() {
let wallet = CashuWallet::new(mint, mint_keys);
// FIXME: Have to manully paste an unspent token
let token = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAzNmY1NTU0ZDMyZDg3MGFjMzZjMDIwOGNiMDlkZmJmZjNhN2RkZTUyNzMwOTNjYzk3ZjE2NDBkNjYyZTgyMmMyMCIsInNlY3JldCI6ImtuRlhvelpjUG5YK1l4dytIcmV3VVlXRHU2ZFVFbkY0KzRUTkRIN010V289In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let token =
"cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAyOTMwNTJhNWEwN2FjMTkxMDgyODQyZTExMDVkOTQ2MzliNWI5NmE3MTU3NTQzZTllMjdkOTg3MWU5YjE2NDJkNCIsInNlY3JldCI6IlQxZ0lYUWlpZnBNY21OMU9ENnV4Nk1rMS93bXIxU3VHU2tvVXIyTkpqZE09In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let prom = wallet.receive(token).await.unwrap();
println!("{:?}", prom);
// println!("{:?}", prom);
}
#[ignore]
#[tokio::test]
async fn test_check_spendable() {
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 =
"cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAyNGQ0ZDUxNWIxYzk2MWZkYzYxY2M5MDFmNzBkOGUwZDA0ZWIwYTI2MzBhNWYxYTdmM2I5ZmRhODdmMGJkNjNmNyIsInNlY3JldCI6IkVUc2pXSGJheXYyTUJQeXo1b0toay85dVdoaldIeXJkODdBQy9XY3VjbkE9In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let token_data = TokenData::from_str(token).unwrap();
let spendable = wallet.check_proofs_spent(token_data.token[0].clone().proofs).await.unwrap();
// println!("Spendable: {:?}", spendable);
}
// #[ignore]
#[tokio::test]
async fn test_split() {
let url = Url::from_str("http://localhost:3338").unwrap();
let mint = CashuMint::new(url);
let mint_keys = mint.get_keys().await.unwrap();
let wallet = CashuWallet::new(mint.clone(), mint_keys);
// FIXME: Have to manully paste an unspent token
let token =
"cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAyNDVjYjBhYzhlMWNmNGViMjk2ZjAyMTFiMDdjYTBjNTczOWM1MTMwMDEzMzM3MjczOTE1ZTVlMDY2NjZlOTBiZCIsInNlY3JldCI6ImRWNThLbU5VOWE0UU45c0QyVDd5bGkvam9qcWpwb3o0VVhkSGR6dkdRZ289In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let proofs = wallet.receive(token).await.unwrap();
let split = wallet.create_split(Amount::ONE_SAT, Amount::ONE_SAT, proofs).await.unwrap();
println!("Split: {:#?}", split);
println!("splint JSON {:?}", serde_json::to_string(&split.split_payload));
let split = mint.split(split.split_payload).await;
println!("Split res: {:#?}", split);
}
#[ignore]
#[tokio::test]
async fn test_send() {
let url = Url::from_str(MINTURL).unwrap();
let mint = CashuMint::new(url);
@@ -92,12 +138,13 @@ async fn test_send() {
let wallet = CashuWallet::new(mint, mint_keys);
// FIXME: Have to manully paste an unspent token
let token = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MSwiQyI6IjAyMjRhMjU5NGY5NWMyMmRiZTA2YjZlN2YzMDNkYTdiZWYwNmM1YzI5YTBjMDU3ZWYyNmNhOWU3ZDVlYzc3MTYzZiIsInNlY3JldCI6IncyL1FpZjZFdlBRYWRtUlYxZzQyTWMrZWVVZ1V3TVZtSC9ndlVlaHhZTXM9In0seyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6NCwiQyI6IjAyMWEwYTIwYTZmOGEwY2JmMWY2Njc5OTIzNWE5N2U4ZTgxNjkxZWExMTFkMWVjYWJiOWZlZjE5OWRhMzYxNmU0YiIsInNlY3JldCI6InFYazRGbjZKdFBaUnVIRWlFMVVBUDB4MCtEcjd4Y21yNWRwTUVRRldDZ2s9In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let token =
"cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6Im9DV2NkWXJyeVRrUiIsImFtb3VudCI6MiwiQyI6IjAzMGI4NWFhYjI5MDY2MGRlNDk4NTEzODZmYTJhZWY2MTk3YzM2MzRkZDE4OGMzMjM2ZDI2YTFhNDdmODZlNzQxNCIsInNlY3JldCI6IjNET0c3eHM2T2RRYno1Nmk1c0lRQjhndHUzbjRMdjRGSU5TeEtLUkJ6UzA9In1dLCJtaW50IjoiaHR0cHM6Ly9sZWdlbmQubG5iaXRzLmNvbS9jYXNodS9hcGkvdjEvU0t2SFJ1czlkbWpXSGhzdEhyc2F6VyJ9XX0=";
let prom = wallet.receive(token).await.unwrap();
let send = wallet.send(Amount::from_sat(1), prom).await.unwrap();
let send = wallet.send(Amount::from_sat(2), prom).await.unwrap();
println!("{:?}", send);
panic!()
}
#[ignore]