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

Support for NUT-19 mint and wallet side #670

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

Conversation

lollerfirst
Copy link
Contributor

@lollerfirst lollerfirst commented Nov 15, 2024

Adding support for NUT-19.
Main changes:

Common:

  • MintQuote has two more optional fields named privkey and pubkey
  • nut19.py contains construct_message, sign_mint_quote and verify_mint_quote
  • PostMintQuoteRequest has one more optional field named pubkey
  • PostMintRequest has one more optional field named pubkey

Mint:

  • If a PostMintQuoteRequest contains a pubkey, save the public key together with the quote and
    require a BIP340 signature from said public key on the mint request's quote id and blind messages.
  • If a PostMintQuoteRequest does not contain a pubkey while Mint is configured to require it, reject the request.
  • Signal support for NUT-19 and requirement for NUT-19

Wallet:

  • When requesting a quote -if the Mint supports NUT19- generate an ephemeral keypair and include pubkey in the PostMintRequest.
  • Include privkey in the quote when saving it to DB.
  • When minting always pull the quote from DB and if the quote has a non-null key, sign the quote id and the blinded message with key

cashu/core/base.py Outdated Show resolved Hide resolved
cashu/core/base.py Outdated Show resolved Hide resolved
cashu/core/base.py Outdated Show resolved Hide resolved
cashu/mint/crud.py Outdated Show resolved Hide resolved
cashu/mint/crud.py Outdated Show resolved Hide resolved
cashu/mint/ledger.py Outdated Show resolved Hide resolved
@@ -518,13 +520,15 @@ async def mint(
*,
outputs: List[BlindedMessage],
quote_id: str,
witness: Optional[str] = None,
) -> List[BlindedSignature]:
"""Mints new coins if quote with `quote_id` was paid. Ingest blind messages `outputs` and returns blind signatures `promises`.

Args:
outputs (List[BlindedMessage]): Outputs (blinded messages) to sign.
quote_id (str): Mint quote id.
keyset (Optional[MintKeyset], optional): Keyset to use. If not provided, uses active keyset. Defaults to None.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
keyset (Optional[MintKeyset], optional): Keyset to use. If not provided, uses active keyset. Defaults to None.

outdated

cashu/mint/ledger.py Outdated Show resolved Hide resolved
await conn.execute(
f"""
ALTER TABLE {db.table_with_schema("mint_quotes")}
ADD COLUMN key TEXT DEFAULT NULL
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here (key -> pubkey)



def construct_message(quote_id: str, outputs: List[BlindedMessage]) -> bytes:
serialized_outputs = bytes.fromhex("".join([o.B_ for o in outputs]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
serialized_outputs = bytes.fromhex("".join([o.B_ for o in outputs]))
serialized_outputs = bytes.fromhex("".join([o.B_ for o in outputs]))

This should be utf-8 encoded?

cashu/wallet/cli/cli.py Outdated Show resolved Hide resolved
cashu/wallet/wallet.py Outdated Show resolved Hide resolved
cashu/wallet/wallet.py Outdated Show resolved Hide resolved
@callebtc
Copy link
Collaborator

Nice work. One issue here is that key is confusing and it seems like you're storing pubkey as well as privatekey in the same field, depending on whether it is the mint or the wallet.

@callebtc
Copy link
Collaborator

I think we should move the file core/crypto/nut19.py to a new directory core/nuts and clean up nut-specific stuff in the future to this path.

@lollerfirst
Copy link
Contributor Author

Nice work. One issue here is that key is confusing and it seems like you're storing pubkey as well as privatekey in the same field, depending on whether it is the mint or the wallet.

yea that was the idea. The alternative is keeping the private key separate but still storing it together with the quote in the DB. And then when we request the quote from DB also return the separate private key.

@@ -261,6 +261,7 @@ async def request_mint_deprecated(self, amount) -> PostMintQuoteResponse:
paid=False,
state=MintQuoteState.unpaid.value,
expiry=decoded_invoice.date + (decoded_invoice.expiry or 0),
pubkey=None
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
pubkey=None

Optional parameter doesn't need definition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mypy was complaining about this because it's defined as optional but doesn't have a default value.

Comment on lines 549 to 550
privkey = PrivateKey(bytes.fromhex(quote_key), raw=True)
witness = nut19.sign_mint_quote(quote_id, outputs, privkey)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would move the PrivateKey instantiation into the nut19 submodule

@@ -424,13 +429,29 @@ async def request_mint(self, amount: int, memo: Optional[str] = None) -> MintQuo
Returns:
MintQuote: Mint Quote
"""
mint_quote_response = await super().mint_quote(amount, self.unit, memo)
privkey, pubkey = await self.get_quote_ephemeral_keypair()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we move get_quote_ephemeral_keypair also to nut19.py?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So if we later want to add derivation from seed+counter functionality we might want to keep it inside the wallet class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general depends on the state of the wallet so...

@callebtc
Copy link
Collaborator

The alternative is keeping the private key separate but still storing it together with the quote in the DB.

I think it should have two separate columns privatekey for the wallet and pubkey for the mint, so it's as explicit as possible.

@lollerfirst
Copy link
Contributor Author

I moved nuts.py to the nuts directory as well because I was getting some import errors.

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