Skip to content

Commit

Permalink
Add support for automatic delegation on RETURN
Browse files Browse the repository at this point in the history
On the RETURN opcode, check if any known ciphertext handle is part of
the returned buffer. If that's the case, consider it a delegation
request and make the handle available to the the caller.

Change the value of the `verifiedCiphertexts` map from a value to a
pointer. That aids in updating the map and avoids copying potentially
big ciphertexts.

Add examples in `handles.sol` for automatic delegation.
  • Loading branch information
dartdart26 committed Dec 2, 2022
1 parent 2552b7b commit 3bd0b9b
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 14 deletions.
6 changes: 6 additions & 0 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package vm

import (
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -94,3 +96,7 @@ func minInt(a int, b int) int {
}
return b
}

func contains(haystack []byte, needle []byte) bool {
return strings.Contains(string(haystack), string(needle))
}
6 changes: 2 additions & 4 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ func (e *verifyCiphertext) RequiredGas(input []byte) uint64 {
func (e *verifyCiphertext) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) (ret []byte, err error) {
// TODO: Accept a proof from `input` too
ctHash := crypto.Keccak256Hash(input)
accessibleState.Interpreter().verifiedCiphertexts[ctHash] = verifiedCiphertext{accessibleState.Interpreter().evm.depth, input}
accessibleState.Interpreter().verifiedCiphertexts[ctHash] = &verifiedCiphertext{accessibleState.Interpreter().evm.depth, input}
return ctHash.Bytes(), nil
}

Expand Down Expand Up @@ -1140,11 +1140,9 @@ func (e *delegateCiphertext) Run(accessibleState PrecompileAccessibleState, call
if len(input) != 32 {
return nil, errors.New("invalid ciphertext handle")
}
hash := common.BytesToHash(input)
ct, ok := accessibleState.Interpreter().verifiedCiphertexts[hash]
ct, ok := accessibleState.Interpreter().verifiedCiphertexts[common.BytesToHash(input)]
if ok {
ct.depth = minInt(ct.depth, accessibleState.Interpreter().evm.depth-1)
accessibleState.Interpreter().verifiedCiphertexts[hash] = ct
return nil, nil
}
return nil, errors.New("unverified ciphertext handle")
Expand Down
10 changes: 6 additions & 4 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,7 @@ func verifyIfCiphertextHandle(val common.Hash, interpreter *EVMInterpreter, cont
if alreadyVerified {
verifiedCt.depth = minInt(verifiedCt.depth, interpreter.evm.depth)
} else {
verifiedCt.depth = interpreter.evm.depth
verifiedCt.ciphertext = ctBytes
verifiedCt = &verifiedCiphertext{interpreter.evm.depth, ctBytes}
}
interpreter.verifiedCiphertexts[val] = verifiedCt
}
Expand Down Expand Up @@ -941,9 +940,12 @@ func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
offset, size := scope.Stack.pop(), scope.Stack.pop()
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))

// Remove all verified ciphertexts that have depth > current depth - 1
for key, verifiedCiphertext := range interpreter.verifiedCiphertexts {
if verifiedCiphertext.depth > interpreter.evm.depth-1 {
if contains(ret, key.Bytes()) {
// If a handle is returned, automatically make it available to the caller.
verifiedCiphertext.depth = minInt(verifiedCiphertext.depth, interpreter.evm.depth-1)
} else if verifiedCiphertext.depth > interpreter.evm.depth-1 {
// Remove any ciphertexts that are not delegated for use by the caller.
delete(interpreter.verifiedCiphertexts, key)
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ type EVMInterpreter struct {
readOnly bool // Whether to throw on stateful modifications
returnData []byte // Last CALL's return data for subsequent reuse

verifiedCiphertexts map[common.Hash]verifiedCiphertext // A map from a ciphertext hash to itself and stack depth at which it is verified
verifiedCiphertexts map[common.Hash]*verifiedCiphertext // A map from a ciphertext hash to itself and stack depth at which it is verified
}

// NewEVMInterpreter returns a new instance of the Interpreter.
Expand Down Expand Up @@ -111,7 +111,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
return &EVMInterpreter{
evm: evm,
cfg: cfg,
verifiedCiphertexts: make(map[common.Hash]verifiedCiphertext),
verifiedCiphertexts: make(map[common.Hash]*verifiedCiphertext),
}
}

Expand Down
21 changes: 17 additions & 4 deletions tests/solidity/zama/handles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ contract HandleOwner is Precompiles {
handle = bogus_handle;
}

// Returns the handle without delegation. Callers using it must fail.
// Returns the handle without delegation. Callers using it must succeed
// due to automatic delegation.
function get_handle_without_delegate() public view returns (uint256) {
return handle;
uint256 h = handle;
return h;
}

// Returns the handle with delegation. Callers using it must succeed.
Expand All @@ -82,6 +84,11 @@ contract HandleOwner is Precompiles {
function callee_reencrypt() public view returns (uint256) {
return callee.reencrypt(handle);
}

function load_handle_without_returning_it() public view returns(uint256) {
uint256 h = handle + 1;
return h;
}
}

contract Callee is Precompiles {
Expand All @@ -97,13 +104,19 @@ contract Caller is Precompiles {
owner = HandleOwner(owner_addr);
}

// Fails, because the owner hasn't delegated.
// Succeeds, because we do automatic delegation on return.
function reencrypt_without_delegate() public view returns (uint256) {
return precompile_reencrypt(owner.get_handle_without_delegate());
}

// Succeeds, because the owner hasn't delegated.
// Succeeds, because there is an explicit delegate by the caller.
function reencrypt_with_delegate() public view returns (uint256) {
return precompile_reencrypt(owner.get_handle_with_delegate());
}

// Fails, because the owner hasn't delegated, even though the handle is valid.
function reencrypt_with_a_valid_handle(uint256 handle) public view returns (uint256) {
owner.load_handle_without_returning_it();
return precompile_reencrypt(handle);
}
}

0 comments on commit 3bd0b9b

Please sign in to comment.