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

Cashu v1 – The Great Cleanup #55

Merged
merged 47 commits into from Dec 18, 2023
Merged

Cashu v1 – The Great Cleanup #55

merged 47 commits into from Dec 18, 2023

Conversation

callebtc
Copy link
Contributor

@callebtc callebtc commented Oct 3, 2023

The Great Cleanup

X

This is a PR that encapsulates a collection of changes to the protocol that will make our lives easier in the future. It cleans up early mistakes that were made as the protocol grew organically and removes implicit assumptions about the funding sources (e.g. Lightning) and currency units (e.g. "sats") used for Cashu mints that are inherent in the current protocol.

New v1 API

We are introducing the v1 API where we clean up many input and output models.

Click to show new endpoints

New endpoints

  • New: GET /v1/keys and GET /v1/keys/{keyset_id} (new Keyset model)
  • New: GET /v1/keysets (new Keysets model)
  • New: POST /v1/swap (new BlindedMessage model)
  • New: POST /v1/melt/quote/bolt11 (replaces GET /checkfees)
  • New: POST /v1/melt/bolt11
  • New: POST /v1/mint/quote/bolt11 (replaces GET /melt)
  • New: POST /v1/mint/bolt11
  • New: POST /v1/check (no changes)
  • New: GET /v1/info (no changes)
  • New: POST /v1/restore (new GetInfoResponse)
Click to show error response

Error responses

Error responses of the mint are now defined as a HTTP 400 response with a JSON body

{
  "detail": "oops",
  "code": 1337
}
Click to show deprecated endpoints

Deprecated endpoints

  • GET /keys, GET /keys/{keyset_id}
  • GET /keysets
  • POST /split
  • GET /mint
  • POST /mint
  • GET /checkfees
  • POST /melt
  • POST /check
  • GET /info
  • POST /restore

