Grin-wallet contract prototype

Grin contracts

NOTE: The software has bugs and it may lose your funds. The author is not responsible for any damage it may cause. Use this in a VM on testnet wallet.

I should mention that members of OC have not looked at this attempt yet, they were, like the community, informed I’m going to make a prototype, but I’m sharing this with the community and OC at the same time.

This document attempts to explain the contract terminology as well as how to test a prototype including exchange deposit/withdraw. Every transaction that contains 2 parties goes through the same steps. First the initiator creates a new contract (often referred to as a Slate) which is then signed by the counter party and finally, signed by the initiator. This is the same regardless if the flow is regular (payment) or invoice. These two flows can be joined to provide for a unified contract building experience: new -> sign -> sign.

We have 2 actions here namely new and sign. I think it’s good if we completely detach the contract building from its transport to the counterparty as it allows for seamless composition of functionality. This is also why there is no notion of a “destination” in the contract new and sign functions. Instead, we merely pick who we want to encrypt the slate for.

The prototype builds transactions a bit differently than they currently are. Some notable differences are:

  1. A transaction is a payjoin by default
  2. Each party contributes for their own fees, including for the kernel - each pays kernel_cost * (1/num_participants)
  3. By default transactions are late-locked (early-locking is not yet supported)

Prototype implementation

Installation
  1. Clone my git repo git clone https://github.com/phyro/grin-wallet.git
  2. Switch branch git checkout simple_contracts
  3. Build branch cargo build
  4. Go where the binary is located cd target/debug
  5. Call commands with ./grin-wallet --testnet ...

