feat: wallet create and unlock p2pk

nut11 verifying and signingkeys
This commit is contained in:
thesimplekid
2024-02-20 20:13:59 +00:00
parent 2bacda4a56
commit 9f842d2c3c
8 changed files with 317 additions and 75 deletions

View File

@@ -14,7 +14,7 @@ use cashu::nuts::{CheckStateRequest, CheckStateResponse};
use cashu::secret::Secret;
use cashu::{Amount, Bolt11Invoice};
use serde_json::Value;
use tracing::{error, warn};
use tracing::warn;
use url::Url;
use super::join_url;
@@ -170,19 +170,18 @@ impl Client for HttpClient {
mint_url: Url,
split_request: SwapRequest,
) -> Result<SwapResponse, Error> {
// TODO: Add to endpoint
let url = join_url(mint_url, &["v1", "swap"])?;
let res = minreq::post(url).with_json(&split_request)?.send()?;
let value = res.json::<Value>()?;
let response: Result<SwapResponse, serde_json::Error> =
serde_json::from_value(res.json::<Value>()?.clone());
serde_json::from_value(value.clone());
if let Err(err) = &response {
error!("{}", err)
match response {
Ok(res) => Ok(res),
Err(_) => Err(ErrorResponse::from_json(&value.to_string())?.into()),
}
Ok(response?)
}
/// Spendable check [NUT-07]

View File

@@ -64,10 +64,10 @@ pub enum Error {
}
impl From<Error> for ErrorResponse {
fn from(_err: Error) -> ErrorResponse {
fn from(err: Error) -> ErrorResponse {
ErrorResponse {
code: 9999,
error: None,
error: Some(err.to_string()),
detail: None,
}
}

View File

@@ -6,9 +6,10 @@ use bip39::Mnemonic;
use cashu::dhke::{construct_proofs, unblind_message};
#[cfg(feature = "nut07")]
use cashu::nuts::nut07::ProofState;
use cashu::nuts::nut11::SigningKey;
use cashu::nuts::{
BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, PreMintSecrets, PreSwap, Proof,
Proofs, SwapRequest, Token,
BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, P2PKConditions, PreMintSecrets,
PreSwap, Proof, Proofs, SwapRequest, Token,
};
#[cfg(feature = "nut07")]
use cashu::secret::Secret;
@@ -363,7 +364,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
let swap_response = self
.client
.post_swap(token.mint.clone().try_into()?, pre_swap.split_request)
.post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
.await?;
// Proof to keep
@@ -393,9 +394,6 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
amount: Option<Amount>,
proofs: Proofs,
) -> Result<PreSwap, Error> {
// Since swap is used to get the needed combination of tokens for a specific
// amount first blinded messages are created for the amount
let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
let pre_mint_secrets = if let Some(amount) = amount {
@@ -415,11 +413,11 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
PreMintSecrets::random(active_keyset_id, value)?
};
let split_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
Ok(PreSwap {
pre_mint_secrets,
split_request,
swap_request,
})
}
@@ -471,7 +469,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
let swap_response = self
.client
.post_swap(mint_url.clone().try_into()?, pre_swap.split_request)
.post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
.await?;
let mut keep_proofs = Proofs::new();
@@ -553,7 +551,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
}
// Select proofs
async fn select_proofs(
pub async fn select_proofs(
&self,
mint_url: UncheckedUrl,
unit: &CurrencyUnit,
@@ -676,6 +674,118 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
Ok(melted)
}
/// Create P2PK locked proofs
/// Uses a swap to swap proofs for locked p2pk conditions
pub async fn create_p2pk_proofs(
&mut self,
mint_url: &UncheckedUrl,
unit: &CurrencyUnit,
input_proofs: Proofs,
conditions: P2PKConditions,
) -> Result<Proofs, Error> {
let amount = input_proofs.iter().map(|p| p.amount).sum();
let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
let pre_mint_secrets =
PreMintSecrets::with_p2pk_conditions(active_keyset_id, amount, conditions)?;
let swap_request =
SwapRequest::new(input_proofs.clone(), pre_mint_secrets.blinded_messages());
let pre_swap = PreSwap {
pre_mint_secrets,
swap_request,
};
let swap_response = self
.client
.post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
.await?;
let post_swap_proofs = construct_proofs(
swap_response.signatures,
pre_swap.pre_mint_secrets.rs(),
pre_swap.pre_mint_secrets.secrets(),
&self.active_keys(mint_url, unit).await?.unwrap(),
)?;
let mut send_proofs = vec![];
let mut change_proofs = vec![];
for proof in post_swap_proofs {
let conditions: Result<cashu::nuts::nut10::Secret, _> = (&proof.secret).try_into();
println!("{:?}", conditions);
if conditions.is_ok() {
send_proofs.push(proof);
} else {
change_proofs.push(proof);
}
}
self.localstore
.remove_proofs(mint_url.clone(), &input_proofs)
.await?;
self.localstore
.add_pending_proofs(mint_url.clone(), input_proofs)
.await?;
self.localstore
.add_pending_proofs(mint_url.clone(), send_proofs.clone())
.await?;
self.localstore
.add_proofs(mint_url.clone(), change_proofs.clone())
.await?;
Ok(send_proofs)
}
pub async fn claim_p2pk_locked_proof(
&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 mut signed_proofs: Proofs = Vec::with_capacity(proofs.len());
// 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);
}
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,
mint_url: UncheckedUrl,

View File

@@ -229,7 +229,7 @@ mod tests {
assert_eq!(sec, r.into());
assert_eq!(
b.to_hex(),
b.to_string(),
PublicKey::from(
k256::PublicKey::from_sec1_bytes(
&hex::decode(
@@ -239,7 +239,7 @@ mod tests {
)
.unwrap()
)
.to_hex()
.to_string()
);
let message = "f1aaf16c2239746f369572c0784d9dd3d032d952c2d992175873fb58fae31a60";
@@ -254,7 +254,7 @@ mod tests {
assert_eq!(sec, r.into());
assert_eq!(
b.to_hex(),
b.to_string(),
PublicKey::from(
k256::PublicKey::from_sec1_bytes(
&hex::decode(
@@ -264,7 +264,7 @@ mod tests {
)
.unwrap()
)
.to_hex()
.to_string()
);
}

View File

@@ -40,6 +40,6 @@ pub use nut08::{MeltBolt11Request, MeltBolt11Response};
#[cfg(feature = "nut10")]
pub use nut10::{Kind, Secret as Nut10Secret, SecretData};
#[cfg(feature = "nut11")]
pub use nut11::{P2PKConditions, Proof, SigFlag, Signatures};
pub use nut11::{P2PKConditions, Proof, SigFlag, Signatures, SigningKey, VerifyingKey};
pub type Proofs = Vec<Proof>;

View File

@@ -65,16 +65,6 @@ impl From<VerifyingKey> for PublicKey {
}
impl PublicKey {
pub fn from_hex(hex: String) -> Result<Self, Error> {
let hex = hex::decode(hex)?;
Ok(PublicKey(k256::PublicKey::from_sec1_bytes(&hex)?))
}
pub fn to_hex(&self) -> String {
let bytes = self.0.to_sec1_bytes();
hex::encode(bytes)
}
pub fn to_bytes(&self) -> Box<[u8]> {
self.0.to_sec1_bytes()
}
@@ -91,7 +81,8 @@ impl FromStr for PublicKey {
impl std::fmt::Display for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_hex())
let bytes = self.0.to_sec1_bytes();
f.write_str(&hex::encode(bytes))
}
}
@@ -280,9 +271,12 @@ mod tests {
#[test]
fn pubkey() {
let pubkey_str = "02c020067db727d586bc3183aecf97fcb800c3f4cc4759f69c626c9db5d8f5b5d4";
let pubkey = PublicKey::from_str(pubkey_str).unwrap();
assert_eq!(pubkey_str, pubkey.to_string());
/*
let pubkey_str = "04918dfc36c93e7db6cc0d60f37e1522f1c36b64d3f4b424c532d7c595febbc5";
let pubkey = PublicKey::from_hex(pubkey_str.to_string()).unwrap();
assert_eq!(pubkey_str, pubkey.to_hex())
assert_eq!(pubkey_str, pubkey.to_hex())*/
}
#[test]

View File

@@ -14,7 +14,7 @@ pub use crate::Bolt11Invoice;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PreSwap {
pub pre_mint_secrets: PreMintSecrets,
pub split_request: SwapRequest,
pub swap_request: SwapRequest,
}
/// Split Request [NUT-06]

View File

@@ -8,7 +8,7 @@ use std::str::FromStr;
use bitcoin::hashes::{sha256, Hash};
use k256::schnorr::signature::{Signer, Verifier};
use k256::schnorr::{Signature, SigningKey, VerifyingKey};
use k256::schnorr::Signature;
use serde::de::Error as DeserializerError;
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -16,17 +16,22 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::nut01::PublicKey;
use super::nut02::Id;
use super::nut10::{Secret, SecretData};
use super::SecretKey;
use crate::error::Error;
use crate::utils::unix_time;
use crate::Amount;
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Signatures {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
signatures: Vec<String>,
}
fn no_signatures(signatures: &Signatures) -> bool {
signatures.signatures.is_empty()
impl Signatures {
pub fn is_empty(&self) -> bool {
self.signatures.is_empty()
}
}
/// Proofs [NUT-11]
@@ -44,7 +49,7 @@ pub struct Proof {
pub keyset_id: Id,
/// Witness
#[serde(default)]
#[serde(skip_serializing_if = "no_signatures")]
#[serde(skip_serializing_if = "Signatures::is_empty")]
pub witness: Signatures,
}
@@ -82,10 +87,10 @@ impl PartialOrd for Proof {
pub struct P2PKConditions {
#[serde(skip_serializing_if = "Option::is_none")]
pub locktime: Option<u64>,
pub pubkeys: Vec<PublicKey>,
pub pubkeys: Vec<VerifyingKey>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub refund_keys: Vec<PublicKey>,
pub refund_keys: Vec<VerifyingKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_sigs: Option<u64>,
pub sig_flag: SigFlag,
@@ -94,8 +99,8 @@ pub struct P2PKConditions {
impl P2PKConditions {
pub fn new(
locktime: Option<u64>,
pubkeys: Vec<PublicKey>,
refund_keys: Vec<PublicKey>,
pubkeys: Vec<VerifyingKey>,
refund_keys: Vec<VerifyingKey>,
num_sigs: Option<u64>,
sig_flag: Option<SigFlag>,
) -> Result<Self, Error> {
@@ -131,7 +136,7 @@ impl TryFrom<P2PKConditions> for Secret {
return Err(Error::Amount);
}
let data = pubkeys[0].to_hex();
let data = pubkeys[0].to_string();
let mut tags = vec![];
@@ -187,14 +192,14 @@ impl TryFrom<Secret> for P2PKConditions {
})
.collect();
let mut pubkeys: Vec<PublicKey> = vec![];
let mut pubkeys: Vec<VerifyingKey> = vec![];
if let Some(Tag::PubKeys(keys)) = tags.get(&TagKind::Pubkeys) {
let mut keys = keys.clone();
pubkeys.append(&mut keys);
}
let data_pubkey = PublicKey::from_hex(secret.secret_data.data)?;
let data_pubkey = VerifyingKey::from_str(&secret.secret_data.data)?;
pubkeys.push(data_pubkey);
let locktime = if let Some(tag) = tags.get(&TagKind::Locktime) {
@@ -258,20 +263,15 @@ impl Proof {
for signature in &self.witness.signatures {
let mut pubkeys = spending_conditions.pubkeys.clone();
let data_key = PublicKey::from_str(&secret.secret_data.data).unwrap();
let data_key = VerifyingKey::from_str(&secret.secret_data.data).unwrap();
pubkeys.push(data_key);
for v in &spending_conditions.pubkeys {
let sig = Signature::try_from(hex::decode(signature).unwrap().as_slice()).unwrap();
let verifying_key: VerifyingKey = v.try_into()?;
if verifying_key.verify(&msg.to_byte_array(), &sig).is_ok() {
if v.verify(&msg.to_byte_array(), &sig).is_ok() {
valid_sigs += 1;
} else {
println!(
"{:?}",
verifying_key.verify(&msg.to_byte_array(), &sig).unwrap()
);
println!("{:?}", v.verify(&msg.to_byte_array(), &sig).unwrap());
}
}
}
@@ -288,7 +288,6 @@ impl Proof {
for v in &spending_conditions.refund_keys {
let sig = Signature::try_from(s.as_bytes())
.map_err(|_| Error::InvalidSignature)?;
let v: VerifyingKey = v.clone().try_into()?;
// As long as there is one valid refund signature it can be spent
if v.verify(&msg.to_byte_array(), &sig).is_ok() {
@@ -398,8 +397,8 @@ pub enum Tag {
SigFlag(SigFlag),
NSigs(u64),
LockTime(u64),
Refund(Vec<PublicKey>),
PubKeys(Vec<PublicKey>),
Refund(Vec<VerifyingKey>),
PubKeys(Vec<VerifyingKey>),
}
impl Tag {
@@ -445,7 +444,7 @@ where
let pubkeys = tag
.iter()
.skip(1)
.flat_map(|p| PublicKey::from_hex(p.as_ref().to_string()))
.flat_map(|p| VerifyingKey::from_str(p.as_ref()))
.collect();
Ok(Self::Refund(pubkeys))
@@ -454,7 +453,7 @@ where
let pubkeys = tag
.iter()
.skip(1)
.flat_map(|p| PublicKey::from_hex(p.as_ref().to_string()))
.flat_map(|p| VerifyingKey::from_str(p.as_ref()))
.collect();
Ok(Self::PubKeys(pubkeys))
@@ -477,7 +476,7 @@ impl From<Tag> for Vec<String> {
let mut tag = vec![TagKind::Pubkeys.to_string()];
for pubkey in pubkeys {
tag.push(pubkey.to_hex())
tag.push(pubkey.to_string())
}
tag
}
@@ -485,7 +484,7 @@ impl From<Tag> for Vec<String> {
let mut tag = vec![TagKind::Refund.to_string()];
for pubkey in pubkeys {
tag.push(pubkey.to_hex())
tag.push(pubkey.to_string())
}
tag
}
@@ -518,33 +517,174 @@ impl<'de> Deserialize<'de> for Tag {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VerifyingKey(k256::schnorr::VerifyingKey);
impl VerifyingKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
Ok(VerifyingKey(
k256::schnorr::VerifyingKey::from_bytes(bytes).unwrap(),
))
}
pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
Ok(self.0.verify(msg, signature).unwrap())
}
}
impl FromStr for VerifyingKey {
type Err = Error;
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(hex)?;
let bytes = if bytes.len().eq(&33) {
bytes.iter().skip(1).cloned().collect()
} else {
bytes.to_vec()
};
Ok(VerifyingKey(
k256::schnorr::VerifyingKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
))
}
}
impl std::fmt::Display for VerifyingKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes = self.0.to_bytes();
f.write_str(&hex::encode(bytes))
}
}
impl From<VerifyingKey> for k256::schnorr::VerifyingKey {
fn from(value: VerifyingKey) -> k256::schnorr::VerifyingKey {
value.0
}
}
impl From<&VerifyingKey> for k256::schnorr::VerifyingKey {
fn from(value: &VerifyingKey) -> k256::schnorr::VerifyingKey {
value.0
}
}
impl From<k256::schnorr::VerifyingKey> for VerifyingKey {
fn from(value: k256::schnorr::VerifyingKey) -> VerifyingKey {
VerifyingKey(value)
}
}
impl TryFrom<PublicKey> for VerifyingKey {
type Error = Error;
fn try_from(value: PublicKey) -> Result<VerifyingKey, Self::Error> {
(&value).try_into()
}
}
impl TryFrom<&PublicKey> for VerifyingKey {
type Error = Error;
fn try_from(value: &PublicKey) -> Result<VerifyingKey, Self::Error> {
let bytes = value.to_bytes();
let bytes = if bytes.len().eq(&33) {
bytes.iter().skip(1).cloned().collect()
} else {
bytes.to_vec()
};
VerifyingKey::from_bytes(&bytes).map_err(|_| Error::Key)
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SigningKey(k256::schnorr::SigningKey);
impl From<SigningKey> for k256::schnorr::SigningKey {
fn from(value: SigningKey) -> k256::schnorr::SigningKey {
value.0
}
}
impl From<k256::schnorr::SigningKey> for SigningKey {
fn from(value: k256::schnorr::SigningKey) -> Self {
Self(value)
}
}
impl From<SecretKey> for SigningKey {
fn from(value: SecretKey) -> SigningKey {
value.into()
}
}
impl SigningKey {
pub fn public_key(&self) -> VerifyingKey {
self.0.verifying_key().clone().into()
}
pub fn sign(&self, msg: &[u8]) -> Signature {
self.0.sign(msg)
}
pub fn verifying_key(&self) -> VerifyingKey {
VerifyingKey(self.0.verifying_key().clone())
}
}
impl FromStr for SigningKey {
type Err = Error;
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(hex)?;
let bytes = if bytes.len().eq(&33) {
bytes.iter().skip(1).cloned().collect()
} else {
bytes.to_vec()
};
Ok(SigningKey(
k256::schnorr::SigningKey::from_bytes(&bytes).map_err(|_| Error::Key)?,
))
}
}
impl std::fmt::Display for SigningKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes = self.0.to_bytes();
f.write_str(&hex::encode(bytes))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use crate::nuts::SecretKey;
#[test]
fn test_secret_ser() {
let conditions = P2PKConditions {
locktime: Some(99999),
pubkeys: vec![
PublicKey::from_str(
VerifyingKey::from_str(
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
)
.unwrap(),
PublicKey::from_str(
VerifyingKey::from_str(
"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
)
.unwrap(),
PublicKey::from_str(
VerifyingKey::from_str(
"023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54",
)
.unwrap(),
],
refund_keys: vec![PublicKey::from_str(
refund_keys: vec![VerifyingKey::from_str(
"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
)
.unwrap()],
@@ -572,13 +712,12 @@ mod tests {
#[test]
fn sign_proof() {
let secret_key =
SecretKey::from_hex("04918dfc36c93e7db6cc0d60f37e1522f1c36b64d3f4b424c532d7c595febbc5")
.unwrap();
let secret_key = SigningKey::from_str(
"04918dfc36c93e7db6cc0d60f37e1522f1c36b64d3f4b424c532d7c595febbc5",
)
.unwrap();
let pubkey: PublicKey = secret_key.public_key();
let v_key: VerifyingKey = pubkey.clone().try_into().unwrap();
let v_key: VerifyingKey = secret_key.verifying_key();
let conditions = P2PKConditions {
locktime: None,