Git Product home page Git Product logo

mobius's Introduction

Möbius

Build Status

Trustless Tumbling for Transaction Privacy

Introduction

Möbius is a Smart Contract that runs on Ethereum that offers trustless autonomous tumbling using linkable ring signatures.

This proof of concept is still evolving and comes with the caveat that it should not be used for anything other than a technology demonstration.

White Paper

S. Meiklejohn, R. Mercer. Möbius: Trustless Tumbling for Transaction Privacy

Using Möbius

👉 See the full Tutorial


Möbius supports ether and ERC20 compatible token transactions. In order to use the Mixer with ERC20 compatible tokens, the DepositERC20Compatible and WithdrawERC20Compatible functions must be used. However, in order to do an ether transaction, one has to use the DepositEther and WithdrawEther functions.


To tumble a token it is deposited into the Mixer smart contract by sending the token and the stealth public key of the receiver to the Deposit method.

The Mixer contract places the token into an unfilled Ring specific to that token and denomination and provides the GUID of the Ring. The current ring size is 4, so when 3 other people deposit the same denomination of token into the Mixer the Ring will have filled. Tokens can only be withdrawn when the Ring is full.

The receiver then generates a linkable ring signature using their stealth private key, this signature and the Ring GUID is provided to the Withdraw method in exchange for the token.

The lifecycle and state of the Mixer and Rings is monitored using the following Events:

  • LogMixerDeposit - Tokens have been deposited into a Ring, includes: Ring GUID, Receiver Public Key, Token, Value
  • LogMixerReady - Withdrawals can be now me made, includes: Ring GUID, Signing Message
  • LogMixerWithdraw - Tokens have been withdrawn from a Ring, includes: Ring GUID, Tag, Token, Value
  • LogMixerDead - All tokens have been withdrawn from a Ring, includes: Ring GUID

The Orbital tool can be used to create the necessary keys and to create and verify compatible ring signatures, for details see the Orbital Integration Tests.

Caveats

  • #34 - Gas payer exposes sender/receiver
  • #22 - Only Ether is presently supported
  • #32 - Tokens are locked into the Ring until it's filled
  • #12 - Withdraw messages can be replayed

Gas Usage

Despite being an improvement over the previous iteration which used a Solidity P256k1 implementation, the new alt_bn128 opcodes are still expensive and there are many improvements which can be made to reduce these costs further. If you have any interesting optimisations or solutions to remove storage and memory operations please open an issue.

Currently the Gas usage is:

Function Avg
Deposit 150k
Withdraw 725k

Developing

Truffle is used to develop and test the Möbius Smart Contract. This has a dependency on Node.js. solidity-coverage provides code coverage metrics.

Prerequisites:

yarn needs to be installed (but npm should work just as well).

yarn install

This will install all the required packages.

Start testrpc in a separate terminal tab or window.

yarn testrpc

# in separate window or tab
yarn test

This will compile the contract, deploy to the Ganache instance and run the tests.

Testing with Orbital

The Orbital tool is needed to generate the signatures and random keys for some of the tests. If orbital is in $PATH the yarn test command will run additional tests which verify the functionality of the Mixer contract using randomly generated keys instead of the fixed test cases.

mobius's People

Contributors

antoinerondelet avatar daragao avatar dependabot[bot] avatar harryr avatar shapeshed avatar shirikatsu avatar tbm avatar zoenolan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mobius's Issues

Don't rely on the `orbital` tool to perform half of the tests

Without the orbital tool, code coverage is poor and the Withdraw tests aren't run. The reason for this is to make sure the Orbital tool works with the Mixer, but also because the Mixer uses globally unique ring IDs which will change every test run which makes using static test data a pain.

Analyse probability of linking input to output through a ring, or multiple rings

For example:

  • Alice Deposits into a Ring of size 4 (A)
  • All inputs are Withdrawn into 4 other Rings (B, C, D & E), each of size 4.
  • This requires 12 additional Deposits.

When a Withdrawal is made from A, the probability that it is Alice is 1/4.

When a Withdrawal is made from B, C, D or E the probability that it is Alice is at least 1/16.

For more information, see:

Deposit function in Mixer doesn't check denomination matches tx.value

The current mixer only supports ether deposits and withdrawals, however the deposit function does not check that the ether value passed matches the denomination in the function parameter. This could cause this ring or other rings withdrawals to fail, as insufficient funds would be held by the contract.

Tokens are locked into the Ring until it's filled

There is no way to recover tokens from an unfilled Ring.

Some options are:

  1. Make the remaining deposits to fill the ring and unlock funds
  2. Allow removal of a specific participant (and their token) by providing their non-anonymous signature

