Open ycscaly opened 3 months ago
For the other direction, in which a full node + user tries to be malicious without the network, we have to verify light client (either #168 or Sui's native light-client) for the EncryptedUserShare
we pull from the network.
This big task is divided into sub-tasks as follows:
EncryptedUserShare
object, which the blockchain verifies before creating the object (#175)centralized_party::dkg::Output
), and you can make sure the values are the same as the one saved on the blockchain (the decentralized_party::dkg::Output
). After you do, it is safe to sign. (#163) EncryptedUserShare
they should sign that if they believe it (even though they didn't see the DKG output themselves, they trusted the old owner that signed over it, creating a chain of signatures that should date back to the owner that created the DKG).
Potentially, we can even verify this entire chain, dating back to the original user. For now, perhaps it is sufficient that when we "accept" a user-share, we verify the signature, and then sign over the same DKG output - i.e. we should add an accept_user_share()
TS function that takes an EncryptedUserShare
that was encrypted to us, verifies the signature, and then encrypts it to our own encryption key with our own signature. Later, when we want to share it with someone else, we don't add a new signature - we fetch our previous signature from chain and place it there https://github.com/dwallet-labs/dwallet-network/issues/191EncryptedUserShare
object and verify our own signature over it) (#180)Finally, we have to sign our own EncryptionKey
s and add that signature to the chain, and before we encrypt a user-share to someone else, we must make sure that it signed by that address (otherwise the blockchain/full-node/lightclient can create its own encryption key, and fool us to encrypt it to the malicious blockchain/full-node/lightclient) (#171)
A very serious issue that I came across during the encrypt user-share #123 feature.
Although the blockchain verifies that the proof the encrypter sends indeed encrypts the user-share, the user that decrypts the user-share cannot know this, for they didn't verify the proof themselves. At first, I wanted to add a verification that simply multiplies the secret share by the generator and checks if equals the public key share #155. But then we stumbled upon a problem: the receiving user don't know what public key share is to verify the proof against.
A trivial implementation would query the public key-share from the blockchain. However, who says we can trust the blockchain for this value? and indeed we can't.
This brought up an even larger issue. How can we trust any values that come out of an MPC computation (e.g. DKG output, dWallet user share, presign etc.) that are saved on the blockchain? The 2PC-MPC access structure guarntees that even in the case the entire network is malicious the user-share is not compromised and the dWallet is protercted. But in this case, the network can save invalid values in the state, and can cheat and lie and say that the public key share of the user is wrong, and temper with the transfer of the user-share.
True, this is solved if you run your own full-node, for it would verify the proof too, and it would verify that the user that generated the dWallet "agreed" with the DKG output when it finalized its creation and later on used it. But malicious full-nodes are part of the attack threats that we should cover, and indeed we should not have to trust that users run their own full-nodes or use an honest full-node for the dWallet security.
To further demonstrate the threat, consider the following attack: the user creates a dWallet, but doesn't take the DKG output from the MPC session, instead, to know the public key, they fetch it from the blockchain (this is, in fact, how we implemented our TS library and documentation code at the moment). In this case, the blockchain can simply generate a local key, and save its public key as the dWallet key. Since no verification occurs, an address on e.g. Bitcoin will be derived and funds deposited to it. But since the colluding validators hold the signing key (in the clear) they can steal the funds at any time (!)
Instead, we suggest the following principal to be implemented: any output from an MPC session should be signed by the user that participated in it, and saved on the blockchain. This signature should later be verified before using values from this output queried from the state. Although transactions are signed by default, unless you are a full-node they aren't easily accessible, so this should really be the signature saved on the state.
This applies for both DKG & presign. However, if the presign does not involve the user, than it is OK to trust those values.
Here I shall treat the DKG case. The most straight-forward way I can think to mitigate this threat is: add a signature over the dWallet public key, and the network and user public key-shares to the
EncryptedUserShare
object. Increate_encrypted_user_share()
verify that signature before saving it to the object. In the client side TS (#155), verify this signature in addition to verifying the user-share..This covers transfer, but we should also do this for ourselves: when you create a dWallet, you add a signature by yourself to the encrypted user-share you submit on chain for backup (#163). When you fetch the dWallet public key or public key shares, first verify this signature.. We should expose an easy method that takes a dWallet ID and gets these values, so that everyone that ever do this have to go through the signature verification.
I believe that after you received a secret-share, you should also encrypt it to yourself so that it is straight-forward that you approved it, and later on when you use it you can query the public key and verify your own signature (and not someone elses', which will require you to remember who sent the dWallet to you and assure that they are indeed the correct owner and not a maliciously generated address that is controlled by colluding validators.)