NOTE: If you lack some dependencies and can’t build the project, vegycslol shared with me a configuration to get these over nix. We first install Nix package manager and then the dependencies through by doing:

  1. run sh <(curl -L https://nixos.org/nix/install) --daemon and install nix
  2. open a new shell session
  3. run git checkout simple_contracts, go inside the grin-wallet directory and type nix-shell. This will download all dependencies needed to build grin-wallet (and grin) including tor. It also puts you inside a nix-shell where all these dependencies are available. From there, cargo build should work.

In case anyone is concerned about what it is they are going to build, the changes are on this branch and the nix packages are defined in default.nix file. The code is not a great example of engineering, but this would be improved. Run in a wallet where you have no mainnet accounts/value (for safety!) and only test against the testnet. This functionality only supports the main/default wallet account at the moment. Payment proofs are also not supported right now. I’d prefer if contracts only supported early payment proofs by @tromp because they seem like a strictly superior variant and on top of that, they fit well in this design due to their ability to work in both directions.

Example usage

For demonstration purposes, I’ll shorten the addresses to save some space when using the commands. The commands below assume you have a testnet grin node running so you can send the transaction on the network. Suppose we have two parties:

  • party A: tgrin1y4rqquklzykma8fjyksfhcmqa
  • party B: tgrin1y4rqquklzycaa8fjyksfhcmqb

For now we can test the contract building with

##### Payment flow

# 1. Party A creates a new contract and encrypts it for B
./grin-wallet --testnet contract new --send=0.2 --encrypt-for=tgrin1y4rqquklzycaa8fjyksfhcmqb
# 2. Party B signs it and automatically encrypts it back for A
./grin-wallet --testnet contract sign --receive=0.2
# 3. Party A signs it at which point it's broadcasted to the network
./grin-wallet --testnet contract sign
# Party A didn't need to again describe how much they send/receive,
# because already did it when calling 'new' with --send=0.2

##### Invoice flow

./grin-wallet --testnet contract new --receive=0.2 --encrypt-for=tgrin1y4rqquklzycaa8fjyksfhcmqb
./grin-wallet --testnet contract sign --send=0.2  # party B signs
./grin-wallet --testnet contract sign             # party A signs

# Note that the flows above look the same. It's important to understand
# the fact that we have signed a transaction for contract revocation.

##### Self-spending by party A
./grin-wallet --testnet contract new --send=0 --num-participants=1 --encrypt-for=tgrin1y4rqquklzykma8fjyksfhcmqa
./grin-wallet --testnet contract sign

As mentioned, by default these transactions are made such that every party contributes for their own fees and they are payjoins by default. It is possible to opt out of a payjoin by calling with --no-payjoin. At the moment, every party creates exactly 1 output. This may become configurable to be able to create self-spends that look like other regular 2-2 contracts. We can also tell the software to use specific commitments by providing --use-inputs flag e.g.

# Self-spending consolidation (we enumerate which commitments we want to use as inputs)
./grin-wallet --testnet contract new --send=0 --num-participants=1 --encrypt-for=tgrin1y4rqquklzykma8fjyksfhcmqa --use-inputs=084fb2500a00f19138600f2d94c41f29f80852f7744c26c1a3e83c2af5ee83b3fc,08a7a703388a82dd735c259a55b11cf66e8ce254b2c0745c34173f432b58ba9204
./grin-wallet --testnet contract sign

We also have revocation of a contract (safe-cancel tx) which reverts the expected transaction changes as well as double-spends an input through a 1-1 self-spend if inputs were contributed

./grin-wallet --testnet contract revoke --tx-id=68

To observe what’s going on, ./grin-wallet --testnet txs and ./grin-wallet --testnet outputs are your friends.

Here at the end of the code is a quick example of contract API functionality in python.

Later on, I think it would make sense to add a few more commands e.g.

# Listing contracts - lists open contracts by default (either setup or sign performed, but not confirmed)
./grin-wallet contract list
./grin-wallet contract list --all
./grin-wallet contract list --filter=signed,unconfirmed

# Viewing contracts
# If we have received a contract we want to know what it looked like and what we contributed to it. This
# way we can review the contract and send it again to the person if they lose the slatepack. This is still
# something that would need to be defined what it would look like.
./grin-wallet contract view --id=1

# Transporting contracts
./grin-wallet contract transport --tor --grin-addr=tgrin1y4rqquklzycaa8fjyksfhcmqb

To obtain testnet coins

One way to get testnet coins is through faucet. The faucet communication works through TOR so you need to have it installed on your system and then run grin-wallet --testnet listen to listen for TOR communication (when you run this command it will output that you don’t have TOR if you don’t have it installed). Then run curl -d ‘{“address”: “”}’ http://faucet.testnet.grin.lesceller.com - that’s basically “please send me 1 grin”. You get 1 grin for each call, can do this up to 5 times i think - probably per day or smth - thanks to quentinlesceller for the faucet

Testnet exchange integration

@vegycslol was kind enough to integrate this flow on his testnet exchange so anyone can test sending/receiving from/to the exchange - you can of course opt-out of a payjoin when interacting with the exchange. Feel free to play around with this.

Far from finished and not to be used with wallets that carry value!

I’d like to remind everyone that this is not close to being finished or ready to use with wallets that also hold real coins. We’d need to agree on the interface, try out things, receive feedback, have a really careful and long review to find any potential mistakes and/or attacks and potentially bump the slatepack version because now parties pay for their own parts. Please Let us know of any feedback you may have and we’ll discuss this. I suggest any discussion around this are either on the forum (if the comment doesn’t expect a quick answer) or, preferred, on keybase for a smoother interaction.

13 Likes

This is great stuff. Thanks to both of you for contributing new ideas and code

1 Like

I’ve got some questions about the proposed command syntax.

# 2. Party B signs it and automatically encrypts it back for A
./grin-wallet --testnet contract sign --receive=0.2

How can (for example in Payment flow) signing command work without some kind of handle of the contract to sign?

./grin-wallet contract view --id=1

“–id=1” is kind of a contract handle?

Question about payjoins: They can improve privacy and bandwidth, but don’t reduce count or size of kernels? Only new Outputs that become spend within the payjoin, and normally would get removed after they become part of the blockchain, can be removed (before) instead of become ever part of the blockchain?

After you call contract sign command it asks you to paste the slatepack, that’s how it knows which contract to sign

No, they don’t reduce count or size of kernels. They do reduce (vastly) the increase of utxo set size if they’re used by default since most transactions would be 2-2 (meaning the utxo set didn’t increase) instead of 1-2 as it is now (meaning utxo set increases by 700 bytes). In terms of privacy i see it as “when you payjoin you reduce your privacy against the sender while increasing it for the sender” which might sound silly, but if everyone does this, then everyone’s privacy increases. Also in theory the payjoined inputs might be from mwixnet, so it wouldn’t even decrease receiver’s privacy

5 Likes

On top of that, they require a replay attacker to create the input receiver used before they could perform a replay. This means that if I send you 3 coins in a 1-2 tx where you get 1 output, once you spend that output, I can’t broadcast the same transaction again. For a 1-2 tx, this is possible because the only input was from the sender who can recreate it.

Let me add another point about mwixnet and payjoins. If just one of the parties uses a coinswapped output as input where the coinswap tx had 20 inputs and 20 outputs, then for a 2-2 payjoin the following two statements are true:

  1. The sender is one of the 20 inputs of the coinswap tx or 1 from 2-2 tx meaning the true sender input could be one of the 21 inputs in practice (with mwixnet assumption)
  2. The same is true for the receiver

So just one party adding a coinswapped input changes a lot as I think both the sender and the receiver obtain the coinswap anonimity set with the probability of 1/2.

Additionally, when you have late locking, the party at step2 won’t be able to tell whether the other party will contribute a coinswapped input or not until the tx is broadcasted. They can’t prevent them from using one. Late locking has more problems with fee estimation though as you assume roughly the same input availability.

Edit: to make it clear, it’s still 1/2 probability that the input is from the sender/receiver, but one of those two has an additional anonimity set of 20 and you can’t tell whether that’s the sender or the receiver meaning it benefits both of them.

3 Likes

this and next command could make it easier for some people to follow if you spell out the steps. Before this would cd grin-wallet then cargo build. Next step would be cd target/debug/ etc.

Unfortunately I cannot test because build failed on ARM (edit the mimblewimble repository wallet does build on ARM fyi)

Yeah, cargo build builds the project you’re in so you have to change directory. Hmm, sorry, I only targeted x86-64 for this prototype and have not tried building it on ARM so it’s quite possible it doesn’t work on that architecture right now. Thanks for trying it out though!

As described in (mostly) Lock free transactions - #20 by oryhp, I think the early-lock & late-add is interesting because it brings robustness to fee estimation. Regardless of when we lock the inputs, we can always do the late-add at the sign step without any drawbacks (as far as I can tell) which is why I’ll add this as the only behavior in the contract code. I welcome any feedback on this and am open to being convinced other approaches may be more suitable. I’m especially interested in feedback whether early-lock or late-lock should be the default. The tradeoff seems to be stability (you can’t start a contract which you can’t finish) vs flexibility (just in time input selection).

1 Like

The wallet should in general keep track of a list of incomplete txs; those for which it has already determined the fee, but not signed yet, and thus not (necessarily) assigned inputs for yet. It should determine fees in such a way that such an assignment is always possible. When asked to perform another spend for a certain amount, the wallet can decide if it’s able to satisfy an extended list for some appropriate fee.
It may not be able to for any of the following reasons:

  1. Insufficient balance, regardless of available inputs.

  2. Enough balance, but no possible assignment of inputs.

  3. Possible assignment of inputs, but no feasible assignment.

Here, feasible differs from possible because the wallet must use an efficient algorithm to determine input assignment, such as a greedy algorithm. Thus, the wallet is not even able to distinguish case 2).
It simply reports to the user that there’s no inputs available for the new tx.

In this formulation, input locking is just one possible cached implementation of the fee determination algorithm.

1 Like

If I understand your idea right, we have a set of open and unsigned contracts S for which we claim we could pick inputs such that we’d be able to complete all contracts in S at once. When we try to open a new unsigned contract, the logic just tries to add another contract to S while keeping this invariant?
So we can report false negatives where we’ll say we don’t have enough inputs when in fact there may exist a combination that satisfies all of them, but we’ll never have a false positive?

Edit: This sounds like a flexible early locking variant where you claim you can satisfy locking in the future

1 Like

Correct; I’m describing the most flexible wallet behaviour, and how any implementation can be viewed in terms of how it implements the method to decide whether the list of open contracts can be extended with one more amount to send or receive (the latter may also be impossible if the wallet insists on payjoins and not enough inputs are available).

I believe this may need to assume that the input/output selection strategy is defined as soon as possible (at the key setup phase) which is at contract new for the initiator. Otherwise, you might add a contract to S because there would exist a way to do it given a flexible strategy, but later on when we provided a selection strategy at the sign step, we may not find a way to make it work under these new selection constraints.

Thinking about these selection strategies now, I think the other variant you described on keybase where you give a guarantee that the fee estimation can be satisfied for contracts in S (which doesn’t necessarily mean you can complete all contracts at once or even a single contract if the total amount is too low for any contract) has the same issue, where you’d have to define the selection at the setup phase, or you can’t make any guarantees (well, you can, but you’d have to find another solution for the updated contract constraints).

Yes; I was assuming a fixed assignment satisfiability algorithm. I’m not sure the current choice of selection strategies is appropriate for payjoins anyway…

Perhaps picking a selection strategy should be possible only before the setup phase (impossible at step3) and if a strategy is provided (i.e. any constraint for inputs/outputs), an early-lock happens. So a non-default selection strategy, no matter how detailed, makes it an early lock.