Skip to content

Commit

Permalink
AES en/decryption precompiles (#274)
Browse files Browse the repository at this point in the history
* add aes en/decrypt precompiles (suave gen output missing)

* run suave gen

* fix bugs

* fix aes buffer encoding, use GCM, add AES precompile tests

* add clarifying comment

* set better example in test

* remove commented code

* catch unhandled err in aes test
  • Loading branch information
zeroXbrock authored Aug 8, 2024
1 parent f3a2222 commit ad7a8c1
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 5 deletions.
2 changes: 1 addition & 1 deletion core/types/suave_structs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions core/vm/contracts_suave.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package vm
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
Expand Down Expand Up @@ -347,3 +349,46 @@ func (s *suaveRuntime) contextGet(key string) ([]byte, error) {
}
return val, nil
}

func (s *suaveRuntime) aesEncrypt(key []byte, message []byte) ([]byte, error) {
// force 32-byte key for best security
keyBytes := make([]byte, 32)
copy(keyBytes[:], key[:])
c, err := aes.NewCipher(keyBytes)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
buf := gcm.Seal(nonce, nonce, message, nil)
return buf, nil
}

func (s *suaveRuntime) aesDecrypt(key []byte, ciphertext []byte) ([]byte, error) {
keyBytes := make([]byte, 32)
copy(keyBytes[:], key[:])
c, err := aes.NewCipher(keyBytes)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
94 changes: 92 additions & 2 deletions core/vm/contracts_suave_runtime_adapter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions core/vm/contracts_suave_runtime_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ func (m *mockRuntime) randomBytes(length uint8) ([]byte, error) {
return bytes, nil
}

func (m *mockRuntime) aesEncrypt(key []byte, message []byte) ([]byte, error) {
return []byte{0x1}, nil
}

func (m *mockRuntime) aesDecrypt(key []byte, ciphertext []byte) ([]byte, error) {
return []byte{0x1}, nil
}

func TestRuntimeAdapter(t *testing.T) {
adapter := &SuaveRuntimeAdapter{
impl: &mockRuntime{},
Expand Down
24 changes: 24 additions & 0 deletions core/vm/contracts_suave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vm

import (
"context"
"crypto/rand"
"math/big"
"net/http"
"net/http/httptest"
Expand All @@ -16,6 +17,7 @@ import (
suave "github.com/ethereum/go-ethereum/suave/core"
"github.com/ethereum/go-ethereum/suave/cstore"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/scrypt"
)

type mockSuaveBackend struct {
Expand Down Expand Up @@ -137,6 +139,28 @@ func TestSuave_DataRecordWorkflow(t *testing.T) {
}
}

func TestSuave_AESPrecompiles(t *testing.T) {
b := newTestBackend(t)

message := []byte("hello world")

// safely generate a 32-byte secret
salt := make([]byte, 32)
_, err := rand.Read(salt)
require.NoError(t, err)
pk, err := scrypt.Key([]byte("some password"), salt, 32768, 8, 1, 32)
require.NoError(t, err)

// note: any 32-byte value will do for pk
// if your secret is not 32 bytes long, it's right-padded with 0s
ciphertext, err := b.aesEncrypt(pk, message)
require.NoError(t, err)

decrypted, err := b.aesDecrypt(pk, ciphertext)
require.NoError(t, err)
require.Equal(t, message, decrypted)
}

func TestSuave_ConfStoreWorkflow(t *testing.T) {
b := newTestBackend(t)

Expand Down
2 changes: 1 addition & 1 deletion suave/artifacts/SuaveLib.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion suave/artifacts/addresses.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions suave/gen/suave_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,33 @@ functions:
- name: value
type: bytes
description: "Randomly-generated bytes"
- name: aesEncrypt
address: "0x000000000000000000000000000000005670000e"
description: "Encrypts a message using given bytes as a cipher."
input:
- name: key
type: bytes
description: "Private key used to encrypt the message"
- name: message
type: bytes
description: "Message to encrypt"
output:
fields:
- name: ciphertext
type: bytes
description: "Encrypted message"
- name: aesDecrypt
address: "0x000000000000000000000000000000005670000d"
description: "Decrypts a message using given bytes as a cipher."
input:
- name: key
type: bytes
description: "Private key used to decrypt the ciphertext"
- name: ciphertext
type: bytes
description: "Message to decrypt"
output:
fields:
- name: message
type: bytes
description: "Decrypted message"
30 changes: 30 additions & 0 deletions suave/sol/libraries/Suave.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ library Suave {

address public constant IS_CONFIDENTIAL_ADDR = 0x0000000000000000000000000000000042010000;

address public constant AES_DECRYPT = 0x000000000000000000000000000000005670000D;

address public constant AES_ENCRYPT = 0x000000000000000000000000000000005670000e;

address public constant BUILD_ETH_BLOCK = 0x0000000000000000000000000000000042100001;

address public constant BUILD_ETH_BLOCK_TO = 0x0000000000000000000000000000000042100006;
Expand Down Expand Up @@ -165,6 +169,32 @@ library Suave {
}
}

/// @notice Decrypts a message using given bytes as a cipher.
/// @param key Private key used to decrypt the ciphertext
/// @param ciphertext Message to decrypt
/// @return message Decrypted message
function aesDecrypt(bytes memory key, bytes memory ciphertext) internal returns (bytes memory) {
(bool success, bytes memory data) = AES_DECRYPT.call(abi.encode(key, ciphertext));
if (!success) {
revert PeekerReverted(AES_DECRYPT, data);
}

return abi.decode(data, (bytes));
}

/// @notice Encrypts a message using given bytes as a cipher.
/// @param key Private key used to encrypt the message
/// @param message Message to encrypt
/// @return ciphertext Encrypted message
function aesEncrypt(bytes memory key, bytes memory message) internal returns (bytes memory) {
(bool success, bytes memory data) = AES_ENCRYPT.call(abi.encode(key, message));
if (!success) {
revert PeekerReverted(AES_ENCRYPT, data);
}

return abi.decode(data, (bytes));
}

/// @notice Constructs an Ethereum block based on the provided data records. No blobs are returned.
/// @param blockArgs Arguments to build the block
/// @param dataId ID of the data record with mev-share bundle data
Expand Down

0 comments on commit ad7a8c1

Please sign in to comment.