Skip to content

Commit

Permalink
Exit on failed requests to zkpoks and oracle DB (ethereum#46)
Browse files Browse the repository at this point in the history
If a request to the tfhe-zkpoks or zbc-oracle-db services fails,
exit the whole process instead of calling panic(). Rationale is that
panic() will unwind the stack and, potentially, leave the process
running. If, for example, tfhe-zkpoks or zbc-oracle-db is down for some
reason, that will lead to the node continuing and, ultimately, falling
out of sync, because other nodes wouldn't have failed and would have
added the txn being executed. If we instead stop the whole process, the
node won't be able to progress with the txn in question until it gets a
response (either success or failure) from tfhe-zkpoks or zbc-oracle-db.

Essentially, we treat tfhe-zkpoks and zbc-oracle-db as robust and
trusted services that are not supposed to be down or being malicious for
the system to progress.

For tfhe-zkpoks, we treat the response as verification failure only on
HTTP status 406. We treat HTTP status 200 as success. On all other
errors or statuses, we exit the process.

For zbc-oracle-db, we treat HTTP status 200 as success and on all other
errors or statuses, we exit the process. On non-oracle nodes, if
signature verification fails or if the require is not found, we exit the
process, because it means that the oracle hasn't executed the txn.
However, the oracle is assumed to be trusted and always correct. In that
case, a non-oracle node cannot reliably proceed.
  • Loading branch information
dartdart26 committed Mar 6, 2023
1 parent ba41f75 commit 806cd88
Showing 1 changed file with 97 additions and 81 deletions.
178 changes: 97 additions & 81 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -1126,13 +1126,15 @@ func (c *bls12381MapG2) Run(accessibleState PrecompileAccessibleState, caller co

type tomlConfigOptions struct {
Oracle struct {
Mode string
OracleDBAddress string
Mode string
OracleDBAddress string
RequireRetryCount uint8
}

Zk struct {
Verify bool
VerifyRPCAddress string
VerifyRetryCount uint8
}

Tfhe struct {
Expand Down Expand Up @@ -1294,31 +1296,44 @@ func fheEncryptToUserKey(value uint64, userAddress common.Address) ([]byte, erro
return ct, nil
}

func exitProcess() {
os.Exit(1)
}

type verifyCiphertext struct{}

func (e *verifyCiphertext) RequiredGas(input []byte) uint64 {
// TODO
return 8
}

func verifyZkProof(input []byte) ([]byte, error) {
req, err := http.NewRequest(http.MethodPost, tomlConfig.Zk.VerifyRPCAddress, bytes.NewReader(input))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/msgpack")
resp, err := zkHttpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("failure HTTP status code on ZK verify: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.New("failed reading ZK verification response body")
// Returns the verified ciphertext on success or nil on invalid ZK proof.
// Exits the process on errors.
func verifyZkProof(input []byte) []byte {
for try := uint8(1); try <= tomlConfig.Zk.VerifyRetryCount+1; try++ {
req, err := http.NewRequest(http.MethodPost, tomlConfig.Zk.VerifyRPCAddress, bytes.NewReader(input))
if err != nil {
continue
}
req.Header.Add("Content-Type", "application/msgpack")
resp, err := zkHttpClient.Do(req)
if err != nil {
continue
}
// The ZKPoK service returns 406 if the proof is incorrect.
if resp.StatusCode == 406 {
return nil
} else if resp.StatusCode != 200 {
continue
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
continue
}
return body
}
return body, nil
exitProcess()
return nil
}

func (e *verifyCiphertext) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
Expand All @@ -1331,10 +1346,9 @@ func (e *verifyCiphertext) Run(accessibleState PrecompileAccessibleState, caller
// For testing: if input size <= `ciphertextSize`, treat the whole input as ciphertext.
ctBytes = input[0:minInt(ciphertextSize, len(input))]
} else {
var err error
ctBytes, err = verifyZkProof(input)
if err != nil {
return nil, err
ctBytes = verifyZkProof(input)
if ctBytes == nil {
return nil, fmt.Errorf("invalid ZK Proof")
}
}
ct := new(tfheCiphertext)
Expand Down Expand Up @@ -1425,80 +1439,82 @@ func requireURL(key *string) string {
return tomlConfig.Oracle.OracleDBAddress + "/require/" + *key
}

func putRequire(ciphertext []byte, value bool) error {
// Puts the given ciphertext as a require to the oracle DB or exits the process on errors.
// Returns the require value.
func putRequire(ct *tfheCiphertext) bool {
ciphertext := ct.serialize()
value := (ct.decrypt() != 0)
key := requireKey(ciphertext)
j, err := json.Marshal(requireMessage{value, signRequire(ciphertext, value)})
if err != nil {
return err
exitProcess()
}
req, err := http.NewRequest(http.MethodPut, requireURL(&key), bytes.NewReader(j))
if err != nil {
return err
}
resp, err := requireHttpClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("failure HTTP status code on require PUT: %d", resp.StatusCode)
for try := uint8(1); try <= tomlConfig.Oracle.RequireRetryCount+1; try++ {
req, err := http.NewRequest(http.MethodPut, requireURL(&key), bytes.NewReader(j))
if err != nil {
continue
}
resp, err := requireHttpClient.Do(req)
if err != nil {
continue
}
if resp.StatusCode != 200 {
continue
}
return value
}
return nil
exitProcess()
return value
}

func getRequire(ciphertext []byte) (bool, error) {
// Gets the given require from the oracle DB and returns its value.
// Exits the process on errors or signature verification failure.
func getRequire(ct *tfheCiphertext) bool {
ciphertext := ct.serialize()
key := requireKey(ciphertext)
req, err := http.NewRequest(http.MethodGet, requireURL(&key), http.NoBody)
if err != nil {
return false, nil
}
resp, err := requireHttpClient.Do(req)
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, fmt.Errorf("require: failure HTTP status code on require GET: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, errors.New("failed reading response body")
}
msg := requireMessage{}
if err := json.Unmarshal(body, &msg); err != nil {
return false, err
}
b := requireBytesToSign(ciphertext, msg.Value)
s, err := hex.DecodeString(msg.Signature)
if err != nil {
return false, err
}
if ed25519.Verify(publicSignatureKey, b, s) {
return msg.Value, nil
for try := uint8(1); try <= tomlConfig.Oracle.RequireRetryCount+1; try++ {
req, err := http.NewRequest(http.MethodGet, requireURL(&key), http.NoBody)
if err != nil {
continue
}
resp, err := requireHttpClient.Do(req)
if err != nil {
continue
}
if resp.StatusCode != 200 {
continue
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
continue
}
msg := requireMessage{}
if err := json.Unmarshal(body, &msg); err != nil {
continue
}
b := requireBytesToSign(ciphertext, msg.Value)
s, err := hex.DecodeString(msg.Signature)
if err != nil {
continue
}
if !ed25519.Verify(publicSignatureKey, b, s) {
continue
}
return msg.Value
}
return false, errors.New("invalid require signature")
exitProcess()
return false
}

func evaluateRequire(ct *tfheCiphertext) bool {
switch mode := strings.ToLower(tomlConfig.Oracle.Mode); mode {
case "oracle":
requireValue := ct.decrypt()
if err := putRequire(ct.serialize(), requireValue != 0); err != nil {
panic(err)
}
if requireValue == 0 {
return false
}
return true
return putRequire(ct)
case "node":
requireValue, err := getRequire(ct.serialize())
if err != nil {
panic(err)
}
if !requireValue {
return false
}
return true
return getRequire(ct)
}
panic(errors.New("unimplemented require mode"))
exitProcess()
return false
}

func (e *require) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, readOnly bool) ([]byte, error) {
Expand Down

0 comments on commit 806cd88

Please sign in to comment.