Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transcoder Approved Address Lists #7

Open
j0sh opened this issue May 22, 2018 · 4 comments
Open

Transcoder Approved Address Lists #7

j0sh opened this issue May 22, 2018 · 4 comments

Comments

@j0sh
Copy link

j0sh commented May 22, 2018

A thought came up when working on the TLS proposal and reviewing #4.

TLS root certificates are meant to be kept offline and safely under lock-and-key, while day-to-day work is done with intermediate certs. Less handling is required of the root cert, which minimizes the space of things that could compromise the keys. The ability to rotate, revoke and issue new certs is also an important operational safeguard.

The more I think of it, we should build similar safeguards for the transcoder's keys, which could potentially be very valuable targets. Currently, the transcoder's bonded address is "hot" : it is required for calling reward, claim, verify, withdraw and so forth. Any compromise of the transcoder's key is game over for the operator: they cannot replace (or even rotate) their keys without a re-bonding campaign.

To solve this, transcoders could maintain a set of authorized addresses [1] that are allowed to submit certain transactions on behalf of the the transcoder. Most transcoder function signatures would need to take the transcoder's address, for example reward(transcoder_address) [2]. We could still have separate withdrawal addresses.

Altogether, this would allow operators to campaign for a transcoder whose private key existed only on a Ledger, and keep that Ledger in a safe deposit box.

There are also scalability benefits. Transcoders could run multiple physical nodes for redundancy, load balancing, geoip routing, and independent operations without relying on a single, central node. Any damage caused by the compromise of a single node would also be limited to that node. Compromised nodes could immediately have their keys revoked by removing their address from the authorized list.

[1] Rather than maintaining an explicit list of addresses on-chain, HD-type addresses might help here, but that looks like it might be more expensive in terms of the additional metadata and computation needed to verify the root address.

[2] Actually, the reward function itself could probably be called by anyone, rather than an authorized caller. But other functions, eg claim, require more care.

@dob
Copy link
Member

dob commented May 22, 2018

Yes, this is the concept I was getting at with the "hot key / cold key" revocation process in #4. I think the next step here would be for an actual proposal of the protocol changes required to support the authorized accounts concept.

One of the challenges is that the transaction sender is no longer going to be identifiable as a transcoder/delegator directly, and instead we will have to do a lookup to verify the authorization of this sender. At that point, the correct transcoder would be identified and all actions would look as if they were coming from that specific transcoder.

@yondonfu
Copy link
Member

One possible way to achieve is the concept of a validation address which is borrowed from Casper FFG. When a transcoder first registers it could provide the address of its validation contract which can implement whatever validation scheme that the transcoder desires.

