-
Notifications
You must be signed in to change notification settings - Fork 721
Cashu wallet + Nutzaps #1369
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 wallet + Nutzaps #1369
Conversation
| ```jsonc | ||
| { | ||
| "kind": 7375, | ||
| "content": nip44_encrypt({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, why not use the cashu token encoding here, rather than the raw json?
https://github.com/cashubtc/nuts/blob/main/00.md#v4-tokens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would add an unnecessary extra encryption/decryption step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, what calvadev said
dluvian
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there is too much friction on the sender side of a nut zap. A sender has to fetch the Nutzap informational event, check if listed relays and mints are online and then mint new tokens in one of these mints. This complexity discourages client devs to adopt this standard and nut-zap-senders are discouraged from zapping if almost all of their attempts fail because the intended recipient hasn't published a 10019 event yet or the listed relays and mints are offline.
I haven't dabbled in the Cashu protocol yet but isn't it possible to timelock Cashu tokens with conditions? A sender could then timelock tokens in a way that allows the recipient to claim them anytime and the sender to reclaim them if they haven't been claimed after n days. Nuts will never get lost, relays don't have to be trusted and the sender can always immediately send those nutzaps.
No, this is as simple as it gets; anyone can nutzap without checking the 10019 of the recipient, but the recipient might never see those nutzaps if they don't check for them; this spec is actually incredibly simple and adding a timelock component would only make it more complex. Checking if the infrastructure is online is a downside of a decentralized protocol; sure, it would be more straightforward to assume that you can publish to api.twitter.com and not even bother with error handling, but any nostr client needs must properly handle trying to speak with a few relays and having one of them be offline. Fwiw, I wrote and implemented the entire spec in a few hours, this is not hard stuff whatsoever. Also, you might have misunderstood the spec, relays don't need to be trusted in the same way NIP 65 relays don't need to be trusted, the listing in the 10019 is just a shorthand to make it easier to know where to publish in a way the recipient will see it. It's literally;y the exact same thing as nip 65 or as DM relays |
|
Can the user's relay push its user by deleting all 7375 and 7377 events? I suppose you cannot recover the tokens if you don't have these events anymore. |
You need to trust the relay to not delete the token event and to deliver the event in the future. If you nutzap a 1-of-2 multisig nut then you don't have to trust the relay anymore as you can reclaim the funds if something goes wrong on the relay side. And if you can reclaim then you're free to zap anyone, making it less restrictive and simpler. |
I don't understand the comment -- "relay push its user"? what does that mean? like kick out its user from using the relay? At one point I had this appendix, that might shed some light to this concern, if that's what you meant. I removed it from the NIP because it sounded too paternalistic and clients can do whatever they choose to ensure their users are ok (i.e. like choosing good and enough relays, etc)
|
you're mixing two separate concerns: 1) token availability -- 2) clawback possibility when you talk about "you need to trust the relay" you are exclusively talking about 1) because if you don't have the token you can't claw it back, regardless of what script you had in it, so the multisig comment is irrelevant here. keep in mind that the recipient WANTS to receive the money, so they wouldn't choose relays that are going to delete their incoming money, that makes no sense. And ultimately the sender can publish to the relays the recipient specified and to their own relays or somewhere else. |
|
@pablof7z sorry, I meant to punish the user. We have seen relays just going bust or deleting events at their will, either reacting to something the user uploaded or just as a suspicion of malfeasance. The warning you mention is good but it can be clearer: In the current version of this NIP, the user can be rug pulled by the mint AND by the relay he/she decides to use. I know the same risk is true for all nostr events, but losing money is very different than losing Twitter posts. |
Ok, perhaps it should be explicit, but yeah, this is dollars to be used with multiple relays, particularly ones you run or trust; much like the drafts nip, I would hurt more if I lost my drafts than if I lose a few thousands sats. (Not kidding, I have tweet drafts I've only saved in typefully and have gone back to reading them for years, if typefully goes bust it'll suck; I need to move thise to nostr drafts asap) I'll update the nip to make this explicit and perhaps add the Appendix as is if you think it's helpful. |
| * `a` an optional tag linking the token to a specific wallet. | ||
|
|
||
| ### Spending proofs | ||
| When one or more proofs of a token are spent, the token event should be [[NIP-09]]-deleted and, if some proofs are unspent from the same token event, a new token event should be created rolling over the unspent proofs and adding any change outputs to the new token event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is challenging to maintain on the client since each spend requires the client to determine which events need to be deleted and what proofs from the deleted event should roll over to the new event while possibly adding more proofs.
What if the Spending History Event had inputs of the spent proofs and outputs of the new proofs instead? This event would now be mandatory, but it would make wallet tracking easier on the client since all the client has to do is replay all the events to get the current state. When the wallet makes a transaction, it is an easy-to-construct single event.
Maybe optionally, there can be a wallet "snapshot" or backup event that has all the unspent proofs at a specified point in time. The wallet can then replay the spending events since then to get the current state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is challenging to maintain on the client since each spend requires the client to determine which events need to be deleted and what proofs from the deleted event should roll over to the new event while possibly adding more proofs.
This is not problematic at all, or maybe I'm not understanding your point; but if the client fetches, say, 4 events, which each has, say, 3 proofs inside, total of 12 proofs.
If the client spends, say, 10 of those proofs, it's trivial to know it has to rollover the 2 unspent proofs into a new token event and delete all 4 token events.
Which equates to the client publishing 2 events:
Kind 9 (event deletion) e-tagging 4 events
Kind 7375 storing two proofs.
Just to be very explicit, the relays won't send the 4 deleted events any more when a client REQs for 7375s, it would now just reply with a single event, the one that contains the two proofs.
Again, perhaps I misunderstood your comment 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if I would describe that process as "trivial." 😅
This way of handling proofs forces clients to associate event IDs with proofs versus the suggested alternatives, which don't require that association. So, in that sense, this idea is more complicated for the client than it needs to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to agree with @davidcaseria
The annoying part with deleting events is if you have two nip60 wallet implementations on your device or different device.
You would store those events/proofs in an indexdb or local storage.
To get these 2 wallets in sync means replaying all the unspent proof events from the wallet creation (to account for the deleted one that once existed) Which you probably don’t want to do each time you open your app (or worse have both wallet polling all events every x seconds or min)
the append only method sounds simpler and more robust to sync the wallet accross platform/app, you just take all the unspent proofs from the events feed minus the spent proofs and you should be fine.
it doesn’t change anything for that NIP except that the spent proofs feed/event would now be mandatory. One can still delete the unspent proofs event but this would now be optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if the wallet wants to show an history for the user, which most neobanks do (wise, Revolut etc) keeping the full event history is a must
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the feeling the NIP is getting too deep into implementation details at this point, which isn't the goal of a NIP. It should be an implementation decision how to track the balance of the wallet. I'd find great to make the Spending History Event mandatory and thus wallets could choose an event sourcing approach as proposed by @davidcaseria too.
|
NIP-61 still doesn't seem to address my previous concern: If the content(nuts) of kind7337 is obtained by someone else and they also send a kind7337 event with this content(nuts), then who is the real person sent this nuts zap? How does the client determine this? |
Very good point! I think this can be addressed: you can commit to arbitrary data by adding tags to the |
This is certainly possible from a Cashu perspective and maybe even compliant with this spec? A P2PK-locked token can have a |
Awesome, yeah, this is a good point -- I'll update the NIP to include tags for the sender and an optional "e" of the zapped note. So the proofs would have an additional |
|
In order for a client to implement a complete wallet, there needs to be a way to store NUT-04 and NUT-05 quotes. I believe a new expirable event for each quote is probably best suited to handle the wallet's storage needs. Note that the quote ID must remain private, so it needs to be in an encrypted content payload. |
|
Another case that should be handled is the different statuses a proof can be in. For example, when sending a token, CDK puts a proof in a |
yeah, the idea I had for this was to moved the proofs that are in reserved status to a new kind, so the same operation of rolling over proofs from a token event after they're spent, but move them to a different event kind, exact same format; upon initialization a wallet can check for this event kind and check if the proofs have been spent or not and either delete them or roll them back in into the available tokens kind. |
|
What if instead of having to manage deleting events, the token event was structured in such a way that the action for the proof was embedded: I want to refrain from relying on deleted events because that can cause issues if mishandled. |
Oh yeah, that's an interesting idea; I'm not sure we need the deleted proofs one, the state of these events shouldn't grow too much as that would make syncing a bit more expensive -- is there a benefit to keeping that state?
Mishandled by the relays, as in, you instruct the relay to delete and it doesn't? Or what do you mean exactly? |
Yes, or clients. One relay implementation I was using returned both the deleted event and the kind 5 event, while another didn't return either. It's easy for an implementor not to handle that properly, so it should be avoided if possible.
Thanks. The deleted proofs are needed because if a proof is reserved and doesn't get claimed, the user can reclaim it. Proofs will always go from added (unspent) to reserved to deleted (spent). It's best not to confuse events with states. In event-sourced systems, a state is always derived from events. If you replay all the events through a proof state machine, it's easy to arrive at the same deterministic result. Also, I recommend that most clients implementing this NIP save a local copy of these events since it's critical not to lose the proofs, so relying exclusively on relays would be imprudent. From there, syncing would be querying for events since the last event. If the number of proof events grows too large for the client to store, we can introduce an optional proof snapshot (replaceable) event type. This should have either unspent or reserved proofs in its current state. Seeding that event as the initial event into the client's state machine, you can replay events after the point in time of the snapshot and similarly arrive at the same deterministic result. |
| Clients MUST add `e` tags to create references of destroyed and created token events along with the marker of the meaning of the tag: | ||
| * `created` - A new token event was created. | ||
| * `destroyed` - A token event was destroyed. | ||
| * `redeemed` - A [[NIP-61]] nutzap was redeemed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to also log outgoing zaps? When balance changes because of "out" it could be due to the user e.g. melting tokens to lightning, but it could also be due to sending a zap. (as in "redeemed" but in the other direction)
|
NIP-61 uses |
|
I have a legit doubt, really not trying to poo on this PR.
I saw that minibits, a cashu wallet that supports its own mint but also from others and offers lightning address to users, recently added NWC support. NWC can pay zaps and also has a Are nutzaps so much better than NIP-57 to justify creating a competing standard for transfering sats within nostr? If it is better than ok, let NIP-57 die with time, but else, this may confuse users a lot for little gain. |
|
Might be asking in the wrong place entirely Don't you think it would be possible to somehow use a Nostr extension, NIP-07, to let the mint know you are indeed in possession of the private key to the P2PK proofs that this mint has emitted? You would just need to sign some sort of secret, that's probably possible already... |
ekzyis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks cool! I haven't read through the details yet but I am already intrigued by this. I was wondering about this though:
NIP-60 money can be used to send NIP-57 zaps (by instructing the mint to pay the recipient's bolt11) or NIP-61 nutzaps.
Can't NIP-60 money be used to pay any bolt11 (like any other cashu wallet), no matter how we fetched it? As this is written with specific mentions of NIP-57 and NIP-61, this suggests that might actually not be the case.
yes, of course, you can pay any bolt11 with a nip-60 wallet |
NIP-60 is always custodial since it's based on cashu whereas |
NIP-07 does not allow you to sign arbitrary data, only nostr events. Every attempt to change this got shut down quite quickly |
yes, the |
|
Correct me if I'm wrong, but cashu and nostr do not share the same encryption. That means cashu and Nostr pubkey cannot be the same. If this above is correct, I would rather have NIP-61 NIP-44 encrypt the C part of the proofs a sender wants to zap, without p2pk lock the proofs at all, at least you can now zap anyone on Nostr without any previous configuration from the recipient |
incorrect, they are the same curve
that would make them unverifiable by others, which is a major benefit over LN zaps. You can still nutzap users that haven't done any setup, by zapping their nostr pubkey; but they will need to enter the nsec in the client to redeem it -- still completely reasonable, but would in real-world limit the number of clients a user can redeem from. In my view, the main reason to have a user do the initial nutzap setup (a byproduct of which can be establishing this other pubkey for the Of course, nothing prevents you from nutzapping someone that has not done their nutzap setup, but clients should not display those nutzaps as valid since perhaps even the receiver user is not even checking for them. This is all open design space; different clients/users will make different decisions. |
|
From my experience, p2pk lock a cashu token using my Nostr pubkey and then unlocking it with my Nostr private key just did not work on the mainstream mint, had an error where it said my key was not 32 or 64 Byte long. Probably an error on my end. As long as using a Nostr pubkey to p2pk an ecash is feasible, I agree with this flow, where not only the recipient but a Nostr client can validate a zap. I just wasn't able to do so using minibit or lnserver mints. Actually a matter of preference, which encryption do we use knowing they are pretty much the same? Using Cashu encryption would discard all Nostr extension to use this NIP, at the benefit of having anyone validate the proofs. |
You were missing the parity byte; nostr drops it, cashu keeps it. This is mentioned in NIP-60/61 😉 |
|
Is this ready to merge? I see there are like 5 clients using it already. Is it working? |
I suggest waiting until a consensus is reached on using deleted events to manage proofs. I am firmly in favor of not using deleted events. Also, there needs to be a way to handle reserved proofs; otherwise, double-spend issues will be common. |
Co-authored-by: callebtc <[email protected]>
Co-authored-by: callebtc <[email protected]>
|
There have been enough discussions and most people seem to be in consensus about this, and there are like 10 implementations out there, so it's time to merge. That doesn't mean we can't still change things. |
This PR introduces two independent but highly related NIPs.
Either NIP can be implemented separately and has independent benefits.
NIP-60: Cashu wallets
Store Cashu proofs in relays so that a wallet balance can follow you around nostr and can be easily spent from any client. Just like you load a client and your follow list is there, so can your pocket-money.
NIP-61: Nutzaps
Scheme to send nutzaps -- these are P2PK-locked cashu transfers that are NOT encrypted, but require a signature from a private key to be redeemed. Anyone can see the payload but only the holder of the private key can spend them.
Later on, I'll add DLEQ proofs to the nutzaps to make it possible to validate the validity of the proofs without talking to the mints.
Nutzaps have no zapper pubkey, where a third-party has to sign-off that a zap occurred.
NIP-60 money can be used to send NIP-57 zaps (by instructing the mint to pay the recipient's bolt11) or NIP-61 nutzaps.
Important considerations
Because ecash is such a contentious topic, here's a TL;DR: