Eliminating finalize step

I’ve checked a bit the Eliminate Finalize Step RFC (closed) and tried to achieve similar things, but dumb them down to a simpler model. That’s not to say this is an improvement. I’m merely sharing this because I find it simpler to reason about grin transactions this way. This is mainly because the exchange of data between the parties is symmetric and so is the payment proof.

As any other thing, it may contain flaws.

Grin partial contracts

This has nothing to do with smart contracts. I just couldn’t think of a better term than ‘partial contract’ and decided to leave it for now. Below I’ll try to explain why I believe it’s not an address - though, if it makes it easier, you can think of them as a version of grin address which contains additional transaction scoped data.

Notation

Reading some old topics made me realize each one of us uses different variable names (it’s a pain to think about these), so I’ll first explain what variables mean.

We denote P as public excess and p as its private excess.
Similarly, we denote R as nonce and r as its private nonce.

To distinguish public excess and nonce owners, we subscript them by the owner’s first letter e.g.

  • Ps - Sender’s public excess
  • Rs - Sender’s nonce
  • Pr - Receiver’s public excess
  • Rr - Receiver’s nonce

Schnorr verification equation is defined as the following:

e*P + R = s*G

Grin partial contracts

Our grin address holds ed25519 public key which is party’s identity.
We define a new construct called grin partial contract which along with ed25519 key also holds a one-time pair (P, R). In order to commit to the pair, we also require the contract come with a signature for H(P | R) with ed25519 key. So a partial contract has the following form

contract = (ed25519, P, R, signature)

Why not just say that this is a new version of an address?

To avoid confusion. The added triplet (P, R, signature) has nothing to do with an address because it’s a transaction specific context. This is why it might be a good idea to keep the identity separated from the transaction context. In other words partial_contract = address + tx_precommitment. The way I find it easy to think about it is that each party shows a “partial contract” and then they both commit to the whole.

Payment proofs

Given two partial contracts

contracts = (ed25519s, Ps, Rs, signatures)

contractr = (ed25519r, Pr, Rr, signaturer)

We define a payment proof challenge for nonces to be Rs’ = f * Rs for the sender and Rr’ = f * Rr for the receiver, where

f = H(H(contracts) | H(contractr) | H(payment_info)) where payment_info can be (amount, memo).

Instead of using Rr, Rs as nonces in e, we use Rr’, Rs. The idea is to make both parties commit to the payment proof through the challenge f. When the sender is signing for Schnorr challenge e, they can check that the receiver’s nonce is f*Rr and vice versa. This should work regardless whether the flow is SRS or RSR.

The sender can prove they paid to ed25519 public key by showing (R, contracts, contractr, payment_info) where R is a part of a signature (R, s) that landed on the chain. This should be enough to compute f and show that f * contracts.Rs + f * contractr.Rr = R.

Soundness of payment proof

We first note that none of the parties reveals the private key of their nonce or excess at any point. I believe our payment proof verification f * contracts.Rs + f * contractr.Rr = R can’t be gamed because f commits to both contracts and the payment_info. This commitment also commits to R the signature will use because R becomes R = f * Rs + f * Rr. Changing the challenge f in any way also changes the R, so f is directly tied to a specific R.

NOTE: It would seem simpler for the receiver to send the signature saying that if R lands on the chain, he is considered paid by the sender for some amount and memo. However, this breaks symmetry because in the RSR flow, the receiver is the last one to sign and only learns R at the last step. Communicating a signature would require another round of communication. In contrast, the above payment proof happens at step2 for both parties.

Steps

  1. Sender shares their partial contract contracts
  2. Receiver creates their partial contract contractr. Having both partial contracts allows the receiver to compute new challenged nonces Rs’ and Rr’, the challenge e = H(M | Ps + Pr | Rs’ + Rr’) and produce a partial signature (Rr’, sr) which is sent back to the sender along with their partial contract contractr.
  3. Sender now has both partial contracts, which allows them to computes e, verify that the nonces have payment proof challenge f, construct their partial signature (Rs’, ss) and produce the final signature (Rs’ + Rr’, ss + sr) which should be valid for Ps + Pr on message M.

The only computational difference in RSR flow is that the receiver is the first to share their contractr. At step 2, both contracts are known means we have all the variables for the computation.

Improvements

Versioning payment proofs

It would likely make sense to add versioning to the payment proof e.g.

f = H(version | H(contracts) | H(contractr) | H(payment_info)).

Payment info simplification

Adding amount, memo to the partial_contract simplifies things. This would make the whole joint contract independent of external things (except for a document memo references which can’t be solved). Embedding the two in the partial contract would mean we’d have two partial contracts:

contracts = (ed25519s, -amount, memo, Ps, Rs, signatures)

contractr = (ed25519r, amount, memo, Pr, Rr, signaturer)

Note that the sender has -amount and the receiver has amount. Both could verify that

contractr.amount - contracts.amount = 0. This would also simplify the challenge f to

f = H(H(contracts) | H(contractr)).

The neat part of this representation is that it scales with more parties, we just end up having n partial contracts

f = H(H(contract1) | … | H(contractn)) and everyone gets to challenge their nonce. Everyone ends up with the same payment proof which proves who sent/received and how much. In this case the payment proof would be (R, contract1, …, contractn).

In a multisignature setting where we have many senders and receivers, it might not be possible to reveal only how much a specific party sent/received. Perhaps we’d need to show all of the partial contracts involved.

13 Likes