The first is potentially expensive and an observer will see that the same gas payer has filled the ring.

The second will link the gas payer to the key owner.

Withdraw checks possible enhancement

Within the Mixer contracts Withdraw function the signature is checked for validity, including checking for the existence of the tag, then the 'tag' is added to ensure the same signer doesn't attempt to withdraw again.

Would this not be more readable if this were split into two checks, one where we require the signature tag not to already exist and the second to validate the signature.

require (AddTag)
require (SignatureValid)

The AddTag function would check for the existence of the tag, and return false if tag already exists or add and return true if it doesn't. This would also be consistent with the AddParticipant logic in the Deposit function.

Withdraw messages can be replayed

If a user submits a Withdraw message there is nothing preventing others from replaying the same message.

This is a concern if:

  1. The Withdraw fails due to lack of Gas
  2. The replayed message to be processed before the real message (e.g. no guarantee of ordering)

Until this problem is solved it would be possible for an attacker to monitor all rings and retransmit Withdraw messages with no cost of failure.

Destroy contract after use

The mixer contracts are short-lived and one-time-use contracts and thus there will be plenty of them deployed contiuously.

For that reason, I think it's a good idea to destroy the mixer contracts after use. The data in it is no longer useful after all withdrawals.

Add support for ERC-223 tokens

It is possible to make the mixer support both ERC-223 and ERC-20 tokens as well as Ether in the Mixer contract. It's also possible to make it send Ether to smart contracts too, or send tokens then invoke a method on a smart contract.

In the ERC-223 and ERC-20 case the Mixer calls the transfer method of the token.

When the token address is 0 this is synonymous with Ether, if not zero the Mixer must validate the contract exists by calling extcodesize(addr).

ERC-223 tokens also support an optional arbitrary data field.

To make arbitrary smart contract calls the Withdraw caller must specify the msg.data field, contract address and Gas limit.

e.g. WithdrawToContract(ring_id, tx, ty, ctlist, address, gas, msg_data)

With this construct you make the Mixer call its self to Deposit again without needing a utility method for every conceivable thing the user may want to do, but it opens up a security hole for anything that relies on tx.origin rather than msg.sender

All of these parameters need to be included in the hash.

For now, I think it would be good to split the Mixer.Withdraw function into multiple variants to avoid convoluted code paths.

  • WithdrawERC20
  • WithdrawERC223
  • WithdrawEther

And maybe functions like:

  • WithdrawEtherTo (recipient address)

Solidity best practice

As of solidity 0.4.17, the constant modifier for functions has been deprecated in favour of the pure or view modifiers. The contracts still contain a number of functions using the deprecated constant modifier.

Within some of the contracts I still see a mix of uint and uint256 being used (uint is an alias for uint256). IMHO best practice is to specify the size explicitly.

It might be worth fixing the version of solidity to ensure everyone is running the same tested bytecode. The current version pragma using the caret syntax allows for solidity versions >=0.4.18 <0.5.0.

In Mixer.sol the types of variables are not set explicitly (using var), lines 110,111,180.

Incentivised/Proxy Withdraw to hide identity of key holder

As with all Ethereum transactions the address submitting the transaction can be used to trace origins and destinations of payments, however the ring signature will still reduce linkability to a 1/N probability between each sender and receiver.

With the account abstraction improvements expected in Serenity it will be possible to sever this link and enjoy much stronger practical anonymity guarantees, but this isn't ready yet.

With incentivised / proxy withdraw you can let somebody else submit the Withdraw message in return for an amount enough to cover the Gas costs and their time.

The problem is a fluctuation in the Gas price could make it cost more for the submitter than the incentive is worth. The tx.gasprice global variable exists, but the sender needs to verify the transaction can be completed at the current gas price and doesn't revert() (which will cost them)

Combined WithdrawDeposit

The combined Withdraw and Deposit function is used to transfer a token from one ring to another in a single operation.

This could be 'churning' a token, or it could be transferring the token to a new owner.

For example:

  • Alice deposits 1 Token into Ring, using pubx,puby that she controls
  • Alice must wait for the ring to fill up before it can be moved
  • Alice performs WithdrawDeposit, depositing the token into another Ring with another pubx,puby

To do this Alice generates the Withdraw parameters, and passes the additional Ring address and pubx,puby.

This relies on the replay protection modification from #12 to bind the signature to a specific set of parameters.

The only thing necessary for this too be possible is to extend the 'Withdraw' function to include target address and msg.data, this can be used to implement arbitrary smart contract calls.

This requires additional parameters to the Withdraw function, in the case of sending ETH it would be:

  • target address
  • message data

Care needs to be taken to ensure that this functionality cannot be used to perform internal calls or abuse trust relationships, that's a significant problem - if there is any trust relationship between the Ring and a Parent contract the withdraw function must be barred from using the Parent as the Target.

However, with ERC20 the payable function attribute isn't really applicable - e.g. you can send tokens to a contract and all it does is modify a counter in the Token - but it doesn't invoke the target method on the third-party contract.

output = Token.transferFrom(this, msg.sender, PaymentAmount);

The code would have to be changed to something like:

            if (UsingToken) {            
                if( ! Token.transferFrom(this, Target, PaymentAmount) ) {
                    revert();
                }
            }
            output = Target.call.gas(X).value(PaymentAmount)(TargetData)

Where Target is the contract address, and TargetData is the arbitrary msg.data to be passed.

For more information about handling transfer of Gas from one call to another, see: ethereum/solidity#2999

The problem is - the call needs to be performed last, and we may need to save some gas for a handful of cleanup operations.

The second problem is, if you just want to send a token to a target address, it's not necessary to perform the second call() on the target afterwards.

Need to handle the following conditions:

  • Perform token transfer to non-contract address
  • Perform ETH transfer to non-contract address
  • Perform token transfer to contract (without call)
  • Perform ETH transfer to contract (with call)
  • Perform token transfer to contract (with call)

Lets call these two functions:

  • Withdraw (compatible with current function)
  • ExWithdraw (includes additional parameters)

Third problem:

In Deposit, if UsingToken it performs Token.transferFrom from msg.sender, this doesn't work if the function has been chained from the ExWithdraw function.

When using ERC223 this doesn't seem to be a problem because it supports the tokenFallback function. The from parameter of tokenFallback must be the address of the Ring or Parent.

With ERC20 and ERC223 tokens, it seems that it's not immediately possible to perform arbitrary calls and a commbined WithdrawDeposit method must be used to ensure the right actions are performed.

For the sake of simplicity we are making an assumption that, for now at least, we will not do anything special when withdrawing tokens?


This rule of thumb should be followed:

Anybody can send the message, but only the owner can decide what it does.

This means that msg.sender should be ignored, the owner should instead specify who the token/eth should be transferred too. etc.


ERC223 EIP: ethereum/EIPs#223
ERC223 reference implementation: https://github.com/Dexaran/ERC223-token-standard

Cleanup repo

I am wondering whether it would be cleaner to add all community related files (contribution, issue/PR templates and so on) under .github like https://github.com/docker/cli/tree/master/.github or https://github.com/ethereum/go-ethereum/tree/master/.github for instance.

Looking at the current state of the repo, it looks like many images are in this .github folder. We could do something like .github/images and put these images into it, and have the contribution files under .github directly.

What do you think @zoenolan ?

Archive

Description

As per discussion with @zoenolan in the #dev channel in Slack, this repository is to be archived as it is now legacy and work is now carried on in the Zeth repository.

Acceptance criteria

Can compressed curve points save gas or storage?

It is possible to derive the X/Y coords of a point from a compressed X coordinate which includes a sign bit. This halves the size of every point that's passed as an argument or stored.

At the moment the Gas cost of decompressing the point is more expensive than the storage costs.

Move from secp256k1 to bn256.G1

The following changes are necessary:

  1. remove eclib code
  2. add test data generated by orbital for bn256.g1 curve (keep secp256k1 data for reference)
  3. update deposit() to validate points for bn256.g1 [1]
  4. update 'ring full' branch in deposit() to calculate hashx for bn256.g1 [2]
  5. in withdraw() update ring signature calculation [3]
  6. update tests
  7. check if coverage runs without timing out

[1] https://github.com/clearmatics/mobius/blob/master/contracts/Ring.sol#L81
[2] https://github.com/clearmatics/mobius/blob/master/contracts/Ring.sol#L118
[3] https://github.com/clearmatics/mobius/blob/master/contracts/Ring.sol#L162

Improve coverage of Mixer and Ring branch conditions

The following conditions need to be tested:

  • submitting a deposit with a bad point
  • cannot add same public key twice to any ring
  • submitting a transaction with a bad transaction value (e.g. submitting a payment of 1 when the ring size is 5)
  • submitting a withdrawal twice (e.g. duplicate tag check)
  • submitting a deposit twice to the same withdraw address

See code coverage, the require() statements are passing for successful conditions, but need to test the edge cases.

Should the requirement for denominations to be power of 2 be removed?

Currently there is a requirement that any deposit must be an exact power of two, this was enforced for efficiency - making it more likely for rings to be filled.

However, many people may want to tumble tokens in decimal units, e.g. 10**5 or 100000.

Should the power of 2 requirement be removed?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.