Sapling test contract. Is it safe / audited ? Ready for mainnet?

With the adoption of sapling contracts and shielded transactions with Edo, there has been a lot of talk about the integration of confidential transaction into main stream applications.

Sadly there is no “visible” progress in this matter.

The documentations for the sapling integrations includes a test contract for a shielded Tez pool with a 1 to 1 conversion to (unshielded) Tez.

https://tezos.gitlab.io/010/sapling.html
https://gitlab.com/tezos/tezos/-/blob/master/src/proto_008_PtEdo2Zk/lib_protocol/test/contracts/sapling_contract.tz

While It’s currently only usable with cli, I’m thinking about locking some funds in this contract and incentivize some usage.

Hence my question if it is safe to lock larger Tez amount in this contract?

4 Likes

@NomadicLabs can you comment on this?

No answer…

Airgap has added support for sapling since August, when will the mainnet contract be deployed?

you really don’t see this level of ghosting the community anywhere else
@NomadicLabs @murbard

I don’t know if that code’s been audited but it’s very straightforward and only about 40 lines of well documented code, all the heavy lifting happens in SAPLING_VERIFY_UPDATE, not really in the contract itself.

However, it doesn’t really make sense to use it as such, as the absence of delegation would create an incentive to go in and out of the pool which would hurt privacy. This is essentially the type of use case ctez exists for.

2 Likes

It only takes a few minimal tweaks, for instance

storage (sapling_state 8);
parameter (list (pair (sapling_transaction 8) (option address) ) );
code {
       # Do not accept tez
       AMOUNT;
       PUSH mutez 0;
       ASSERT_CMPEQ ;
       # Get ctez contract entrypoint (or other)
       PUSH @ctez address "KT1SjXiUX63QvdNMcM2m492f7kuf8JxXRLp4%transfer" ;
       CONTRACT (pair nat (pair address address)); ASSERT_SOME ; 
       SWAP;
       # Stack manipulation
       UNPAIR;
       NIL operation; DUG 2;
       ITER { UNPAIR;
              DIP { SWAP };
       # We verify the transaction and update the storage if the transaction is
       # valid. The shielded transactions are handled here.
       # The new state is pushed on top of the stack in addition to the balance
       # of the transaction. If the rest of the script goes well, this state
       # will be the new state of the smart contract.
              SAPLING_VERIFY_UPDATE;
       # In the case of an invalid transaction, we stop.
              ASSERT_SOME;
              UNPAIR;
              DUP;
              # We have three cases now: unshielding, shielding and transfers.
              # If the balance is strictly positive (i.e. unshielding), we send funds
              # to the given address.
              # If no address is given (see ASSERT_SOME), we stop
              IFGT {
                     ABS @sapling_balance_is_positive;
                     DIIP { ASSERT_SOME };
                     SWAP;
                     DIP {
                           SWAP ; RENAME @to;
                           SELF_ADDRESS @from;
                           PAIR; SWAP; PAIR @param;
                           DUP 3;
                           SWAP;
                           PUSH mutez 0;
                           SWAP ;
                           TRANSFER_TOKENS;
                           CONS };
                   }
                   {
                     DUP ;
                     IFLT
                       {
                         # If the balance is negative, we are shielding. The spend  will have to have been authorized in the fa1.2 contract
                         SWAP ;
                         DIP
                           {
                             ABS ; SELF_ADDRESS @to ;
                             DIG 2 ; ASSERT_SOME ; RENAME @from ;
                             PAIR; SWAP ; PAIR @param;
                             DUP 3;
                             SWAP;
                             PUSH mutez 0;
                             SWAP ;
                             TRANSFER_TOKENS ;
                             CONS ;
                           };
                       }
                       {
                         # Internal transfer
                         DROP ; SWAP ; ASSERT_NONE ;
                       };
                   };
            };
       DIG 2 ; DROP ; SWAP ; PAIR ;
     }
 
2 Likes

Sorry for the late reply.

Like @murbard pointed out, most of the complexity of the Sapling protocol is hidden in the Michelson instruction SAPLING_VERIFY_UPDATE, the rest of the contract logic should be auditable by any Michelson developer. No special expertise is needed.

Our goal was to integrate a privacy enhancing solution as generic as possible. We therefore did it as a Michelson instruction. The contract we provided is just a simple example that we developed for testing and educational purposes.

There are many ways in which Sapling can be used inside a product, we are happy to provide support to any interested developer.

2 Likes

With Airgap launching, I had another look at the contract over the weekend. As I’ve said above, it’s pretty straighfoward code, but it also has a subtle issue. SAPLING_VERIFY_UPDATE only returns the unshielded amount, but it doesn’t bind the proof to the output address. This makes unshielding transactions malleable.

While it’s technically possible to use Sapling as is on Tezos with a commit/reveal scheme, writing a safe and practical contract would require a version of SAPLING_VERIFY_UPDATE that binds the proof to a given output, in our case the optional address. It’s not a difficult fix, but it is a protocol level change. I contacted the AirGap team when I spotted the issue and there was only about 68 tez in the pool.

5 Likes

We agree with @murbard in his observation that this sapling contract, as deployed on mainnet, is indeed vulnerable.

We are working towards producing a patch to fix this issue, which will be included with the next protocol proposal J.

In the meantime it is not advisable to rely on this contract as is.

4 Likes

At the end of last week, we at AirGap announced the integration of Sapling into our solution.

This was the first known implementation of Sapling into a product. Unfortunately, an issue with Sapling was spotted by Arthur Breitman - as a precaution we acted quickly to disable this integration.

It appears that with the current integration of Sapling, it is possible that the address to which funds are unshielded to, can be changed while the signed operation is in the mempool. As the current contract that AirGap uses holds only an amount of 68 tez, this flaw was fortunately caught in a timely manner.

We advise any users who have locked funds to reach out to us on Discord, Telegram or by Email.

The Nomadic Labs team is aware of this and has also confirmed they are working on a fix that will be proposed for inclusion in the “J” protocol proposal.

We are excited to reactivate this feature once the fix is implemented so users can use Sapling from our solution.

5 Likes

After the announcement of this vulnerability, we have developed a new, safer integration, for Sapling transactions into Michelson smart contracts. This has already been merged into the Tezos master branch, and will be a part of an upcoming “J” protocol proposal.

More details can be found in our latest blog entry: https://research-development.nomadic-labs.com/fixing-the-sapling-protocol-integration.html

Notice that this new version of the integration is part of the Tezos economic protocol, and as such it will only be available if protocol proposal “J” is accepted by the community, and only after it becomes active on mainnet.

Until then, we advise against originating new Sapling contracts on mainnet, and to avoid interacting with existing deployed Sapling contracts.

2 Likes

are you launching it on jakartanet (testnet)?
would be great if we could try it out prior to the mainnet launch.

this is the fixed contract for J right?