Closed charithabandi closed 7 months ago
Following up on this, just to make sure we're on the same page:
It seems like the best solution is probably to make validators also act as the oracles for the network. As discussed, there are two ways this could occur:
Both of these would only allow unidirectional transfers, from the source chain to Kwil. @charithabandi is looking into how we can do something along the lines of #2.
In my opinion, it seems that the easiest way to trigger these would be to have Kwil nodes be able to detect events emitted from a smart contract. The event would act as their signal that they should do something (like increment a balance). I'm open to other ideas on this, this is just a naive approach.
Am I on the same page with how we are attacking this?
We can definitely make use of ABCI++ vote extensions to do this. Just a brief on Vote extensions in ABCI++: CometBFT introduced 2 new methods to extend the pre-commit phase.
Through ExtendVote, each application can add additional information as part of the pre-commit vote. In our case, this data can be the list of assetLocked events on the eth smart contract potentially signed by the validator. These vote extensions are then broadcast to the other validators for verification.
If the application doesn't have any vote extension information to add, it can just return a 0-length byte array.
This method is called on all Validators on the receival of vote extensions. Using this method, other validators can validate the vote extension data attached. If the validation fails, the pre-commit message is deemed invalid and ignored. Validation can be something like verifying the signatures of the events in our case.
This verification process has to be deterministic and should depend only on the on-chain data (to support replays). We should be careful in attaching right voteExtensions, because failure in validation can lead to liveness issues as the pre-commit votes are ignored for failed validations.
All the voteExtensions extended by the validators in the previous pre-commit phase are available only to the proposer proposing the next block. Proposer can now accumulate all the votes and issue mintToken transactions when he sees atleast threshold signatures are received from other validators for a lockedAsset event on the smart contract. Can use multisig here.
Once this block is accepted and committed, all the nodes will locally mint the tokens and update the account store state.
Vote extensions are not part of the blockchain, they are not stored on the blockchain. They are created at the end of the round and consumed in the PrepareProposal in the next round. Will there be any issues with replay? or for a node catching up?
ABCI++ added new configs to enable vote extensions: Genesis file:
"abci": {
"vote_extensions_enable_height": "0"
}
We spent most of the day yesterday independently researching how to support withdraws. After discussing our findings, I think we agreed that it is feasible to do this with validators authorizing withdraws in two general models:
In both approaches the authorizor (or list of authorizors as in the explicit multi-sig case) would be updateable.
Without getting into much detail on how a withdraw or batch of withdraws is proposed and specified within the contract, the general characteristics of approaches are described below.
Each validator would approve some proposed withdraw, and the contract counts authorizations. The threshold would be the same as consensus, 2/3. Contract would have a list of authorizer/validator pubkeys.
General options for submitting and counting the authorizations: a. Collecting all signatures could be done off-chain (i.e. in Kwil p2p and consensus), and all pubkeys+signatures passed in a single transaction into a withdraw/authorize_withdraw contract method. b. Each validator could submit their authorization tx for validation and recording in contract storage.
How to update the authorizer key set? A validator set change would involve an update_authorizers
method call txn, which would also be multi-sig.
This is complex Solidity, and requires more transactions, and more gas.
A regular signature is produced in kwild via a threshold signature scheme (there are many). In short, there is a collective public key (typically P
or Q
), and each validator has a secret share (s_i
), which is a piece of a collective private key that is never actually revealed. There are two main components of making this scheme work:
P
and each s_i
, which are kept private.The Ethereum caller / sender must match a single authorized withdrawer in the contract.
This has the advantage of fewer Ethereum transactions, and a simpler contract. The complexity is in the threshold signature creation in kwild.
In this approach, can we avoid complex solidity and high gas consumption.
https://github.com/ZenGo-X/awesome-tss https://github.com/MixinNetwork/tip#decentralized-network-setup https://github.com/taurusgroup/multi-party-sig https://github.com/torusresearch/torus-node#key-generation- https://github.com/dedis/kyber/tree/master/share/dkg https://eprint.iacr.org/2021/060 https://gitlab.com/thorchain/tss/go-tss (forked and maintained binance's) https://github.com/bnb-chain/tss-lib https://docs.mantle.xyz/network/introduction/concepts/threshold-signature-scheme https://mananmonga.github.io/posts/Threshold%20Signatures.pdf https://github.com/coinbase/kryptology https://eprint.iacr.org/2020/852.pdf https://gist.github.com/BjornvdLaan/ca6dd4e3993e1ef392f363ec27fe74c4 (BLS in solidity, interesting, but not what we want), https://hackmd.io/@liangcc/bls-solidity https://github.com/orbs-network/bgls https://github.com/Project-Arda/bgls
There are many more libraries and algorithms. We need to decide which can work with us, and if we should use the primitives only, or the full comms protocols and how to integrate with CometBFT. ThorChain's "ThorNodes" are built with Cosmos SDK and they have their own fork of Binance's TSS libs, so this is a good place to start: https://gitlab.com/thorchain/thornode. Taurus Group's looks promising too: https://github.com/taurusgroup/multi-party-sig
What scheme should we use to derive secp256k1 keys from the ed25519 keys, or do we actually need another independent key per-validator?
How to initialize and rotate the authorizer key(s) e.g. on validator set change? Thoughts:
How to specify the transfer being authorized? User + amount? Perhaps mapping(address => uint256) public authorizations;
There are some questions about what party would do the actual transfer txn, or if it would even be separate from the approvals.
Loose plan for wiki pages:
Thanks for putting this together, very helpful.
I'm sure you've already thought of this, but one immediate question is: how would the system handle changes to the validator set?
Validator set updates would require the DKG component to generate new secret shares and thus a new collective public key. In some libraries this is called a "refresh" of the key. An update method on the contract would then update the authorizor key, which would presumably be the responsibility of the block proposer that includes the validator set updates. The exact mechanics and sequence of operations isn't well defined yet however.
Illustrative solidity
contract Owned {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner, "You are not the owner");
_;
}
function changeOwner(address newOwner) public onlyOwner {
owner = newOwner;
}
}
For derivation of ecdsa keys from the eddsa keys, I'm thinking we use the BIP32 child key derivation scheme with a master key seeded by the ed25519 key. It's battle tested, deterministic, and can be seeded with variable length data from 128 to 512 bits. I'm looking at other KDFs though, possibly an application for argon2 or pbkdf2, but I think simplicity is important in this case. There's lots of existing code for all of these
Validator set updates would require the DKG component to generate new secret shares and thus a new collective public key. In some libraries this is called a "refresh" of the key. An update method on the contract would then update the authorizor key, which would presumably be the responsibility of the block proposer that includes the validator set updates. The exact mechanics and sequence of operations isn't well defined yet however.
I am thinking on this a bit, Do we need to trigger a new KeyGen process whenever we update the ValidatorSet or just trigger a reshare process and keep the Pubkey same? This should handle the scenarios where the previous validators don't exist anymore. Do you see a value in regenerating a new public key in terms of security?
For sure! That would be the best solution, and the key on the contract would never change. I wasn't sure if that kind of refresh operation that kept the collective key the same would exist, but it seems to be supported in some of the packages. e.g. https://github.com/taurusgroup/multi-party-sig/blob/4d84aafb57b437da1b933db9a265fb7ce4e7c138/protocols/cmp/cmp.go#L47-L50
// Refresh allows the parties to refresh all existing cryptographic keys from a previously generated Config.
// The group's ECDSA public key remains the same, but any previous shares are rendered useless.
// Returns *cmp.Config if successful.
func Refresh(config *Config, pl *pool.Pool) protocol.StartFunc {
That would be awesome if it doesn't have the caveat that all parties need to be involved, just the threshold.
Oh wait, can a new validator that didn't have a previous secret share be part of the pool in that Refresh operation?
I'm really starting to like taurusgroup's implementation of the CGGMP21 protocol. It's way cleaner and more technically up-to-date than binance or thorchain's fork.
For sure! That would be the best solution, and the key on the contract would never change. I wasn't sure if that kind of refresh operation that kept the collective key the same would exist, but it seems to be supported in some of the packages. e.g. https://github.com/taurusgroup/multi-party-sig/blob/4d84aafb57b437da1b933db9a265fb7ce4e7c138/protocols/cmp/cmp.go#L47-L50
// Refresh allows the parties to refresh all existing cryptographic keys from a previously generated Config. // The group's ECDSA public key remains the same, but any previous shares are rendered useless. // Returns *cmp.Config if successful. func Refresh(config *Config, pl *pool.Pool) protocol.StartFunc {
That would be awesome if it doesn't have the caveat that all parties need to be involved, just the threshold.
Oh wait, can a new validator that didn't have a previous secret share be part of the pool in that Refresh operation?
Yes, binance-tss supports re-sharing of keys keeping the public key constant. It has concept of Old Committee and NewCommittee and I think the Old Committee has to send new shares of their local keys to the new comers and the rest of the process is technically the same as newKeyGen.
Let me also look into the taurus group's implementation.
I'll start the wiki pages today I suppose, but echoing one thing from chat:
https://github.com/SwingbyProtocol/tss-lib is one of many forks for the binance lib, but update for GG20 one-round signing. It's apparently used for their SkyBridge: https://swingby.network/
Their contract is quite complex. https://github.com/SwingbyProtocol/skybridge-contract/blob/v3/contracts/SwapContract.sol They are trying to collect fees, deal with rewards, etc.
Lastly, it's worth noting that they have a churn
method for updating contract owner, but it also seems to be concerned with multiple "nodes" rather than just a single key yet it refers to TSS
We have Kwil-Eth-Bridge Deposits support. Track the withdrawals on a different issue.
Let's use this thread to discuss bridging solutions and specific implementation details.
We want to develop a bridging solution using which we can transfer ERC20 tokens from Ethereum to Kwil and vice versa.
Potential Solutions:
Optimistic Bridges:
Pros: Semi Trusted, 1-of-N Security model (very secure) Cons: High latencies as we need to go through the optimistic challenge timeouts which can last for around 7 days or more.
External Validator-Set Bridges
The validity of the "Deposit Locked" or "Tokens Burned" events is verified by the external validator set. It's up to us if we want to use the same set of validators to control these operations.
Things to figure out:
Centralized or Decentralized
Escrow contract on Ethereum:
Validating the deposit lock transaction:
Governance: