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

WIP: Creating 'Eliminating finalize' RFC #59

Closed

Conversation

DavidBurkett
Copy link
Contributor

@DavidBurkett DavidBurkett commented Aug 10, 2020

@tromp
Copy link
Contributor

tromp commented Aug 10, 2020

What is the purpose of proof_nonce?
I.e. why not define total_nonce as sender.nonce + receiver.nonce ?

@DavidBurkett
Copy link
Contributor Author

I actually think we can get rid of diffie_secret, and just use: proof_nonce = Hash(sender.public_nonce | receiver.public_nonce | tx.amount), so let's assume that's the case for now.

By hashing the sender's and receiver's public_nonces to generate the proof_nonce, then the existence of a kernel signature whose nonce is sender.public_nonce + receiver.public_nonce + proof_nonce along with evidence proof_nonce was generated from Hash(sender.public_nonce | receiver.public_nonce | tx.amount), then the receiver can't deny being part of that transaction.

@tromp
Copy link
Contributor

tromp commented Aug 10, 2020

As stated, these proofs don't work.

Sender can prove knowledge of sender.nonce just as easily as they can prove knowledge of
sender.nonce - proof_nonce + other_random_proof_nonce
Also, receiver.public_nonce would have to be signed for by receiver somehow.

Perhaps we need something like

pre_nonce = sender_nonce + receiver_nonce
total_nonce = pre_nonce + Hash(pre_nonce | sender_pubkey | receiver_pubkey | amount)

Copy link
Contributor

@lehnberg lehnberg left a comment

Choose a reason for hiding this comment

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

Nice job, @DavidBurkett 👍 I took a pass with some thoughts and questions, let me know what you think.

# Motivation
[motivation]: #motivation

To simplify transaction building.
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be good to understand how transaction building gets simplified with this RFC. What are the specific use cases that become easier? Does it become easier for services to process payments? Will user <> user flows be simplified? Cold storage transfers?

I also understand it as enabling payment proofs in the invoice flow, something which hasn't been possible before and is an open question of RFC006, which might be worth while mentioning here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, was focusing on the technical details. I got a bit lazy. (ok, a lot lazy)

Copy link
Contributor

Choose a reason for hiding this comment

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

Haha, understandably! I can help here, if we can identify roughly what these are. It's not entirely clear to me where the benefits arise

# Community-level explanation
[community-level-explanation]: #community-level-explanation

This feels like unnecessary red tape. See summary.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be useful to outline high level before and after flows for transaction building, and how it might be experienced from the end user perspective. Perhaps something like the below?

Prior to this RFC

Sender initiated (regular workflow)

  1. Sender creates and sends partial slate to receiver to a slatepack address.
  2. Receiver produces a response and returns to sender.
  3. Sender finalizes and broadcasts to the network.

Receiver initiated (invoice workflow)

  1. Receiver creates a payment request and partial slate and sends to sender.
  2. Sender approves the request, produces a response and returns to receiver.
  3. Receiver finalises and broadcasts to the network.

Flow as per this RFC

Sender initiated (regular workflow)

  1. Receiver shares a one-time address with sender along with grin1 slatepack address.
  2. Sender creates a partial slate and shares with receiver.
  3. Receiver completes the slate and broadcasts transaction to the network.

Receiver initiated (invoice workflow)

  1. Receiver sends payment request, one-time address, and partial slate to sender.
  2. Sender approves the request, completes the slate, and broadcasts transaction to the network.

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 will make a few small fixes to this, and use it as the community-level explanation. Thank you

Copy link
Contributor

Choose a reason for hiding this comment

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

Flow as per this RFC

Sender initiated (regular workflow)

  1. Receiver shares a one-time address with sender along with grin1 slatepack address.

To me that's receiver initiated.
Whoever first sends the first slate contents, i.e. public excess and nonce, is the initiator.
Isn't one-time address is just a misleading name for initial slate contents?!
So this looks rather identical to 1st step of traditional receiver workflow "Receiver creates a payment request and partial slate and sends to sender."

  1. Sender creates a partial slate and shares with receiver.

That looks identical to 2nd step of traditional receiver workflow "Sender approves the request, produces a response and returns to receiver."

  1. Receiver completes the slate and broadcasts transaction to the network.

That looks identical to the 3rd step of traditional receiver workflow "Receiver finalizes and broadcasts to the network."

The only interesting part to me is the ability to still have a payment proof in the receiver workflow.

Receiver initiated (invoice workflow)

  1. Receiver sends payment request, one-time address, and partial slate to sender.
  2. Sender approves the request, completes the slate, and broadcasts transaction to the network.

I fail to see how this works, given that the receiver never produced a partial signature.

Copy link
Contributor Author

@DavidBurkett DavidBurkett Aug 16, 2020

Choose a reason for hiding this comment

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

Whoever first sends the first slate contents, i.e. public excess and nonce, is the initiator.
Isn't one-time address is just a misleading name for initial slate contents?!

I believe you're thinking about this too technically, and not enough like a typical user. Right now, nobody complains about calling the regular workflow "sender initiated", even though the first part includes receiver sharing the address with the sender. And the slatepack address could just as easily be considered part of the slate as the one-time public nonce and excess are. If you think of the pub excess, pub nonce, and slatepack address as a "one-time address", and not as a partial slate, the RFC wording will make much more sense.

I fail to see how this works, given that the receiver never produced a partial signature.

You're correct. When I included it in the RFC, I made some modifications to make it more accurate. Please see here instead: https://github.com/DavidBurkett/grin-rfcs/blob/eliminate_finalize/text/0000-eliminate-finalize.md#community-level-explanation

Copy link

@ljcolomacks ljcolomacks Aug 31, 2020

Choose a reason for hiding this comment

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

Isn't the point of this RFC to get rid of the third step for both sender and receiver initiated transactions? Pretty sure kurt (from the forum thread) mentioned this is possible if i understood correctly.

Sender initiated

  1. The sender generates a partial slate, with nonce timestamp etc, and sends it to receiver. (in this case the receiver does not need to be known, could be anonymous)
  2. The receiver completes the slate verifies the transaction and broadcasts it to the network.

Flow as per this RFC

Sender initiated (regular workflow)

  1. Receiver shares a one-time address with sender along with grin1 slatepack address.
  2. Sender creates a partial slate and shares with receiver.
  3. Receiver completes the slate and broadcasts transaction to the network.

Receiver initiated (invoice workflow)

  1. Receiver sends payment request, one-time address, and partial slate to sender.
  2. Sender approves the request, completes the slate, and broadcasts transaction to the network.

Copy link

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

That's what I thought, thanks for clarifying. Wasn't sure if something changed after reading this.

TODO: Define JSON & binary formats
TODO: Mention QR codes

## Backward Compatibility
Copy link
Contributor

Choose a reason for hiding this comment

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

Might also be good to outline whether we think this should be the default moving forward, and if not, i.e. if the existing flow still should be supported, how we expect the two to co-exist without added friction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I'm not even sure what's best to do here. I'll put some thought into it and then update this section accordingly.

text/0000-eliminate-finalize.md Show resolved Hide resolved
text/0000-eliminate-finalize.md Outdated Show resolved Hide resolved
Addresses are significantly longer (170 characters).

Using one-time addresses increases the risk of "Play" attacks [2]. Recommendations for dealing with these have been discussed in the above "Play attacks" section.

Copy link
Contributor

Choose a reason for hiding this comment

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

How are replay attacks affected by this? Not at all?
Are PayJoins still possible to do as before?
Are there any consensus breaking changes required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Replay attacks seem unaffected.
  • Interestingly, it solves the various privacy concerns I have with PayJoins. Using today's workflows, I consider PayJoins a non-starter, as they're terrible for the receiver's privacy. With this RFC, receiver doesn't have to leak any inputs until they choose to broadcast the transaction.
  • Nope, consensus layer remains unchanged.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhh... you're right, it doesn't become possible to probe for tx outputs... ping @phyro

Copy link
Member

Choose a reason for hiding this comment

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

First, congrats on the scheme David 👍 I think I understand the general idea, but have yet to fully grasp it. Yeah, this should make it easy to avoid the need to show the input to the sender. The receiver not having to send back to sender makes it possible to hide receiver's input which makes it more suitable for the receiver's side. It does require the sender to show their inputs and outputs to the receiver though which is not true in today's tx building since a tx could be merged prior to the receiver seeing it. This scheme basically shifts the roles of information knowledge hence making the sender show their inputs/outputs to the receiver. This gives that possible hiding of inputs/outputs power that the sender has in the current transaction building process to the receiver if I understand it correctly.

As you've already said, repetitive utxo spoofing should be impossible in theory, but some care needs to be taken because if the receive action is automated, the sender could send 1 nanogrin a dozen times and then observe the transactions which could cause output leaking if the receiver finalizing the tx was automated.

Such A->B scheme would be nice if we conclude it is secure. I have some questions but am not sure how relevant they are:

  1. What happens if the sender sends to the same address twice? are there any possible security issues?
  2. UTXO spoofing should be impossible in theory, but what happens if you just send 1 nanogrin a dozen times to a wallet that is automated to receive? My current understanding is that automated PayJoin receive transactions could be a bad idea if one is concerned about this attack, so payjoins might be best if they required a manual confirmation. Am I understanding it correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This scheme basically shifts the roles of information knowledge hence making the sender show their inputs/outputs to the receiver.

Yea, this is how we used to do it before compact slates anyway. It's far less harmful than letting a random sender prod a listening wallet for inputs. The sender chooses who to reveal its inputs to, making it far less worrisome.

the sender could send 1 nanogrin a dozen times and then observe the transactions which could cause output leaking if the receiver finalizing the tx was automated.

You're talking about when using PayJoin?

  1. What happens if the sender sends to the same address twice? are there any possible security issues?

Huge security issues, which is why I define the exact process in the "Nonce Reuse" section. Have a re-read of it and let me know if you still have questions.

2. My current understanding is that automated PayJoin receive transactions could be a bad idea if one is concerned about this attack, so payjoins might be best if they required a manual confirmation. Am I understanding it correctly?

Yea, you're understanding that correctly. Good point. PayJoin is still problematic. However, since these are one-time addresses, presumably you're not giving dozens of them out to the same person. You could have an automated service that does that though, so it's still worth considering if maybe PayJoin should always require manual intervention. An automated service could still collect partial transactions, and user could later manually finalize them all and choose whether to payjoin or not.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

An alternative approach to simplifying transaction building is to support non-interactive transactions [3].
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it fair to say that another alternative would be for a receiver to pre-generate a lot of invoice slates using the existing workflow? With the drawback there being that they would need to specify amounts (right?) and also that payment proofs would not be supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The amounts part kind of seems like a deal killer. It's not a practical solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe still worth raising it as an alternative and explain why it's not as good, it only helps build the case for what's being proposed I believe

# Unresolved questions
[unresolved-questions]: #unresolved-questions

* Can this be used to simplify sending to a multisig address?
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting.... We should explore this, it's one of the open things with Slatepacks as well. If we could ascertain that this flow would make multisigs (and maybe also atomic swaps?) easier, it could be a powerful statement.

text/0000-eliminate-finalize.md Outdated Show resolved Hide resolved
@DavidBurkett
Copy link
Contributor Author

Also, receiver.public_nonce would have to be signed for by receiver somehow.

Why? It's part of their address.

total_nonce = pre_nonce + Hash(pre_nonce | sender_pubkey | receiver_pubkey | amount)

Why does pubkey need to be involved at all? Since nonce is already part of the address, I don't see why we also have to commit to excess in the hash also. And without pubkey, this is almost exactly the scheme I mentioned above. Put in this form, it would just be total_nonce = pre_nonce + Hash(sender_nonce | receiver_nonce | amount)

@lehnberg lehnberg added the wallet dev Related to wallet dev team label Aug 10, 2020

TODO: Describe play attacks, and finish this section.

Any inputs sent from a wallet to a one-time address should be considered as sent. They should not be cancelable, however, the sender should have the option to add additional inputs and/or increase the fee (equivalent of bitcoin's RBF).
Copy link
Member

Choose a reason for hiding this comment

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

Please correct me if I'm misunderstanding this, but I think that they should definitely be cancelable from the sender side. Consider a scenario where Alice is sending 5 Grin to Bob and she uses an input that holds 1000 Grin. She creates her change output with 995 Grin. If she can't cancel the transaction, then if Bob simply refuses to finalize the transaction, this is somewhat equivalent to Bob burning all the outputs which would make Alice lose her 995 Grin output. In theory, Bob could blackmail Alice telling her to send some more coins if she wants to get her change output. Adding a cancel option to Alice solves this as she could reuse the inputs in a new transaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I didn't think that through very well at all 😆
I guess I more meant that cancel and resend to same receiver because the transaction "failed" (a common case today) shouldn't be supported, since cancel requires respending those inputs. Instead, we should resend the original partial slate, perhaps with a larger fee. We should never naively just return the inputs back to the wallet as spendable.

Copy link
Member

@phyro phyro Aug 10, 2020

Choose a reason for hiding this comment

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

yeah, I agree. A 'resend' after cancelation should reuse the inputs to avoid the play attacks - a reuse of completely the same inputs and outputs is one such option.

@tromp
Copy link
Contributor

tromp commented Aug 10, 2020

As stated, these proofs don't work.
Sender can prove knowledge of sender.nonce just as easily as they can prove knowledge of
sender.nonce - proof_nonce + other_random_proof_nonce

No comment on this?

Also, receiver.public_nonce would have to be signed for by receiver somehow.
Why? It's part of their address.

But can sender convince a 3rd party (like a court) that the receiver used that address?
I.e. can the receiver repudiate having that address?

total_nonce = pre_nonce + Hash(pre_nonce | sender_pubkey | receiver_pubkey | amount)
Why does pubkey need to be involved at all?

They are already in existing payment proofs, where you call them sender_address, receiver_address.

@DavidBurkett
Copy link
Contributor Author

DavidBurkett commented Aug 10, 2020

But can sender convince a 3rd party (like a court) that the receiver used that address?
I.e. can the receiver repudiate having that address?

Can a receiver repudiate having a bitcoin address? Exactly what should the receiver sign the nonce with? Are we going to require registration or some kind of web of trust in order to validate an address belongs to someone?

They are already in existing payment proofs, where you call them sender_address, receiver_address.

Sure, but the sender_nonce and receiver_nonce are enough to take that place. I'm fine with including full addresses there, but I thought your claim was that including full address was somehow a resolution to the comment "As stated, these proofs don't work". I fail to see how including full address improves the strength of the proof.

Sender can prove knowledge of sender.nonce just as easily as they can prove knowledge of
sender.nonce - proof_nonce + other_random_proof_nonce

But proof_nonce is generated by hashing sender.public_nonce and receiver.public_nonce, so I'm not entirely sure what you're getting at. If you say these proofs are insecure, I believe you; you have a much better understanding of schnorr signature security than I do. But you'll have to ELI5 what's wrong with it so the rest of us can keep up 😄

@DavidBurkett
Copy link
Contributor Author

But can sender convince a 3rd party (like a court) that the receiver used that address?
I.e. can the receiver repudiate having that address?

Can a receiver repudiate having a bitcoin address? Exactly what should the receiver sign the nonce with? Are we going to require registration or some kind of web of trust in order to validate an address belongs to someone?

They are already in existing payment proofs, where you call them sender_address, receiver_address.

Sure, but the sender_nonce and receiver_nonce are enough to take that place. I'm fine with including full addresses there, but I thought your claim was that including full address was somehow a resolution to the comment "As stated, these proofs don't work". I fail to see how including full address improves the strength of the proof.

Sender can prove knowledge of sender.nonce just as easily as they can prove knowledge of
sender.nonce - proof_nonce + other_random_proof_nonce

But proof_nonce is generated by hashing sender.public_nonce and receiver.public_nonce, so I'm not entirely sure what you're getting at. If you say these proofs are insecure, I believe you; you have a much better understanding of schnorr signature security than I do. But you'll have to ELI5 what's wrong with it so the rest of us can keep up 😄

After some offline discussion, it was determined that the issue @tromp was referring to does not apply to this scheme, since we correctly hash the sender.nonce as part of the logic to determine proof_nonce. The payment proof scheme appears to be secure, though I strongly encourage others to try to attack it.

@tromp
Copy link
Contributor

tromp commented Aug 11, 2020

Sorry for misunderstanding the construct previously and incorrectly claiming they don't work.

Are we going to require registration or some kind of web of trust in order to validate an address belongs to someone?

No, but we do want to tie payment proofs to addresses that have some permanence and might conceivably be published or attested to. The latter would be outside the scope of this proposal though.

proof_nonce = Hash(sender.nonce | receiver.address | tx.amount), where receiver.address is the decoded one-time address (version | ed25519_key | public_excess | public_nonce)

I would also Hash a memo field that both parties could agree on to describe the payment.
Receiver reserves the right to specify the exact format of the memo. I can see no harm
in having this option, and some good use cases.

The total_nonce for the transaction will then be sender.nonce + receiver.nonce + proof_nonce.

The following 4 steps are necessary for the sender to prove payment to the receiver's address:
Show that sender.public_nonce + receiver.public_nonce + (proof_nonce * G) = total_nonce * G (ie. the k*G in the kernel signature)

It may be hard to convince a 3rd party arbiter that the claimed receiver.public_nonce was the actual one. This is specific to one tx and not likely to be publicly verifiable.
My suggested use of

pre_nonce = sender_nonce + receiver_nonce
total_nonce = pre_nonce + Hash(pre_nonce | sender.address | receiver.address | amount | memo)

doesn't seem to have that issue as total_nonce unambiguously commits to all the hashed data.

@DavidBurkett
Copy link
Contributor Author

doesn't seem to have that issue as total_nonce unambiguously commits to all the hashed data.

It doesn't though. The only way to tie the receiver.public_nonce unambiguously to the receiver's ed25519 address is if you have a signature to the nonce. I could use any ed25519 public key as receiver.address in the hash and still be able to build a valid transaction.

I suppose the solution is to extend the bech32 address further to contain:

version | ed25519_pubkey | public_excess | public_nonce | signature

@tromp
Copy link
Contributor

tromp commented Aug 11, 2020

You're right; there needs to be an actual signature withe the ed25519_pubkey involved somewhere...

@lehnberg
Copy link
Contributor

A thought I wanted to park here is that this approach (with one time use addresses) to some degree supports the previous thinking where we wanted to discourage slatepack address re-use in any case as it leaks privacy unnecessarily.

On the other hand, there's also:

  1. ...the fact that even if we strongly discourage one time addresses to be reused, we won't be able to prevent this from happening. Or would we? Maybe on the wallet level I suppose, but is that acceptable? Do we introduce additional pitfalls for end users that will lead to mistakes no matter how we go about it?
  2. ...a lot of edge use cases to think about. For example, how would the general fund grin donation wallet use this scheme? Today we have a single slatepack address that users can communicate with (assuming wallet is listening). How would it work under the proposal?

@phyro
Copy link
Member

phyro commented Aug 12, 2020

@lehnberg am I understanding correctly that the receiver would need to pass a new address for every receive action? Wouldn't this mean that automated receives become impossible (unless you have a step 0 where you generate a new address and share it automatically)?

@tromp
Copy link
Contributor

tromp commented Aug 16, 2020

I suppose the solution is to extend the bech32 address further to contain:
version | ed25519_pubkey | public_excess | public_nonce | signature

We used to have a clear separation between slate address and slate contents.
This is starting to migrate the latter into the former, which seems like a slippery slope...

@DavidBurkett
Copy link
Contributor Author

Slippery slope to what? What practical issues are there with it?

@tromp
Copy link
Contributor

tromp commented Aug 16, 2020

Slippery slope to confusing the notions of slatepack and slatepack address.
One issue is getting uncomfortably long addresses, which are more fragile, e.g. due to line breaks and wrapping.
Another issue is that one-time addresses are less useful in payment proofs,
making it easier for receivers to repudiate owning such addresses.

Instead of

  1. Receiver shares a one-time address, amount, and optional memo, expiration, etc.
    we could do
  2. Receiver shares a grin1 slatepack address, a payment request and a partial slate with public excess, public nonce, and signature, with sender.

Then the payment proof could be tied to the more permanent receiver address, making it far more useful.

Copy link
Member

@j01tz j01tz left a comment

Choose a reason for hiding this comment

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

Thanks very much for taking the time to put this together @DavidBurkett, apologies for taking so long to review. This is an exciting idea made even more exciting with a beautiful RFC :)

Something for us to think about in general- how much of a headache will it be to support both tx types long term? If we can live with it, this is an interesting opportunity to improve UX by reducing manual interactive steps and to potentially improve privacy by enforcing unique addresses for tx's, both while still allowing support for short generic slatepack addresses that can remain static as a donation address for example.

This does come at a cost of potential added confusion for users with the added complexity: which kind of address should I paste to receive coin from my exchange? which kind is the most private/secure? even if Tor works for both of us? what if it doesn't? which kind should I implement for my online store? what are the implications and tradeoffs with each? will multisig and payment channels be supported by both types? These questions can all be overcome with additional documentation at the cost of ecosystem fluidity.

Like with slatepack, this RFC seems to be more about a UX improvement than a technical improvement, but we still have to make sure the technical details are sound enough to avoid new UX headaches (or worse). Unfortunately for us, since UX is such a major component of this area, it is harder to objectively determine what the "best" approach is, relative to purely technical decisions.

If address reuse with this transaction flow means potential fund loss, and an address_list and indicator for used addresses must be kept to prevent address reuse, does this mean that for a secure restore from seed, an up-to-date address_list must be kept to be input with a seed on restore? Maybe there is some synergy there for mitigating (re)play related attacks in the wallet, as iirc the ability to track state into a seed restore will solve at least some of the problems. This seems to be one of the biggest challenges of the idea, but if we need to track a wallet state anyway, this data could certainly be included.

As @tromp points out, I am a bit wary to start embedding data at the address layer because we spent a lot of effort while architecting slatepack to think carefully about what data is embedded at which layer. Obviously the opportunity to reduce the number of steps taken by users in an ecosystem that already has usability challenges is appealing and should be explored with a cautious optimism, as long as we are certain that reducing the number of steps in this way does make the improvements we are seeking. For better or worse, with a more complicated slatepack address, we lose the ability to associate Grin keys with identity in future systems like keys.pub and lose the ability to use Grin keys as age encryption/decryption keys for files or even message data a la keybase. While there are advantages to retaining simplicity and compatibility in the address space, a significant UX improvement would be one of the few valid reasons to consider adding complexity there.

I still want to think through implications of the additional data leakage for the newly proposed addresses, as well as step through the crypto more closely, particularly around the payment proofs. As previously pointed out, I'm also very curious what multiparty flows would look like. Overall, I appreciate the opportunity in the idea, even more so with the level of detail you have included in the RFC, nice work.

@j01tz
Copy link
Member

j01tz commented Aug 17, 2020

After another look, I might have been confused around the nonce and the address_list requirement for a restore from seed- if they are non-deterministically generated, a wallet restored from seed would not generate the same address, but then they would also not be able to receive a legitimate transaction that was initiated but not completed before the restore?

@DavidBurkett
Copy link
Contributor Author

Correct, much like a send slate today can't be finalized on a separate, restored wallet, a slate to a one-time address can't be finalized on a separate, restored wallet.

@DavidBurkett
Copy link
Contributor Author

DavidBurkett commented Aug 17, 2020

Supporting both types of transactions should be trivial, and is described in the backward compatibility section. The only challenge is, as you mentioned, trying not to confuse users when displaying both a one-time address and a permanent one.

But maybe we don't have to display both. It's technically possible to do both types of sends (one-time and traditional) provided just a single address. If the one-time address was already used, the receiver will ignore the partial signature created using the one-time nonce/excess, and just return a slate for finalization by the sender. But if the nonce/excess are unused, receiver can finalize herself.

I'm not sure how I feel about that yet, but it's just a quick thought I had. We might be able to iterate on it further.

@DavidBurkett
Copy link
Contributor Author

DavidBurkett commented Aug 19, 2020

If we're open to making a consensus change, removing public excess from the schnorr signature challenge will allow us to eliminate the finalize step without the requirement for using one-time addresses. Kurt had originally claimed this was possible, but I hadn't had time to investigate the consequences until now.

Our schnorr signatures use e=Sha256(M | ks*G +kr*G | rs*G + rr*G) as the challenge string, where M=message, ks=sender nonce, kr=receiver nonce, rs=sender excess, and rr=receiver excess.

A standard schnorr multisignature only requires e=Sha256(M | ks*G + kr*G) to be secure. We only include the rs*G + rr*G to prevent malleability attacks, where the sender or receiver can change the kernel commitment without the other party knowing, making it hard for them to identify it. However, that's not a problem if wallets start identifying kernels by public nonce instead of public excess.

If rs*G + rr*G no longer has to be part of the challenge, the sender can include their partial signature without knowing the receiver's public excess. This means that we can replace one-time addresses with permanent public nonces. A sender can then use a one-time nonce, along with some linear combination of your public nonce (say 10*public_nonce). That, along with his or her private excess is all the sender needs to know to build their partial signature. The sender then just passes their inputs, change output, public excess, public nonce, partial signature, and nonce multiplier (eg. 10) along to the receiver. The receiver can then generate a random excess, add their output and optional inputs, and then generate the final signature from that. This can be done as many times as you want without ever leaking the private key.

Edit: @valdok found that this idea is borked. :(
https://forum.grin.mw/t/eliminating-finalize-step/7621/91

@DavidBurkett
Copy link
Contributor Author

I was going to ask about this on #bitcoin-wizards, but it appears @tromp beat me to it:

image

It seems like we should be ok to go with the approach of always generating a random excess, but letting the sender generate a public nonce for us based on some linear combination of a permanent public key. So one-time addresses would indeed not be necessary to remove the finalize step.

Are there any obvious objections to going this route? Is this consensus change something we'd be ok with? If we are, I'll go ahead and update this RFC to use the permanent address approach that involves changing our schnorr multisignatures to use e=Sha256(M | ks*G + kr*G)

@tromp
Copy link
Contributor

tromp commented Aug 23, 2020

What is the advantage of one-time addresses or new kinds of Schnorr signatures over simply
sharing an existing grin address together with an initial slatepack?

@DavidBurkett
Copy link
Contributor Author

The advantage of changing Schnorr signatures is we get reusable addresses, so receiver doesn't have to initiate a send if the address is already known. It's also much easier to have listening wallets, and "cold" storage. You can set up a listener to collect a bunch of partial transactions while your keys are offline, and then receive/finalize them at your convenience without worrying about sending something back to the sender.

@antiochp
Copy link
Member

I don't think the following point is correct -

We only include the rsG + rrG to prevent malleability attacks, where the sender or receiver can change the kernel commitment without the other party knowing, making it hard for them to identify it.

My (limited) understanding is it is necessary to include both R (public nonce) and P (public key) in the challenge to avoid "related-key attacks" as discussed in BIP-340.

https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki

Key prefixing Using the verification rule above directly makes Schnorr signatures vulnerable to "related-key attacks" in which a third party can convert a signature (R, s) for public key P into a signature (R, s + a⋅hash(R || m)) for public key P + a⋅G and the same message m, for any given additive tweak a to the signing key. This would render signatures insecure when keys are generated using BIP32's unhardened derivation and other methods that rely on additive tweaks to existing keys such as Taproot.
To protect against these attacks, we choose key prefixed[4] Schnorr signatures; changing the equation to s⋅G = R + hash(R || P || m)⋅P. It can be shown that key prefixing protects against related-key attacks with additive tweaks. In general, key prefixing increases robustness in multi-user settings, e.g., it seems to be a requirement for proving the MuSig multisignature scheme secure (see Applications below).

https://twitter.com/pwuille/status/1297380417494847488

@DavidBurkett
Copy link
Contributor Author

DavidBurkett commented Aug 24, 2020

My (limited) understanding is it is necessary to include both R (public nonce) and P (public key) in the challenge to avoid "related-key attacks" as discussed in BIP-340.

This would be a problem if we were generating kernel excesses from our BIP32 seeds. Fortunately, we're not. They're randomly generated, so only the owner knows the relationship between the public keys.

Edit: Actually, I think it's just transaction offset which is generated randomly. Since outputs are generated from BIP32 seeds, indirectly the kernel excesses would be too. I think it still requires knowing the xpub to determine the relationships between child pubkeys, but we should switch to hardened keys to be safe.

@antiochp
Copy link
Member

This would allow any third-party to malleate a transaction, not just the sender or receiver?
We would want to think through the implications of this in terms of transaction relay and "identity".

a signature (R, s) for public key P
into a signature (R, s + a⋅hash(R || m)) for public key P + a⋅G

For any arbitrary "additive tweak" a -
Adjust the signature by a⋅hash(R || m)).
Adjust the public excess by a⋅G to match.
Adjust the kernel offset by a to compensate.

We would need to use R as kernel (and transaction) "identity" everywhere, not just wallet. Any transaction could be "tweaked" by any node during transaction relay.

I do like the idea of removing key-prefixing if this is not actually required. But the malleability makes me hesitate.

@DavidBurkett
Copy link
Contributor Author

We would need to use R as kernel (and transaction) "identity" everywhere, not just wallet.

Yep, correct. I think this should be fine in our existing use cases, although it's certainly a non-trivial change.

What does this do to your NRD proposal? Can it work with R instead of commitment? I'm ashamed to admit I never got a chance to review the RFC.

@antiochp
Copy link
Member

I'm guessing two kernels sharing the same R could be used instead of two kernels sharing the same public excess. We'd need to think it through but I believe the concept would still apply.

I'm not clear on if this there is a problem with nonce reuse here though.

@DavidBurkett
Copy link
Contributor Author

I'm not clear on if this there is a problem with nonce reuse here though.

Presumably the excess would be different for any "duplicate" (duplicate R) kernel, so I believe it should be fine. For sure using the same excess with a different message would be bad.

I'll see if I can get some time this weekend to look at the NRD RFC and see if we could make it work.

@antiochp
Copy link
Member

The other thing that bugs me here is this proposal effectively pre-shares the public nonce prior to the message being committed to. There is not a lot of freedom in the message as we only include the features, the fee and potentially lock height, but there is some freedom.
So if the recipient publishes their public nonce, is the sender now able to potentially bias the message by choosing different fee and lock_height values? And in turn bias the hash function?

See "Pre-shared nonces in MuSig" here -
https://medium.com/blockstream/insecure-shortcuts-in-musig-2ad0d38a97da

I honestly don't know if this is an issue with this approach - but something we should understand.

@kurt256
Copy link

kurt256 commented Aug 25, 2020

After I put the specific of the scheme with re-usable address for the nonce (https://forum.grin.mw/t/eliminating-finalize-step/7621/22?u=kurt), (and justified in another post why it is important that the sender commits to its partial nonce in x), I started to analyze the security of it and described the details of a possible attack that allows to steal coins if we are not careful with the protocol:
https://forum.grin.mw/t/eliminating-finalize-step/7621/44?u=kurt.
The way to protect against this is to make sure that the signature's messages are always different for a same (re-used) address.

This can be achieved by 2 different solutions:

  1. The receiver makes sure he always receives different "timestamps" (present in the hash in x), in which case the nonces will always be different and so will (be different) the signature's messages (since the signature's message contains the total nonce). This is what I suggested on the forum.
  2. Verifying nonce uniqueness at consensus level.

I prefer the solution 2. by far because it means that the user does not have to be careful, and can even re-use addresses accross wallets. It improves ux and relieves the burdon from the wallet developers and the users.
Verifying nonce uniqueness will also protect against replay attacks at the same time.

It is true that the kernel with this scheme will be malleable by everybody. For now I don't see an issue with this.

Concerning biasing the hash function, I do not see an issue either. The ultimate property of a hash function is to be collision-resistant, which our hash function provides. There is always the possibility in our current transaction building process that the receiver tries out different partial nonces before he finalizes the message of the signature. This is inherent to a Schnorr multi-sig.

Concerning the security of the scheme, I highly encourage everyone to try to attack it more.

This new Schnorr scheme is modified, and moreover intervenes in the context of a Mimblewimble blockchain. The attack that I described on the forum is not due to this modified Schnorr itself, but is precisely due to the fact that this modified Schnorr is inside a Mimblewimble protocol:
while knowing the difference of two partial secret keys is not unsecure in a Schnorr scheme in a more isolitated context, it turns out that is is not secure at all in the context of Mimblewimble (see the attack for the details).

There is absolutely no guarantees that there are not other attacks of this kind, even if we do verify nonce uniqueness to solve the first attack. I have looked more at the scheme and tried to attack it again, and my current feeling is that the scheme is secure.

One of the necessary conditions for this scheme to be secure is that knowing Hash(m1).x1 - Hash(m2).x2 does not lead to an attack (knowing x1 - x2 does lead to an attack, which is the one I described). I believe it does not, but I don't have a formal proof for this.
Of course this is not a sufficient condition: we need to try to attack the scheme under all possible angles to appreciate it more and be certain that is indeed secure.

@DavidBurkett
Copy link
Contributor Author

So if the recipient publishes their public nonce, is the sender now able to potentially bias the message by choosing different fee and lock_height values? And in turn bias the hash function?

It's even worse than that. The sender can also bias their public nonce, since it's not previously committed to. Though despite a few previous attempts to understand Wagner's attack, I still am yet to fully comprehend the implications. It's going to take some head-scratching.

@kurt256
Copy link

kurt256 commented Aug 26, 2020

The other thing that bugs me here is this proposal effectively pre-shares the public nonce prior to the message being committed to. There is not a lot of freedom in the message as we only include the features, the fee and potentially lock height, but there is some freedom.
So if the recipient publishes their public nonce, is the sender now able to potentially bias the message by choosing different fee and lock_height values? And in turn bias the hash function?

See "Pre-shared nonces in MuSig" here -
https://medium.com/blockstream/insecure-shortcuts-in-musig-2ad0d38a97da

I honestly don't know if this is an issue with this approach - but something we should understand.

Irrelevant to our scheme as I detail in https://forum.grin.mw/t/eliminating-finalize-step/7621/78?u=kurt.

@antiochp
Copy link
Member

Maybe another way of thinking about this is less about "eliminating" the finalize step and rather allowing the sender to delegate the responsibility of finalizing the transaction to another party.

In the simple sender->receiver case the sender delegates this responsibility to the receiver.

And this is achieved by publishing partial nonces and removing the public excess from the signature challenge, allowing the sender to build their partial signature earlier in the protocol.

@DavidBurkett
Copy link
Contributor Author

@antiochp What you're saying is more accurate technically, but to a typical user, it's a reduction of the number of actions, leaving just 2: send and receive (or send and accept, depending on how you view it). No more need for them to reason about finalization.

@lehnberg
Copy link
Contributor

@DavidBurkett not seeing much activity on this, is it still active? Was it shown to be flawed? Should it continued to be kept open?

@DavidBurkett
Copy link
Contributor Author

The RFC has not been shown to be flawed, though the consensus change mentioned in an above comment is. It's hard to gauge interest in the idea, which is why I held off. We can see what happens with Kurt's new design for the consensus change approach and decide from there which approach to take.

@ljcolomacks
Copy link

From a community perspective, I think this functionality is critical to our success.

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

Successfully merging this pull request may close these issues.

8 participants