|
@@ -18,11 +18,12 @@ use url::Url;
|
|
use crate::amount::SplitTarget;
|
|
use crate::amount::SplitTarget;
|
|
use crate::cdk_database::{self, WalletDatabase};
|
|
use crate::cdk_database::{self, WalletDatabase};
|
|
use crate::dhke::{construct_proofs, hash_to_curve};
|
|
use crate::dhke::{construct_proofs, hash_to_curve};
|
|
|
|
+use crate::nuts::nut00::token::Token;
|
|
use crate::nuts::{
|
|
use crate::nuts::{
|
|
nut10, nut12, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind,
|
|
nut10, nut12, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind,
|
|
MeltQuoteBolt11Response, MeltQuoteState, MintInfo, MintQuoteBolt11Response, MintQuoteState,
|
|
MeltQuoteBolt11Response, MeltQuoteState, MintInfo, MintQuoteBolt11Response, MintQuoteState,
|
|
PreMintSecrets, PreSwap, Proof, ProofState, Proofs, PublicKey, RestoreRequest, SecretKey,
|
|
PreMintSecrets, PreSwap, Proof, ProofState, Proofs, PublicKey, RestoreRequest, SecretKey,
|
|
- SigFlag, SpendingConditions, State, SwapRequest, Token,
|
|
|
|
|
|
+ SigFlag, SpendingConditions, State, SwapRequest,
|
|
};
|
|
};
|
|
use crate::types::{MeltQuote, Melted, MintQuote, ProofInfo};
|
|
use crate::types::{MeltQuote, Melted, MintQuote, ProofInfo};
|
|
use crate::url::UncheckedUrl;
|
|
use crate::url::UncheckedUrl;
|
|
@@ -891,10 +892,7 @@ impl Wallet {
|
|
.await?;
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
|
|
- Ok(
|
|
|
|
- util::proof_to_token(mint_url.clone(), send_proofs, memo, Some(unit.clone()))?
|
|
|
|
- .to_string(),
|
|
|
|
- )
|
|
|
|
|
|
+ Ok(Token::new(mint_url.clone(), send_proofs, memo, Some(unit.clone())).to_string())
|
|
}
|
|
}
|
|
|
|
|
|
/// Melt Quote
|
|
/// Melt Quote
|
|
@@ -1214,147 +1212,137 @@ impl Wallet {
|
|
Ok((condition_selected_proofs, selected_proofs))
|
|
Ok((condition_selected_proofs, selected_proofs))
|
|
}
|
|
}
|
|
|
|
|
|
- /// Receive
|
|
|
|
|
|
+ /// Receive proofs
|
|
#[instrument(skip_all)]
|
|
#[instrument(skip_all)]
|
|
- pub async fn receive(
|
|
|
|
|
|
+ pub async fn receive_proofs(
|
|
&self,
|
|
&self,
|
|
- encoded_token: &str,
|
|
|
|
|
|
+ proofs: Proofs,
|
|
amount_split_target: &SplitTarget,
|
|
amount_split_target: &SplitTarget,
|
|
p2pk_signing_keys: &[SecretKey],
|
|
p2pk_signing_keys: &[SecretKey],
|
|
preimages: &[String],
|
|
preimages: &[String],
|
|
) -> Result<Amount, Error> {
|
|
) -> Result<Amount, Error> {
|
|
- //TODO: check token is for this mint
|
|
|
|
- let token_data = Token::from_str(encoded_token)?;
|
|
|
|
-
|
|
|
|
- let unit = token_data.unit.unwrap_or_default();
|
|
|
|
-
|
|
|
|
let mut received_proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
|
|
let mut received_proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
|
|
- for token in token_data.token {
|
|
|
|
- if token.proofs.is_empty() {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ let mint_url = &self.mint_url;
|
|
|
|
+ // Add mint if it does not exist in the store
|
|
|
|
+ if self
|
|
|
|
+ .localstore
|
|
|
|
+ .get_mint(self.mint_url.clone())
|
|
|
|
+ .await?
|
|
|
|
+ .is_none()
|
|
|
|
+ {
|
|
|
|
+ self.get_mint_info().await?;
|
|
|
|
+ }
|
|
|
|
|
|
- // Add mint if it does not exist in the store
|
|
|
|
- if self
|
|
|
|
- .localstore
|
|
|
|
- .get_mint(token.mint.clone())
|
|
|
|
- .await?
|
|
|
|
- .is_none()
|
|
|
|
- {
|
|
|
|
- self.get_mint_info().await?;
|
|
|
|
- }
|
|
|
|
|
|
+ let active_keyset_id = self.active_mint_keyset().await?;
|
|
|
|
|
|
- let active_keyset_id = self.active_mint_keyset().await?;
|
|
|
|
|
|
+ let keys = self.get_keyset_keys(active_keyset_id).await?;
|
|
|
|
|
|
- let keys = self.get_keyset_keys(active_keyset_id).await?;
|
|
|
|
|
|
+ // Sum amount of all proofs
|
|
|
|
+ let amount: Amount = proofs.iter().map(|p| p.amount).sum();
|
|
|
|
|
|
- // Sum amount of all proofs
|
|
|
|
- let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
|
|
|
|
|
|
+ let mut proofs = proofs;
|
|
|
|
|
|
- let mut proofs = token.proofs;
|
|
|
|
|
|
+ let mut sig_flag = SigFlag::SigInputs;
|
|
|
|
|
|
- let mut sig_flag = SigFlag::SigInputs;
|
|
|
|
|
|
+ // Map hash of preimage to preimage
|
|
|
|
+ let hashed_to_preimage: HashMap<String, &String> = preimages
|
|
|
|
+ .iter()
|
|
|
|
+ .flat_map(|p| match hex::decode(p) {
|
|
|
|
+ Ok(hex_bytes) => Some((Sha256Hash::hash(&hex_bytes).to_string(), p)),
|
|
|
|
+ Err(_) => None,
|
|
|
|
+ })
|
|
|
|
+ .collect();
|
|
|
|
|
|
- // Map hash of preimage to preimage
|
|
|
|
- let hashed_to_preimage: HashMap<String, &String> = preimages
|
|
|
|
- .iter()
|
|
|
|
- .flat_map(|p| match hex::decode(p) {
|
|
|
|
- Ok(hex_bytes) => Some((Sha256Hash::hash(&hex_bytes).to_string(), p)),
|
|
|
|
- Err(_) => None,
|
|
|
|
- })
|
|
|
|
- .collect();
|
|
|
|
|
|
+ let p2pk_signing_keys: HashMap<XOnlyPublicKey, &SecretKey> = p2pk_signing_keys
|
|
|
|
+ .iter()
|
|
|
|
+ .map(|s| (s.x_only_public_key(&SECP256K1).0, s))
|
|
|
|
+ .collect();
|
|
|
|
|
|
- let p2pk_signing_keys: HashMap<XOnlyPublicKey, &SecretKey> = p2pk_signing_keys
|
|
|
|
- .iter()
|
|
|
|
- .map(|s| (s.x_only_public_key(&SECP256K1).0, s))
|
|
|
|
- .collect();
|
|
|
|
|
|
+ for proof in &mut proofs {
|
|
|
|
+ // Verify that proof DLEQ is valid
|
|
|
|
+ if proof.dleq.is_some() {
|
|
|
|
+ let keys = self.get_keyset_keys(proof.keyset_id).await?;
|
|
|
|
+ let key = keys.amount_key(proof.amount).ok_or(Error::UnknownKey)?;
|
|
|
|
+ proof.verify_dleq(key)?;
|
|
|
|
+ }
|
|
|
|
|
|
- for proof in &mut proofs {
|
|
|
|
- // Verify that proof DLEQ is valid
|
|
|
|
- if proof.dleq.is_some() {
|
|
|
|
- let keys = self.get_keyset_keys(proof.keyset_id).await?;
|
|
|
|
- let key = keys.amount_key(proof.amount).ok_or(Error::UnknownKey)?;
|
|
|
|
- proof.verify_dleq(key)?;
|
|
|
|
- }
|
|
|
|
|
|
+ if let Ok(secret) =
|
|
|
|
+ <crate::secret::Secret as TryInto<crate::nuts::nut10::Secret>>::try_into(
|
|
|
|
+ proof.secret.clone(),
|
|
|
|
+ )
|
|
|
|
+ {
|
|
|
|
+ let conditions: Result<Conditions, _> =
|
|
|
|
+ secret.secret_data.tags.unwrap_or_default().try_into();
|
|
|
|
+ if let Ok(conditions) = conditions {
|
|
|
|
+ let mut pubkeys = conditions.pubkeys.unwrap_or_default();
|
|
|
|
|
|
- if let Ok(secret) =
|
|
|
|
- <crate::secret::Secret as TryInto<crate::nuts::nut10::Secret>>::try_into(
|
|
|
|
- proof.secret.clone(),
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
- let conditions: Result<Conditions, _> =
|
|
|
|
- secret.secret_data.tags.unwrap_or_default().try_into();
|
|
|
|
- if let Ok(conditions) = conditions {
|
|
|
|
- let mut pubkeys = conditions.pubkeys.unwrap_or_default();
|
|
|
|
-
|
|
|
|
- match secret.kind {
|
|
|
|
- Kind::P2PK => {
|
|
|
|
- let data_key = PublicKey::from_str(&secret.secret_data.data)?;
|
|
|
|
-
|
|
|
|
- pubkeys.push(data_key);
|
|
|
|
- }
|
|
|
|
- Kind::HTLC => {
|
|
|
|
- let hashed_preimage = &secret.secret_data.data;
|
|
|
|
- let preimage = hashed_to_preimage
|
|
|
|
- .get(hashed_preimage)
|
|
|
|
- .ok_or(Error::PreimageNotProvided)?;
|
|
|
|
- proof.add_preimage(preimage.to_string());
|
|
|
|
- }
|
|
|
|
|
|
+ match secret.kind {
|
|
|
|
+ Kind::P2PK => {
|
|
|
|
+ let data_key = PublicKey::from_str(&secret.secret_data.data)?;
|
|
|
|
+
|
|
|
|
+ pubkeys.push(data_key);
|
|
}
|
|
}
|
|
- for pubkey in pubkeys {
|
|
|
|
- if let Some(signing) =
|
|
|
|
- p2pk_signing_keys.get(&pubkey.x_only_public_key())
|
|
|
|
- {
|
|
|
|
- proof.sign_p2pk(signing.to_owned().clone())?;
|
|
|
|
- }
|
|
|
|
|
|
+ Kind::HTLC => {
|
|
|
|
+ let hashed_preimage = &secret.secret_data.data;
|
|
|
|
+ let preimage = hashed_to_preimage
|
|
|
|
+ .get(hashed_preimage)
|
|
|
|
+ .ok_or(Error::PreimageNotProvided)?;
|
|
|
|
+ proof.add_preimage(preimage.to_string());
|
|
}
|
|
}
|
|
-
|
|
|
|
- if conditions.sig_flag.eq(&SigFlag::SigAll) {
|
|
|
|
- sig_flag = SigFlag::SigAll;
|
|
|
|
|
|
+ }
|
|
|
|
+ for pubkey in pubkeys {
|
|
|
|
+ if let Some(signing) = p2pk_signing_keys.get(&pubkey.x_only_public_key()) {
|
|
|
|
+ proof.sign_p2pk(signing.to_owned().clone())?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if conditions.sig_flag.eq(&SigFlag::SigAll) {
|
|
|
|
+ sig_flag = SigFlag::SigAll;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- let mut pre_swap = self
|
|
|
|
- .create_swap(Some(amount), amount_split_target, proofs, None)
|
|
|
|
- .await?;
|
|
|
|
|
|
+ let mut pre_swap = self
|
|
|
|
+ .create_swap(Some(amount), amount_split_target, proofs, None)
|
|
|
|
+ .await?;
|
|
|
|
|
|
- if sig_flag.eq(&SigFlag::SigAll) {
|
|
|
|
- for blinded_message in &mut pre_swap.swap_request.outputs {
|
|
|
|
- for signing_key in p2pk_signing_keys.values() {
|
|
|
|
- blinded_message.sign_p2pk(signing_key.to_owned().clone())?
|
|
|
|
- }
|
|
|
|
|
|
+ if sig_flag.eq(&SigFlag::SigAll) {
|
|
|
|
+ for blinded_message in &mut pre_swap.swap_request.outputs {
|
|
|
|
+ for signing_key in p2pk_signing_keys.values() {
|
|
|
|
+ blinded_message.sign_p2pk(signing_key.to_owned().clone())?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- let swap_response = self
|
|
|
|
- .client
|
|
|
|
- .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
|
|
|
|
- .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,
|
|
|
|
- )?;
|
|
|
|
- let mint_proofs = received_proofs.entry(token.mint).or_default();
|
|
|
|
|
|
+ // Proof to keep
|
|
|
|
+ let p = construct_proofs(
|
|
|
|
+ swap_response.signatures,
|
|
|
|
+ pre_swap.pre_mint_secrets.rs(),
|
|
|
|
+ pre_swap.pre_mint_secrets.secrets(),
|
|
|
|
+ &keys,
|
|
|
|
+ )?;
|
|
|
|
+ let mint_proofs = received_proofs.entry(mint_url.clone()).or_default();
|
|
|
|
|
|
- self.localstore
|
|
|
|
- .increment_keyset_counter(&active_keyset_id, p.len() as u32)
|
|
|
|
- .await?;
|
|
|
|
|
|
+ self.localstore
|
|
|
|
+ .increment_keyset_counter(&active_keyset_id, p.len() as u32)
|
|
|
|
+ .await?;
|
|
|
|
|
|
- mint_proofs.extend(p);
|
|
|
|
- }
|
|
|
|
|
|
+ mint_proofs.extend(p);
|
|
|
|
|
|
let mut total_amount = Amount::ZERO;
|
|
let mut total_amount = Amount::ZERO;
|
|
for (mint, proofs) in received_proofs {
|
|
for (mint, proofs) in received_proofs {
|
|
total_amount += proofs.iter().map(|p| p.amount).sum();
|
|
total_amount += proofs.iter().map(|p| p.amount).sum();
|
|
let proofs = proofs
|
|
let proofs = proofs
|
|
.into_iter()
|
|
.into_iter()
|
|
- .flat_map(|proof| ProofInfo::new(proof, mint.clone(), State::Unspent, unit.clone()))
|
|
|
|
|
|
+ .flat_map(|proof| {
|
|
|
|
+ ProofInfo::new(proof, mint.clone(), State::Unspent, self.unit.clone())
|
|
|
|
+ })
|
|
.collect();
|
|
.collect();
|
|
self.localstore.add_proofs(proofs).await?;
|
|
self.localstore.add_proofs(proofs).await?;
|
|
}
|
|
}
|
|
@@ -1362,6 +1350,41 @@ impl Wallet {
|
|
Ok(total_amount)
|
|
Ok(total_amount)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// Receive
|
|
|
|
+ #[instrument(skip_all)]
|
|
|
|
+ pub async fn receive(
|
|
|
|
+ &self,
|
|
|
|
+ encoded_token: &str,
|
|
|
|
+ amount_split_target: &SplitTarget,
|
|
|
|
+ p2pk_signing_keys: &[SecretKey],
|
|
|
|
+ preimages: &[String],
|
|
|
|
+ ) -> Result<Amount, Error> {
|
|
|
|
+ let token_data = Token::from_str(encoded_token)?;
|
|
|
|
+
|
|
|
|
+ let unit = token_data.unit().clone().unwrap_or_default();
|
|
|
|
+
|
|
|
|
+ if unit != self.unit {
|
|
|
|
+ return Err(Error::UnitNotSupported);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let proofs = token_data.proofs();
|
|
|
|
+ if proofs.len() != 1 {
|
|
|
|
+ return Err(Error::MultiMintTokenNotSupported);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let (mint_url, proofs) = proofs.into_iter().next().expect("Token has proofs");
|
|
|
|
+
|
|
|
|
+ if self.mint_url != mint_url {
|
|
|
|
+ return Err(Error::IncorrectMint);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let amount = self
|
|
|
|
+ .receive_proofs(proofs, amount_split_target, p2pk_signing_keys, preimages)
|
|
|
|
+ .await?;
|
|
|
|
+
|
|
|
|
+ Ok(amount)
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Restore
|
|
/// Restore
|
|
#[instrument(skip(self))]
|
|
#[instrument(skip(self))]
|
|
pub async fn restore(&self) -> Result<Amount, Error> {
|
|
pub async fn restore(&self) -> Result<Amount, Error> {
|
|
@@ -1526,14 +1549,14 @@ impl Wallet {
|
|
));
|
|
));
|
|
}
|
|
}
|
|
|
|
|
|
- for mint_proof in &token.token {
|
|
|
|
- if mint_proof.mint != self.mint_url {
|
|
|
|
|
|
+ for (mint_url, proofs) in &token.proofs() {
|
|
|
|
+ if mint_url != &self.mint_url {
|
|
return Err(Error::IncorrectWallet(format!(
|
|
return Err(Error::IncorrectWallet(format!(
|
|
"Should be {} not {}",
|
|
"Should be {} not {}",
|
|
- self.mint_url, mint_proof.mint
|
|
|
|
|
|
+ self.mint_url, mint_url
|
|
)));
|
|
)));
|
|
}
|
|
}
|
|
- for proof in &mint_proof.proofs {
|
|
|
|
|
|
+ for proof in proofs {
|
|
let secret: nut10::Secret = (&proof.secret).try_into()?;
|
|
let secret: nut10::Secret = (&proof.secret).try_into()?;
|
|
|
|
|
|
let proof_conditions: SpendingConditions = secret.try_into()?;
|
|
let proof_conditions: SpendingConditions = secret.try_into()?;
|
|
@@ -1618,14 +1641,14 @@ impl Wallet {
|
|
pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
|
|
pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
|
|
let mut keys_cache: HashMap<Id, Keys> = HashMap::new();
|
|
let mut keys_cache: HashMap<Id, Keys> = HashMap::new();
|
|
|
|
|
|
- for mint_proof in &token.token {
|
|
|
|
- if mint_proof.mint != self.mint_url {
|
|
|
|
|
|
+ for (mint_url, proofs) in &token.proofs() {
|
|
|
|
+ if mint_url != &self.mint_url {
|
|
return Err(Error::IncorrectWallet(format!(
|
|
return Err(Error::IncorrectWallet(format!(
|
|
"Should be {} not {}",
|
|
"Should be {} not {}",
|
|
- self.mint_url, mint_proof.mint
|
|
|
|
|
|
+ self.mint_url, mint_url
|
|
)));
|
|
)));
|
|
}
|
|
}
|
|
- for proof in &mint_proof.proofs {
|
|
|
|
|
|
+ for proof in proofs {
|
|
let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
|
|
let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
|
|
Some(keys) => keys.amount_key(proof.amount),
|
|
Some(keys) => keys.amount_key(proof.amount),
|
|
None => {
|
|
None => {
|