Each protected function that is meant to be invoked by a transcoder would accept a signature and message hash. There would be a specific message hash format for each unique transaction. For example, the message hash for the claimWork() transaction might just be the keccak256 hash of [jobId, segStart, segEnd, claimRoot]. The function would call the validation contract using the signature and message hash - if the validation contract returns true the function continues execution and if it returns false the function reverts. One concern here would be the possibility of the validation contract containing non-validation related logic since the transcoder is the one that provides the code. Casper FFG uses a purity checker contract to double check that the validation contract uses a pure function so that no state related funny business can occur. There is also an EVM STATICCALL opcode that allows a contract to call another contract while guaranteeing no state related changes by the callee can happen (I'm not sure what the status of compiling Solidity to use this functionality is at the moment).

If the validation contract needs to be able to authenticate certain signatures for certain message types (i.e. authenticate one signature from a valid address for the claimWork() transaction and another signature from another address for the verify() transaction) we could use a one byte flag to tell the validation contract which message type the signature should be authenticating for. The validation contract can then maintain a mapping tracking which addresses are valid for which message types as indicated by their corresponding one byte flag.

The transcoder could still provide an identifying address on-chain, but instead of protected functions checking if the sender is the identifying address, they will use the validation contract to determine if the caller is allowed to invoke the function or not.

Using the validation contract might be a nice way to separate any additional authentication logic from the BondingManager contract and at the same time allow transcoders to choose their own validation strategy. As a default, a basic validation contract that just checks if a signature for a message hash corresponds to the transcoder's identifying address can be offered if no customization is desired.

@j0sh
Copy link
Author

j0sh commented May 23, 2018

Interesting, so the basic idea is that the transcoder has the option to provide a validation contract that would allow them to define their own authentication logic? The flexibility seems cool, but also feels like we're giving the transcoder access to a particularly potent footgun. Assuming we run with a laissez-faire approach...

Each protected function that is meant to be invoked by a transcoder would accept a signature and message hash ...

What would the benefit of this be, in Livepeer's case? I can see this being useful if the account submitting the transaction is different from the account that executes the function. However, that doesn't seem like a common case for us. Maybe I'm missing something?

More often for Livepeer, we'd have one node execute+submit that has a different address than the bonded transcoder's. In this case, all we need to do is check msg.sender.

If running all the functions through a validation contract doesn't increase per-transaction gas costs too much, then it'd be a neat feature.

@yondonfu
Copy link
Member

Interesting, so the basic idea is that the transcoder has the option to provide a validation contract that would allow them to define their own authentication logic? The flexibility seems cool, but also feels like we're giving the transcoder access to a particularly potent footgun.

Yeah so I imagine that there could be a number of default contracts that the transcoder could deploy if it does not want any further customization. For example, the simplest default where the contract just checks if the signature validates for a single designated address would look something like:

    // Cold key
    address public owner;
    // Hot key
    address public signer;

    function validate(bytes32 _msgHash, bytes _sig) external returns (bool) {
        return ECRecovery.recover(_msgHash, _sig) == signer;
    }

    function setSigner(address _signer) external onlyOwner {
        signer = _signer;
    }

If the transcoder wanted to be able to designate different addresses for different message types (TBH I'm not sure how desirable this is because its a lot of extra key management overhead):

     // Cold key
    address public owner;
    // Hot keys
    // 0 => Reward
    // 1 => ClaimWork
    // 2 => Verify
    // ...
    mapping (uint256 => address) public signers;

    function validate(uint256 _msgType, bytes32 _msgHash, bytes _sig) external returns (bool) {
        require(signers[_msgType] != address(0), "Invalid signer");

        return ECRecovery.recover(_msgHash, _sig) == signers[_msgType];
    }

    function setSigner(uint256 _msgType, address _signer) external onlyOwner {
        signers[_msgType] = _signer;
    }

If the transcoder wanted to use a different signature scheme besides ECDSA (i.e. ring signatures or whatever fancy crypto that it can implement in a contract):

    function validate(bytes32 _msgHash, bytes _sig) external returns (bool) {
        return SomeSignatureAlgoLib.validate(_msgHash, _sig);
    }

What would the benefit of this be, in Livepeer's case?

It would definitely be simpler to skip the signature validation step and instead just check if a particular msg.sender is contained in an authorized address list. I see the main benefits of using signature validation coming from flexibility - with signature validation it does not matter who submits the transaction as long as the submitter obtains a valid signature for an authorized address. So if the transcoder wanted to run a setup with each node responsible for signing and submitting a transaction that the address associated with the node is authorized for, it can do that. If the transcoder wanted to run a setup with a master node submitting transactions and other nodes being responsible for signing and relaying signed data to the master node (while keeping their own private keys local) it could also do that. If the transcoder doesn't want to be locked into the ECDSA signature algorithm, it can write validation logic for another algorithm in the contract.

The added flexibility comes with the additional gas cost of signature verification which is as expensive as whatever authentication logic the transcoder defines.

Another option would be to forego the concept of a validation contract (whether it be performing signature validation or just checking the existence of an address in an authorized address list), but this would entail adding more logic into the BondingManager contract - it would be nice if we didn't have to do this. Yet another option would be to move the authorization address list to the transcoder metadata registry that would already be storing a URI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants