feat: nut11 p2pk

This commit is contained in:
thesimplekid
2024-02-27 21:48:58 +00:00
parent b4b935db68
commit 1892a9a4b7
8 changed files with 261 additions and 120 deletions

View File

@@ -168,15 +168,19 @@ impl Client for HttpClient {
async fn post_swap(
&self,
mint_url: Url,
split_request: SwapRequest,
swap_request: SwapRequest,
) -> Result<SwapResponse, Error> {
let url = join_url(mint_url, &["v1", "swap"])?;
let res = minreq::post(url).with_json(&split_request)?.send()?;
println!("{}", serde_json::to_string(&swap_request).unwrap());
let res = minreq::post(url).with_json(&swap_request)?.send()?;
let value = res.json::<Value>()?;
println!("{}", value);
let response: Result<SwapResponse, serde_json::Error> =
serde_json::from_value(value.clone());
println!("{:?}", response);
match response {
Ok(res) => Ok(res),

View File

@@ -311,6 +311,7 @@ impl Mint {
amount,
b,
keyset_id,
..
} = blinded_message;
let keyset = self

View File

@@ -9,7 +9,7 @@ use cashu::nuts::nut07::ProofState;
use cashu::nuts::nut11::SigningKey;
use cashu::nuts::{
BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, P2PKConditions, PreMintSecrets,
PreSwap, Proof, Proofs, SwapRequest, Token,
PreSwap, Proof, Proofs, SigFlag, SwapRequest, Token,
};
#[cfg(feature = "nut07")]
use cashu::secret::Secret;
@@ -234,6 +234,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
}
let keysets = self.client.get_mint_keysets(mint_url.try_into()?).await?;
println!("{:?}", keysets);
self.localstore
.add_mint_keysets(
@@ -353,7 +354,17 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
// TODO: if none fetch keyset for mint
let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
let keys =
if let Some(keys) = self.localstore.get_keys(&active_keyset_id.unwrap()).await? {
keys
} else {
self.get_mint_keys(&token.mint, active_keyset_id.unwrap())
.await?;
self.localstore
.get_keys(&active_keyset_id.unwrap())
.await?
.unwrap()
};
// Sum amount of all proofs
let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
@@ -372,15 +383,20 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
swap_response.signatures,
pre_swap.pre_mint_secrets.rs(),
pre_swap.pre_mint_secrets.secrets(),
&keys.unwrap(),
&keys,
)?;
// println!("{:?}", p);
let mint_proofs = proofs.entry(token.mint).or_default();
mint_proofs.extend(p);
}
//println!("{:?}", proofs);
for (mint, proofs) in proofs {
self.localstore.add_proofs(mint, proofs).await?;
for (mint, p) in proofs {
println!("{:?}", serde_json::to_string(&p));
println!("{:?}", mint);
self.add_mint(mint.clone()).await?;
self.localstore.add_proofs(mint, p).await?;
}
Ok(())
@@ -408,9 +424,9 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
desired_messages.sort_secrets();
desired_messages
} else {
let value = proofs.iter().map(|p| p.amount).sum();
let amount = proofs.iter().map(|p| p.amount).sum();
PreMintSecrets::random(active_keyset_id, value)?
PreMintSecrets::random(active_keyset_id, amount)?
};
let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
@@ -420,7 +436,51 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
swap_request,
})
}
/*
/// Create Swap Payload
async fn create_swap_signed(
&mut self,
mint_url: &UncheckedUrl,
unit: &CurrencyUnit,
amount: Option<Amount>,
proofs: Proofs,
signing_key: Option<SigningKey>,
) -> Result<PreSwap, Error> {
let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
let pre_mint_secrets = if let Some(amount) = amount {
let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
let change_messages = if let Some(signing_key) = signing_key {
PreMintSecrets::random_signed(active_keyset_id, change_amount, signing_key)?
} else {
PreMintSecrets::random(active_keyset_id, change_amount)?
};
// Combine the BlindedMessages totoalling the desired amount with change
desired_messages.combine(change_messages);
// Sort the premint secrets to avoid finger printing
desired_messages.sort_secrets();
desired_messages
} else {
let amount = proofs.iter().map(|p| p.amount).sum();
if let Some(signing_key) = signing_key {
PreMintSecrets::random_signed(active_keyset_id, amount, signing_key)?
} else {
PreMintSecrets::random(active_keyset_id, amount)?
}
};
let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
Ok(PreSwap {
pre_mint_secrets,
swap_request,
})
}
*/
pub async fn process_swap_response(
&self,
blinded_messages: PreMintSecrets,
@@ -752,6 +812,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
encoded_token: &str,
signing_keys: Vec<SigningKey>,
) -> Result<(), Error> {
let signing_key = signing_keys[0].clone();
let pubkey_secret_key: HashMap<String, SigningKey> = signing_keys
.into_iter()
.map(|s| (s.public_key().to_string(), s))
@@ -778,6 +839,8 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
let mut proofs = token.proofs;
let mut sig_flag = None;
for proof in &mut proofs {
if let Ok(secret) =
<cashu::secret::Secret as TryInto<cashu::nuts::nut10::Secret>>::try_into(
@@ -786,21 +849,36 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
{
let conditions: Result<P2PKConditions, _> = secret.try_into();
if let Ok(conditions) = conditions {
println!("{:?}", conditions);
let pubkeys = conditions.pubkeys;
for pubkey in pubkeys {
if let Some(signing) = pubkey_secret_key.get(&pubkey.to_string()) {
proof.sign_p2pk_proof(signing.clone()).ok();
proof.sign_p2pk_proof(signing.clone()).unwrap();
proof.verify_p2pk().unwrap();
println!("v");
}
}
sig_flag = Some(conditions.sig_flag);
}
}
}
let pre_swap = self
let mut pre_swap = self
.create_swap(&token.mint, &unit, Some(amount), proofs)
.await?;
if let Some(sigflag) = sig_flag {
if sigflag.eq(&SigFlag::SigAll) {
for blinded_message in &mut pre_swap.swap_request.outputs {
blinded_message
.sign_p2pk_blinded_message(signing_key.clone())
.unwrap();
}
}
}
let swap_response = self
.client
.post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
@@ -824,54 +902,70 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
Ok(())
}
/*
pub async fn claim_p2pk_locked_proofs(
&mut self,
sigflag: SigFlag,
mint_url: &UncheckedUrl,
unit: &CurrencyUnit,
signing_key: SigningKey,
proofs: Proofs,
) -> Result<(), Error> {
let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?;
pub async fn claim_p2pk_locked_proofs(
&mut self,
mint_url: &UncheckedUrl,
unit: &CurrencyUnit,
signing_key: SigningKey,
proofs: Proofs,
) -> Result<(), Error> {
let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?;
let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len());
let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len());
// Sum amount of all proofs
let amount: Amount = proofs.iter().map(|p| p.amount).sum();
// Sum amount of all proofs
let amount: Amount = proofs.iter().map(|p| p.amount).sum();
for p in proofs.clone() {
let mut p = p;
p.sign_p2pk_proof(signing_key.clone()).unwrap();
signed_proofs.push(p);
}
for p in proofs.clone() {
let mut p = p;
p.sign_p2pk_proof(signing_key.clone()).unwrap();
signed_proofs.push(p);
let pre_swap = match sigflag {
SigFlag::SigInputs => {
self.create_swap(mint_url, &unit, Some(amount), signed_proofs)
.await?
}
SigFlag::SigAll => {
self.create_swap_signed(
mint_url,
unit,
Some(amount),
signed_proofs,
Some(signing_key),
)
.await?
}
_ => todo!(),
};
let swap_response = self
.client
.post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
.await?;
// Proof to keep
let p = construct_proofs(
swap_response.signatures,
pre_swap.pre_mint_secrets.rs(),
pre_swap.pre_mint_secrets.secrets(),
&keys.unwrap(),
)?;
self.localstore
.remove_proofs(mint_url.clone(), &proofs)
.await?;
self.localstore.add_proofs(mint_url.clone(), p).await?;
Ok(())
}
let pre_swap = self
.create_swap(mint_url, &unit, Some(amount), signed_proofs)
.await?;
let swap_response = self
.client
.post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
.await?;
// Proof to keep
let p = construct_proofs(
swap_response.signatures,
pre_swap.pre_mint_secrets.rs(),
pre_swap.pre_mint_secrets.secrets(),
&keys.unwrap(),
)?;
self.localstore
.remove_proofs(mint_url.clone(), &proofs)
.await?;
self.localstore.add_proofs(mint_url.clone(), p).await?;
Ok(())
}
*/
pub fn proofs_to_token(
&self,

View File

@@ -64,7 +64,15 @@ pub struct ErrorResponse {
impl ErrorResponse {
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
if let Ok(res) = serde_json::from_str::<ErrorResponse>(json) {
Ok(res)
} else {
Ok(Self {
code: 999,
error: Some(json.to_string()),
detail: None,
})
}
}
}

View File

@@ -2,13 +2,14 @@
// https://github.com/cashubtc/nuts/blob/main/00.md
use std::fmt;
use std::hash::{Hash, Hasher};
use std::hash::{self, Hasher};
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use super::{Id, Proofs, PublicKey};
use super::{Id, Proofs, PublicKey, Signatures, SigningKey};
use crate::error::Error;
use crate::nuts::nut11::{witness_deserialize, witness_serialize};
use crate::secret::Secret;
use crate::url::UncheckedUrl;
use crate::Amount;
@@ -24,9 +25,39 @@ pub struct BlindedMessage {
/// encrypted secret message (B_)
#[serde(rename = "B_")]
pub b: PublicKey,
/// Witness
#[serde(default)]
#[serde(skip_serializing_if = "Signatures::is_empty")]
#[serde(serialize_with = "witness_serialize")]
#[serde(deserialize_with = "witness_deserialize")]
pub witness: Signatures,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
impl BlindedMessage {
pub fn new(amount: Amount, keyset_id: Id, b: PublicKey) -> Self {
Self {
amount,
keyset_id,
b,
witness: Signatures::default(),
}
}
pub fn sign_p2pk_blinded_message(&mut self, secret_key: SigningKey) -> Result<(), Error> {
let msg_to_sign = hex::decode(self.b.to_string())?;
println!("{:?}", msg_to_sign);
let signature = secret_key.sign(&msg_to_sign);
self.witness
.signatures
.push(hex::encode(signature.to_bytes()));
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, hash::Hash)]
#[serde(rename_all = "lowercase")]
pub enum CurrencyUnit {
#[default]
@@ -145,11 +176,7 @@ pub mod wallet {
let secret = Secret::new();
let (blinded, r) = blind_message(&secret.to_bytes()?, None)?;
let blinded_message = BlindedMessage {
amount,
b: blinded,
keyset_id,
};
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
output.push(PreMint {
secret,
@@ -172,11 +199,7 @@ pub mod wallet {
for (secret, amount) in secrets.into_iter().zip(amounts) {
let (blinded, r) = blind_message(&secret.to_bytes()?, None)?;
let blinded_message = BlindedMessage {
amount,
b: blinded,
keyset_id,
};
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
output.push(PreMint {
secret,
@@ -199,11 +222,7 @@ pub mod wallet {
let secret = Secret::new();
let (blinded, r) = blind_message(&secret.to_bytes()?, None)?;
let blinded_message = BlindedMessage {
amount: Amount::ZERO,
b: blinded,
keyset_id,
};
let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
output.push(PreMint {
secret,
@@ -236,11 +255,7 @@ pub mod wallet {
let (blinded, r) =
blind_message(&secret.to_bytes()?, Some(blinding_factor.into()))?;
let blinded_message = BlindedMessage {
keyset_id,
amount,
b: blinded,
};
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
let pre_mint = PreMint {
blinded_message,
@@ -270,11 +285,7 @@ pub mod wallet {
let secret: Secret = conditions.clone().try_into().unwrap();
let (blinded, r) = blind_message(&secret.to_bytes()?, None)?;
let blinded_message = BlindedMessage {
amount,
b: blinded,
keyset_id,
};
let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
output.push(PreMint {
secret,
@@ -480,7 +491,7 @@ impl Proof {
}
}
impl Hash for Proof {
impl hash::Hash for Proof {
fn hash<H: Hasher>(&self, state: &mut H) {
self.secret.hash(state);
}

View File

@@ -64,6 +64,12 @@ impl From<VerifyingKey> for PublicKey {
}
}
impl From<super::VerifyingKey> for PublicKey {
fn from(value: super::VerifyingKey) -> PublicKey {
let v: VerifyingKey = value.into();
PublicKey(v.into())
}
}
impl PublicKey {
pub fn to_bytes(&self) -> Box<[u8]> {
self.0.to_sec1_bytes()

View File

@@ -6,12 +6,11 @@ use std::fmt;
use std::hash::{self, Hasher};
use std::str::FromStr;
use bitcoin::hashes::{sha256, Hash};
use k256::schnorr::signature::{Signer, Verifier};
use k256::schnorr::Signature;
use serde::de::Error as DeserializerError;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
use super::nut01::PublicKey;
use super::nut02::Id;
@@ -25,7 +24,7 @@ use crate::Amount;
pub struct Signatures {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
signatures: Vec<String>,
pub signatures: Vec<String>,
}
impl Signatures {
@@ -50,9 +49,26 @@ pub struct Proof {
/// Witness
#[serde(default)]
#[serde(skip_serializing_if = "Signatures::is_empty")]
#[serde(serialize_with = "witness_serialize")]
#[serde(deserialize_with = "witness_deserialize")]
pub witness: Signatures,
}
pub fn witness_serialize<S>(x: &Signatures, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&serde_json::to_string(x).map_err(ser::Error::custom)?)
}
pub fn witness_deserialize<'de, D>(deserializer: D) -> Result<Signatures, D::Error>
where
D: de::Deserializer<'de>,
{
let s: String = String::deserialize(deserializer)?;
serde_json::from_str(&s).map_err(de::Error::custom)
}
impl Proof {
pub fn new(amount: Amount, keyset_id: Id, secret: crate::secret::Secret, c: PublicKey) -> Self {
Proof {
@@ -136,7 +152,9 @@ impl TryFrom<P2PKConditions> for Secret {
return Err(Error::Amount);
}
let data = pubkeys[0].to_string();
let data: PublicKey = pubkeys[0].clone().into();
let data = data.to_string();
let mut tags = vec![];
@@ -259,7 +277,13 @@ impl Proof {
let mut valid_sigs = 0;
let msg = sha256::Hash::hash(&self.secret.to_bytes().unwrap());
println!("{:?}", self.secret.to_string());
println!(
"sec bytes: {:?}",
self.secret.to_string().into_bytes().len()
);
let msg = &self.secret.to_bytes().unwrap();
for signature in &self.witness.signatures {
let mut pubkeys = spending_conditions.pubkeys.clone();
@@ -268,31 +292,30 @@ impl Proof {
for v in &spending_conditions.pubkeys {
let sig = Signature::try_from(hex::decode(signature).unwrap().as_slice()).unwrap();
if v.verify(&msg.to_byte_array(), &sig).is_ok() {
if v.verify(msg, &sig).is_ok() {
valid_sigs += 1;
} else {
println!("{:?}", v.verify(&msg.to_byte_array(), &sig).unwrap());
println!("{:?}", v.verify(msg, &sig).unwrap());
}
}
}
if valid_sigs.ge(&spending_conditions.num_sigs.unwrap_or(1)) {
println!("valid sigs: {}", valid_sigs);
return Ok(());
}
if let Some(locktime) = spending_conditions.locktime {
// If lock time has passed check if refund witness signature is valid
if locktime.lt(&unix_time()) {
if !spending_conditions.refund_keys.is_empty() {
for s in &self.witness.signatures {
for v in &spending_conditions.refund_keys {
let sig = Signature::try_from(s.as_bytes())
.map_err(|_| Error::InvalidSignature)?;
if locktime.lt(&unix_time()) && !spending_conditions.refund_keys.is_empty() {
for s in &self.witness.signatures {
for v in &spending_conditions.refund_keys {
let sig = Signature::try_from(s.as_bytes())
.map_err(|_| Error::InvalidSignature)?;
// As long as there is one valid refund signature it can be spent
if v.verify(&msg.to_byte_array(), &sig).is_ok() {
return Ok(());
}
// As long as there is one valid refund signature it can be spent
if v.verify(msg, &sig).is_ok() {
return Ok(());
}
}
}
@@ -303,13 +326,14 @@ impl Proof {
}
pub fn sign_p2pk_proof(&mut self, secret_key: SigningKey) -> Result<(), Error> {
let msg_to_sign = sha256::Hash::hash(&self.secret.to_bytes().unwrap());
let msg_to_sign = &self.secret.to_bytes().unwrap();
let signature = secret_key.sign(msg_to_sign.as_byte_array());
let signature = secret_key.sign(msg_to_sign);
self.witness
.signatures
.push(hex::encode(signature.to_bytes()));
Ok(())
}
}
@@ -476,6 +500,7 @@ impl From<Tag> for Vec<String> {
let mut tag = vec![TagKind::Pubkeys.to_string()];
for pubkey in pubkeys {
let pubkey: PublicKey = pubkey.into();
tag.push(pubkey.to_string())
}
tag
@@ -529,7 +554,10 @@ impl VerifyingKey {
}
pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
Ok(self.0.verify(msg, signature).unwrap())
self.0
.verify(msg, signature)
.map_err(|_| Error::InvalidSignature)?;
Ok(())
}
}
@@ -622,7 +650,7 @@ impl From<SecretKey> for SigningKey {
impl SigningKey {
pub fn public_key(&self) -> VerifyingKey {
self.0.verifying_key().clone().into()
(*self.0.verifying_key()).into()
}
pub fn sign(&self, msg: &[u8]) -> Signature {
@@ -630,7 +658,7 @@ impl SigningKey {
}
pub fn verifying_key(&self) -> VerifyingKey {
VerifyingKey(self.0.verifying_key().clone())
VerifyingKey(*self.0.verifying_key())
}
}
@@ -701,15 +729,6 @@ mod tests {
assert_eq!(secret_der, secret);
}
#[test]
fn test_verify() {
let proof_str = r#"{"amount":0,"secret":"[\"P2PK\",{\"nonce\":\"190badde56afcbf67937e228744ea896bb3e48bcb60efa412799e1518618c287\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id": "009a1f293253e41e","witness":{"signatures":["2b117c29a0e405fcbcac4c632b5862eb3ace0d67c681e8209d3aa2f52d5198471629b1ec6bce75d3879c47725be89d28938e31236307b40bc6c89491fa540e35"]}}"#;
let proof: Proof = serde_json::from_str(proof_str).unwrap();
assert!(proof.verify_p2pk().is_ok());
}
#[test]
fn sign_proof() {
let secret_key = SigningKey::from_str(
@@ -721,7 +740,7 @@ mod tests {
let conditions = P2PKConditions {
locktime: None,
pubkeys: vec![v_key.into()],
pubkeys: vec![v_key],
refund_keys: vec![],
num_sigs: None,
sig_flag: SigFlag::SigInputs,
@@ -732,7 +751,7 @@ mod tests {
let mut proof = Proof {
keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
amount: Amount::ZERO,
secret: secret.try_into().unwrap(),
secret: secret.clone().try_into().unwrap(),
c: PublicKey::from_str(
"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
)
@@ -740,9 +759,7 @@ mod tests {
witness: Signatures { signatures: vec![] },
};
let signing_key: SigningKey = secret_key.try_into().unwrap();
proof.sign_p2pk_proof(signing_key).unwrap();
proof.sign_p2pk_proof(secret_key).unwrap();
assert!(proof.verify_p2pk().is_ok());
}

View File

@@ -69,7 +69,7 @@ impl Secret {
serde_json::from_str(&self.0);
match secret {
Ok(_) => Ok(self.0.clone().into_bytes()),
Ok(_) => Ok(self.0.clone().replace('\\', "").into_bytes()),
Err(_) => Ok(hex::decode(&self.0)?),
}
}