NUTs

  • The GET /checkfees endpoint (formerly NUT-03) is now retired
  • The POST /v1/swap endpoint is now in NUT-03 (formerly POST /split NUT-06).
  • NUT-06 is therefore free (suggestion: let's use it for deterministic secrets)

New request and response models

Click to show all changed models

NUT-01

GetKeysResponse

{
  "keysets": [
    {
      "id": <keyset_id_hex_str>,
      "unit": <currency_unit_str>,
      "keys": {
        <amount_int>: <public_key_str>,
        ...
      }
    }
  ]
}

NUT-02

GetKeysetsResponse

{
  "keysets": [
    {
      "id": <keyset_id_hex_str>,
      "unit": <currency_unit_str>,
      "active": <bool>
    },
    ...
  ]
}

NUT-03

PostSwapRequest

{
  "inputs": <Proofs>, <-- RENAMED
  "outputs": <BlindedMessages>,
}

PostSwapResponse

{
  "signatures": <BlindedSignatures> <-- RENAMED
}

NUT-04

PostMintQuoteBolt11Request

{
  "amount": <int>,
  "unit": <str_enum["sat"]>
}

PostMintQuoteBolt11Response

{
  "quote": <str>,
  "request": <str>,
  "paid": <bool>,
  "expiry": <int>
}

PostMintBolt11Request

{
  "quote": <str>,
  "outputs": <Array[BlindedMessage]>
}

PostMintBolt11Response

{
  "signatures": <Array[BlindSignature]> <-- RENAMED
}

NUT-05

PostMeltQuoteBolt11Request

{
  "request": <str>,
  "unit": <str_enum["sat"]>
}

PostMeltQuoteBolt11Response

{
  "quote": <str>,
  "amount": <int>,
  "fee_reserve": <int>,
  "paid": <bool>,
  "expiry": <int>
}

PostMeltBolt11Request

{
  "quote": <str>,
  "inputs": <Array[Proof]>
}

PostMeltBolt11Response

{
  "paid": <bool>,
  "payment_preimage": <str>
}

Quotes

We are also introducing quotes, a general way to register a mint and melt transaction with the mint that will work across different payment methods (bolt11, bolt12, on-chain, ...) and different currency units (sat, msat, usd, ...). Quotes add the ability for Lightning backends to decide the amount and currency of the ecash they need in order to receive or pay a Lightning payment. The flow remains very similar to before and will be illustrated with two examples in the following.

Mint Quotes

To mint ecash (output) via Lightning (input), the wallet first requests a MintQuote for a given amount of sats the wallet wants to mint. The MintQuote has an quote id and includes a Lightning invoice. The user pays the Lightning invoice and then calls /v1/mint referencing the previous quote it corresponds to.

Melt Quotes

To melt ecash (input) and make a Lightning payment (output), the wallet requests a MeltQuote for a given Lightning invoice it likes to pay. In the MeltQuote, the mint tells the wallet how many sats it needs to supply and what the fee reserve is in order for the mint to fulfill this request.

Diagram

image

Hexadecimal keyset IDs

Our keyset IDs are ugly and need special treatment for HTTP (base64 urlsafe). We switch to hexadecimal keyset IDs that are generated much like the previous ones. We also add a version byte as a prefix.

1 - sort public keys by their amount in ascending order
2 - concatenate all public keys to one string
3 - HASH_SHA256 the concatenated public keys
4 - take the first 16 characters of the hex-encoded hash
5 - prefix it with a keyset ID version byte

An example implementation in Python:

def derive_keyset_id(keys: Dict[int, PublicKey]) -> str:
	"""Deterministic derivation keyset_id from set of public keys."""
	sorted_keys = dict(sorted(keys.items()))
	pubkeys_concat = "".join([p.serialize().hex() for _, p in sorted_keys.items()])
	return "00" + hashlib.sha256(pubkeys_concat.encode("utf-8")).hexdigest()[:14]

BlindedMessage now has keyset id field

Outputs (BlindedMessages), now also have a keyset id field, like Proofs (inputs) and BlindSignatures do. With the id, the wallet tells the mint which keyset the client is expecting a signature from during a /v1/swap (created outputs), /v1/melt (change outputs), or /v1/mint (minted outputs). The requested id MUST be from an active keyset (part of the /v1/keys response and the /v1/keysets response). If the wallet uses an id that is not existent or not active (rotated-out of), the mint MUST refuse the transaction.

The BlindedMessage becomes

{
  "amount": int,
  "id": str # <-- NEW!
  "B_": hex_str
}
Click to show additional remarks

Additional remarks

Adding an id fixes a race condition we previously created workarounds for (see cashubtc/cashu-ts#64 for example). Essentially, the mint's keys could have rotated between the wallet sending the outputs to sign to the mint, and the mint responding with a signature. We can now get rid of this code by making it part of the protocol.

Another critical issue that results from the same race condition is deterministic secret derivation: If a wallet deterministically derives secrets for keyset A, sends BlindedMessages to the mint and the mint rotated keys in the mean time, it would respond with BlindedSignatures keyset B. That means the wallet has incremented its deterministic secret derivation counter on the wrong keyset ID.

Standardized secrets

Wallets should use standardized secrets (32 bytes of randomness) in lowercase hex. Closes #54

01.md Outdated Show resolved Hide resolved
02.md Outdated Show resolved Hide resolved
00.md Outdated Show resolved Hide resolved
03.md Outdated Show resolved Hide resolved
Copy link
Collaborator

@thunderbiscuit thunderbiscuit left a comment

Choose a reason for hiding this comment

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

A few overall comments:

  • The PR description states that the new v1 routes are to be used and the old ones are deprecated. Not sure how to handle that elegantly in the NUTs. Should examples for both be given? Or all examples be migrated to the v1? If so, most of those are not yet updated.
  • Breaking changes means we should update the test vectors
  • How should tokens handle v1? Are there corner cases there that could create issues? What if you bundle a token with a ton of inputs, some v1 and some not? Haven't thought about this deeply, just writing down thoughts as they come to mind...

Overall some great improvements in there (also lots of new code to write for us! 😅). I suggest that if there is intention to eventually move forward with a solution to #54, it should be included here as part of this fairly wide-ranging set of breaking changes.

00.md Outdated Show resolved Hide resolved
00.md Outdated Show resolved Hide resolved
01.md Outdated Show resolved Hide resolved
01.md Show resolved Hide resolved
02.md Outdated Show resolved Hide resolved
03.md Show resolved Hide resolved
03.md Outdated Show resolved Hide resolved
03.md Outdated Show resolved Hide resolved
04.md Outdated Show resolved Hide resolved
04.md Outdated Show resolved Hide resolved
@thunderbiscuit
Copy link
Collaborator

thunderbiscuit commented Oct 27, 2023

Great discussions over the dev call the other day. I figured I'd add a small todo list of things that might need to be addressed here before I forget. Note that not all of those might be required, and I'm going off of memory so hopefully not missing anything.

  • Add note on standardized secrets (32 bytes of randomness), most likely transmitted as lowercase hex and why wallet should strive to follow the standard. Closes Uniform secret structure would prevent wallet fingerprinting #54
  • Make sure the /info/ endpoint is future-proof and potentially adds messaging around breaking changes and supported features
  • Consider the idea of using different endpoints for different units (additional NUTs for additional units would define new endpoints). If so, decide how to handle the default bolt11 mint/ and melt/ endpoints and their definition in the NUTs (are they good as is or do they now require their own spec file?)
  • Add unit field to Proof object, requiring a new token format version (cashuB)
  • Update all examples to the v1 routes
  • Address comments from reviewers above
  • Potentially add errors enum with codes

02.md Show resolved Hide resolved
04.md Outdated Show resolved Hide resolved
@callebtc callebtc changed the title The great cleanup wishlist Cashu v1 API – The Great Cleanup Dec 7, 2023
@callebtc callebtc changed the title Cashu v1 API – The Great Cleanup Cashu v1 – The Great Cleanup Dec 7, 2023
Copy link
Collaborator

@thunderbiscuit thunderbiscuit left a comment

Choose a reason for hiding this comment

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

ACK e59f284.

Major improvement of the protocol. I'm happy to see that we have a few implementations that were able to put this into code already, and looking forward to seeing what gets built on top of this. 🚀

@thunderbiscuit
Copy link
Collaborator

One little thing: the test vectors should be updated (if not in this PR in a very close follow-up PR).

Copy link
Collaborator

@thesimplekid thesimplekid left a comment

Choose a reason for hiding this comment

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

Looks good to me. Agree with tb that it would be best to include test vectors as it helps to ensure implementations are correct.

ACK e59f284

00.md Show resolved Hide resolved
01.md Outdated Show resolved Hide resolved
05.md Show resolved Hide resolved
05.md Show resolved Hide resolved
08.md Show resolved Hide resolved
Copy link
Collaborator

@ngutech21 ngutech21 left a comment

Choose a reason for hiding this comment

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

ACK e1568fd

This is a great improvement of the cashu protocol.

00.md Show resolved Hide resolved
07.md Show resolved Hide resolved
06.md Show resolved Hide resolved
@callebtc callebtc merged commit 578bf3e into main Dec 18, 2023
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.

Uniform secret structure would prevent wallet fingerprinting
6 participants