Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KVAC Mint and Wallet #619

Draft
wants to merge 91 commits into
base: main
Choose a base branch
from
Draft

KVAC Mint and Wallet #619

wants to merge 91 commits into from

Conversation

lollerfirst
Copy link
Contributor

@lollerfirst lollerfirst commented Feb 27, 2025

Description

This PR adds basic support for KVAC (amountless ecash). Uses cashu-kvac to verify/create ZKPs and algebraic MACs. The protocol explanation with all the definitions is given here.


TL;DR

Amount

Amounts are encoded into Pedersen Commitments ( amount * G + blinding_factor * H).

Request Inputs and Outputs

  • Every request has inputs and outputs. To get initial inputs wallets do bootstrap requests to get a bunch of coins of 0 value for free.

  • Outputs length to each mint/melt/swap request are fixed to 2, while inputs length might be >= 2. This is done in order to enhance privacy AND reduce the workload (Range Proofs are beefy).

MACs

  • The MAC is equivalent to the BlindSignature. It's the authentication stamp generated by the Mint.
  • When the wallet receives outputs from a mint/melt/swap request, it does not "unblind" them. Rather they are re-blinded (randomized) when they need to be presented as inputs for a successive request.
  • The randomization is deterministic and dependant on the amount blinding factor of the coins (r, but there is also a blinding factor for the script commitment)

Script Attributes

  • Script Attributes and Commitments will be used for encoding spending conditions, but are not supported for now.

What the hell are "Nullifiers"?

Normal ecash uses the Y = HashToG(secret) to identify a coin. That is called a nullifier.
In here we use the Randomized Amount Commitment (Ca), which is just the amount commitment after it's been
randomized. In the code it's often called just "nullifier" or "nullifiers"


Endpoints

Endpoints for checking and requesting mint/melt quotes are in common with normal ecash.
Other endpoints are:

  • v2/kvac:
    • swap: swap request
    • mint: mint request
    • melt: melt request
    • bootstrap: bootstrap request
    • checkstate: checking the state of some nullifiers
    • restore: restore coins from their tags (if generated deterministically)

Run The Example

Run with cargo run --example kvac-wallet

[TBC...]

swagger = ["dep:utoipa"]
mint = ["dep:uuid"]
wallet = []
bench = []
kvac = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To include the KVAC dependency cashu_kvac only when the kvac feature is enabled, you'd have to do two things:

First, specify the kvac feature should pull in the dependency:

Suggested change
kvac = []
kvac = [dep:cashu_kvac]

Second, add an optional = true to the cashu_kvac dependency below, similar for example to utoipa. This will mean the dependency should not be included, unless an activated feature explicitly needs it.

/// Kvac Coin
///
/// A KVAC coin as intended to be saved in the wallet.
#[cfg(feature = "wallet")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it will be easier to maintain if the mint-specific structs and traits would be placed in a mint.rs submodule (for example crates/cashu/src/nuts/kvac/mint.rs) and the wallet-specific stuff in wallet.rs right next to it. Then you wouldn't need to specify #[cfg(feature = "wallet")] for each struct, but you could directly apply the feature flag to the submodule import, like for example here:

#[cfg(feature = "mint")]
pub mod mint;
#[cfg(feature = "wallet")]
pub mod wallet;

Comment on lines 63 to 66
let hex_of_hash = hex::encode(hash.to_byte_array());
Id {
version: KeySetVersion::Version00,
id: hex::decode(&hex_of_hash[0..14])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's hex::encode then hex::decode a few lines below. Wouldn't it be simpler to directly use the original hash.to_byte_array()?

data.extend(kvac_keys.0.I.to_bytes());
let hash = Sha256::hash(&data);
let hex_of_hash = hex::encode(hash.to_byte_array());
Id {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of constructing the Id by specifying the inner fields from the kvac.rs caller, I would suggest to instead:

  • add an Id::new_v0_from_hash(h: Hash) in nut02.rs , which would internally take the first 7 bytes and construct a v0 Id
  • keep the inner fields of Id private

kvac.rs could then call Id::new_v0_from_hash(..), which would make it simpler and more readable.

Another benefit is that other future callers could make use of new_v0_from_hash too.

active BOOL NOT NULL,
input_fee_ppk INTEGER NOT NULL,
counter INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(mint_url) REFERENCES mint(mint_url) ON UPDATE CASCADE ON DELETE CASCADE
Copy link
Contributor

@ok300 ok300 Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to treat the mint_url as foreign key, linking the kvac_keyset and mint tables and propagating a mint row deletion here as well.

I haven't thought it through, but at first sight this is tricky and probably incorrect because:

  • the same mint (and therefore each of its keysets) can have multiple mint_urls
  • deleting a mint_url (for example of a mint with multiple URLs) doesn't necessarily mean the caller wants to delete the keyset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just basically copied the SQL for the other "normal" keysets because they are pretty much the same

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants