mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-24 08:05:02 +01:00
use k256
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CashuMint {
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
217
src/dhke.rs
217
src/dhke.rs
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
45
src/types.rs
45
src/types.rs
@@ -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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user