mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 13:16:00 +01:00
246 lines
7.7 KiB
Rust
246 lines
7.7 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
|
|
use crate::dhke::sign_message;
|
|
use crate::dhke::verify_message;
|
|
use crate::error::mint::Error;
|
|
use crate::nuts::nut00::BlindedMessage;
|
|
use crate::nuts::nut00::BlindedSignature;
|
|
use crate::nuts::nut00::Proof;
|
|
use crate::nuts::nut06::SplitRequest;
|
|
use crate::nuts::nut06::SplitResponse;
|
|
use crate::nuts::nut07::CheckSpendableRequest;
|
|
use crate::nuts::nut07::CheckSpendableResponse;
|
|
use crate::nuts::nut08::MeltRequest;
|
|
use crate::nuts::nut08::MeltResponse;
|
|
use crate::nuts::*;
|
|
use crate::Amount;
|
|
|
|
pub struct Mint {
|
|
// pub pubkey: PublicKey,
|
|
pub active_keyset: nut02::mint::KeySet,
|
|
pub inactive_keysets: HashMap<String, nut02::mint::KeySet>,
|
|
pub spent_secrets: HashSet<String>,
|
|
pub pending_secrets: HashSet<String>,
|
|
}
|
|
|
|
impl Mint {
|
|
pub fn new(
|
|
secret: &str,
|
|
derivation_path: &str,
|
|
inactive_keysets: HashMap<String, nut02::mint::KeySet>,
|
|
spent_secrets: HashSet<String>,
|
|
max_order: u8,
|
|
) -> Self {
|
|
Self {
|
|
active_keyset: nut02::mint::KeySet::generate(secret, derivation_path, max_order),
|
|
inactive_keysets,
|
|
spent_secrets,
|
|
pending_secrets: HashSet::new(),
|
|
}
|
|
}
|
|
|
|
/// Retrieve the public keys of the active keyset for distribution to
|
|
/// wallet clients
|
|
pub fn active_keyset_pubkeys(&self) -> nut02::KeySet {
|
|
nut02::KeySet::from(self.active_keyset.clone())
|
|
}
|
|
|
|
/// Return a list of all supported keysets
|
|
pub fn keysets(&self) -> nut02::Response {
|
|
let mut keysets: HashSet<_> = self.inactive_keysets.keys().cloned().collect();
|
|
keysets.insert(self.active_keyset.id.clone());
|
|
nut02::Response { keysets }
|
|
}
|
|
|
|
pub fn active_keyset(&self) -> nut02::mint::KeySet {
|
|
self.active_keyset.clone()
|
|
}
|
|
|
|
pub fn keyset(&self, id: &str) -> Option<nut02::KeySet> {
|
|
if self.active_keyset.id == id {
|
|
return Some(self.active_keyset.clone().into());
|
|
}
|
|
|
|
self.inactive_keysets.get(id).map(|k| k.clone().into())
|
|
}
|
|
|
|
pub fn process_mint_request(
|
|
&mut self,
|
|
mint_request: nut04::MintRequest,
|
|
) -> Result<nut04::PostMintResponse, Error> {
|
|
let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
|
|
|
|
for blinded_message in mint_request.outputs {
|
|
blind_signatures.push(self.blind_sign(&blinded_message)?);
|
|
}
|
|
|
|
Ok(nut04::PostMintResponse {
|
|
promises: blind_signatures,
|
|
})
|
|
}
|
|
|
|
fn blind_sign(&self, blinded_message: &BlindedMessage) -> Result<BlindedSignature, Error> {
|
|
let BlindedMessage { amount, b } = blinded_message;
|
|
|
|
let Some(key_pair) = self.active_keyset.keys.0.get(&amount.to_sat()) else {
|
|
// No key for amount
|
|
return Err(Error::AmountKey);
|
|
};
|
|
|
|
let c = sign_message(key_pair.secret_key.clone(), b.clone().into())?;
|
|
|
|
Ok(BlindedSignature {
|
|
amount: *amount,
|
|
c: c.into(),
|
|
id: self.active_keyset.id.clone(),
|
|
})
|
|
}
|
|
|
|
pub fn process_split_request(
|
|
&mut self,
|
|
split_request: SplitRequest,
|
|
) -> Result<SplitResponse, Error> {
|
|
let proofs_total = split_request.proofs_amount();
|
|
|
|
let output_total = split_request.output_amount();
|
|
|
|
if proofs_total != output_total {
|
|
return Err(Error::Amount);
|
|
}
|
|
|
|
let mut secrets = Vec::with_capacity(split_request.proofs.len());
|
|
for proof in &split_request.proofs {
|
|
secrets.push(self.verify_proof(&proof)?);
|
|
self.spent_secrets.insert(proof.secret.clone());
|
|
}
|
|
|
|
match &split_request.amount {
|
|
None => {
|
|
let promises: Vec<BlindedSignature> = split_request
|
|
.outputs
|
|
.iter()
|
|
.map(|b| self.blind_sign(b).unwrap())
|
|
.collect();
|
|
|
|
Ok(SplitResponse::new(promises))
|
|
}
|
|
Some(amount) => {
|
|
let outs_fst = (proofs_total.to_owned() - amount.to_owned()).split();
|
|
|
|
// Blinded change messages
|
|
let b_fst = split_request.outputs[0..outs_fst.len()].to_vec();
|
|
let b_snd = split_request.outputs[outs_fst.len()..].to_vec();
|
|
let fst: Vec<BlindedSignature> =
|
|
b_fst.iter().map(|b| self.blind_sign(b).unwrap()).collect();
|
|
let snd: Vec<BlindedSignature> =
|
|
b_snd.iter().map(|b| self.blind_sign(b).unwrap()).collect();
|
|
|
|
let split_response = SplitResponse::new_from_amount(fst, snd);
|
|
|
|
if split_response.target_amount() != split_request.amount {
|
|
return Err(Error::CustomError("Output order".to_string()));
|
|
}
|
|
|
|
Ok(split_response)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn verify_proof(&self, proof: &Proof) -> Result<String, Error> {
|
|
if self.spent_secrets.contains(&proof.secret) {
|
|
return Err(Error::TokenSpent);
|
|
}
|
|
|
|
let keyset = proof.id.as_ref().map_or_else(
|
|
|| &self.active_keyset,
|
|
|id| {
|
|
if let Some(keyset) = self.inactive_keysets.get(id) {
|
|
keyset
|
|
} else {
|
|
&self.active_keyset
|
|
}
|
|
},
|
|
);
|
|
|
|
let Some(keypair) = keyset.keys.0.get(&proof.amount.to_sat()) else {
|
|
return Err(Error::AmountKey);
|
|
};
|
|
|
|
verify_message(
|
|
keypair.secret_key.to_owned(),
|
|
proof.c.clone().into(),
|
|
&proof.secret,
|
|
)?;
|
|
|
|
Ok(proof.secret.clone())
|
|
}
|
|
|
|
pub fn check_spendable(
|
|
&self,
|
|
check_spendable: &CheckSpendableRequest,
|
|
) -> Result<CheckSpendableResponse, Error> {
|
|
let mut spendable = Vec::with_capacity(check_spendable.proofs.len());
|
|
let mut pending = Vec::with_capacity(check_spendable.proofs.len());
|
|
|
|
for proof in &check_spendable.proofs {
|
|
spendable.push(!self.spent_secrets.contains(&proof.secret));
|
|
pending.push(!self.pending_secrets.contains(&proof.secret));
|
|
}
|
|
|
|
Ok(CheckSpendableResponse { spendable, pending })
|
|
}
|
|
|
|
pub fn verify_melt_request(&mut self, melt_request: &MeltRequest) -> Result<(), Error> {
|
|
let proofs_total = melt_request.proofs_amount();
|
|
|
|
// TODO: Fee reserve
|
|
if proofs_total
|
|
< melt_request
|
|
.invoice_amount()
|
|
.map_err(|_| Error::InvoiceAmountUndefined)?
|
|
{
|
|
return Err(Error::Amount);
|
|
}
|
|
|
|
let mut secrets = Vec::with_capacity(melt_request.proofs.len());
|
|
for proof in &melt_request.proofs {
|
|
secrets.push(self.verify_proof(proof)?);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn process_melt_request(
|
|
&mut self,
|
|
melt_request: &MeltRequest,
|
|
preimage: &str,
|
|
total_spent: Amount,
|
|
) -> Result<MeltResponse, Error> {
|
|
let secrets = Vec::with_capacity(melt_request.proofs.len());
|
|
for secret in secrets {
|
|
self.spent_secrets.insert(secret);
|
|
}
|
|
|
|
let change_target = melt_request.proofs_amount() - total_spent;
|
|
let amounts = change_target.split();
|
|
let mut change = Vec::with_capacity(amounts.len());
|
|
|
|
if let Some(outputs) = &melt_request.outputs {
|
|
for (i, amount) in amounts.iter().enumerate() {
|
|
let mut message = outputs[i].clone();
|
|
|
|
message.amount = *amount;
|
|
|
|
let signature = self.blind_sign(&message)?;
|
|
change.push(signature)
|
|
}
|
|
}
|
|
|
|
Ok(MeltResponse {
|
|
paid: true,
|
|
preimage: Some(preimage.to_string()),
|
|
change: Some(change),
|
|
})
|
|
}
|
|
}
|