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

NUT-10: Spending conditions #50

Merged
merged 5 commits into from
Oct 13, 2023
Merged

NUT-10: Spending conditions #50

merged 5 commits into from
Oct 13, 2023

Conversation

callebtc
Copy link
Contributor

@callebtc callebtc commented Sep 19, 2023

This is a NUT describing the structure of the well-known Secret object that can encode more complex spending conditions. This was previously part of #40 but has been refactored into two separate NUTs, the latter only describing P2PK.

@callebtc callebtc changed the title Nut10: spending conditions NUT-10: Spending conditions Sep 19, 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.

Slowly taking a look at this. Don't take this as a final comment but rather an opening of my round of questions 😆. Also I like the split between the NUT-10 and other nuts specifying different kinds of standard secrets.

So my first question is this: is there a reason to need all proofs to be of the same kind (line 28)? If there is then let's add that information here. If not, then it might be a good thing to remove that requirement. I was thinking of someone who needs to pay 100 sat but only has a 64 sat token P2PK and a 50 sat "normal" token. As is, they would not be able to make that payment in one go, and would need to redeem the P2PK token first for a normal token, and then combine that normal token with the other one. Feels like an extra round trip we might be able to remove? Let me know if I'm missing something.

@thunderbiscuit
Copy link
Collaborator

thunderbiscuit commented Sep 20, 2023

Just making sure I have the right mental model

The way I think of secrets after reading this and #40 is basically the following: secrets can be of different types, which could be conceptualized as variants on an enum. For now this NUT simply implies the existence of this enum and of potential new enum variants. Something like:

enum class Secret {
    SIMPLE
}

NUT-11 basically adds the P2PK type, which means a mint that implements it might think of its accepted secrets like so:

enum class Secret {
    SIMPLE,
    P2PK,
}

Under this workflow, a mint would attempt to parse a secret into one of its accepted types (are there potential attacks there? you'd need to make sure that any given secret can only be parsed as one of those types, excluding the Simple type), and if it cannot deserialize the string into any of its "special" types, then interpret the secret at being of type "simple" or whatever you'd call it. Am I roughly on the right track here?

A few thoughts

  1. If a mint cannot deserialize a secret string that was intended to be of a special type (say P2PK), then it will interpret it as being of type simple, and consider the token valid, even though that might not have been the intent of the sender. The process requires trusting the mint anyway so it's not a deal breaker but it's an extra trust requirement; you need to trust that the mint can correctly parse the strings you're building for it, and that your wallet library can build these secret strings correctly, but you won't know until redeem time.
  2. Is there a way to force that a token be of a certain type outside of the secret? Something like adding a type field on the proof object? You commit to the secret when you get the blinded signature anyway so it's not like this would save the token if it was wrongly formed, but I see 3 small advantages: (a) you'd get a better error back from the mint if the token was malformed because the mint would know you were trying to give it a certain type and that deserialization didn't work, (b) it would be easier/cleaner for your wallet to keep track of which proofs are of which kind (loops on the type field instead of attempting to parse every secret to decide what type it belongs to), and (c) would allow mints to enforce stricter rules per type, for example as issue Specify maximum secret size #44 alludes to, forcing the secret to be of a specific/maximum size if it is of type SIMPLE but not enforcing that rule if it is of another type.
{
    "id": "DSAl9nvvyfva",
    "amount": 2,
    "type": "SIMPLE"
    "secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
    "C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
},
{
    "id": "1cCNIAZ2X/w1",
    "amount": 8, 
    "type": "P2PK",
    "secret": '["P2PK",{"nonce": "5d11913ee0f92fefdc82a6764fd2457a","data": "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",}]',
    "C": "02250a37a56b78e66674f7f063e6abd3d9345f8761fb90cac0293108910a8c27a3",
    "p2pksigs": [
        "c43d0090be59340a6364dc1340876211f2173d6a21c391115adf097adb6ea0a3ddbe7fd81b4677281decc77be09c0359faa77416025130e487f8b9169eb0c609"
    ]
}

@callebtc
Copy link
Contributor Author

Thank you for sharing your thoughts!

So my first question is this: is there a reason to need all proofs to be of the same kind (line 28)?

Not really! Since every Proof can be unlocked individually, this should work. I'll have to think about whether weird edge cases can come up in multisig transactions or with sighash flags (anyonecanpay?).

Under this workflow, a mint would attempt to parse a secret into one of its accepted types (are there potential attacks there? you'd need to make sure that any given secret can only be parsed as one of those types, excluding the Simple type), and if it cannot deserialize the string into any of its "special" types, then interpret the secret at being of type "simple" or whatever you'd call it. Am I roughly on the right track here?

That is absolutely correct.

You need to trust that the mint can correctly parse the strings you're building for it, and that your wallet library can build these secret strings correctly, but you won't know until redeem time.

This is also correct. To confirm that your secret is constructed correctly, you might have to test it with a local parser. To guarantee correct script execution, you might have to test it against a development mint and hope that the production mint is doing the same. This should be no problem however with a closed ecash system.

Is there a way to force that a token be of a certain type outside of the secret? Something like adding a type field on the proof object?

I'm not sure of this. The reason why it works so well with the secret is that it is committed to with the mint's blind signature. Sure, the secret itself could again commit to something else, but that's what we already do with the witness data that fulfill this secret's conditions.

Wallets typically store additional data with a Proof already and represent parts of it as a struct. In Nutshell for example, it's (almost, not quite yet) possible to do this:

if proof.secret.kind == secrets.SIMPLE_KIND: 
   ...

@callebtc callebtc merged commit 7e7ac7d into main Oct 13, 2023
@callebtc callebtc deleted the nut10/well_known_secrets branch October 13, 2023 19:24
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