Skip to content
This repository was archived by the owner on May 12, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

92 changes: 92 additions & 0 deletions core/vm/contracts_suave.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package vm
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/decred/dcrd/dcrec/secp256k1/v4"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do this with the crypto libraries already in Geth?

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -347,3 +352,90 @@ func (s *suaveRuntime) contextGet(key string) ([]byte, error) {
}
return val, nil
}

func newAEAD(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(block)
}

func (s *suaveRuntime) secp256k1Decrypt(privkey common.Hash, ciphertext []byte) ([]byte, error) {
privKey := secp256k1.PrivKeyFromBytes(privkey.Bytes())

// Read the sender's ephemeral public key from the start of the message.
pubKeyLen := binary.LittleEndian.Uint32(ciphertext[:4])
senderPubKeyBytes := ciphertext[4 : 4+pubKeyLen]
senderPubKey, err := secp256k1.ParsePubKey(senderPubKeyBytes)
if err != nil {
fmt.Println(err)
return nil, err
}

// Derive the key used to seal the message, this time from the
// recipient's private key and the sender's public key.
recoveredCipherKey := sha256.Sum256(secp256k1.GenerateSharedSecret(privKey, senderPubKey))

// Open the sealed message.
aead, err := newAEAD(recoveredCipherKey[:])
if err != nil {
fmt.Println(err)
return nil, err
}
nonce := make([]byte, aead.NonceSize())
recoveredPlaintext, err := aead.Open(nil, nonce, ciphertext[4+pubKeyLen:], senderPubKeyBytes)
if err != nil {
fmt.Println(err)
return nil, err
}

return recoveredPlaintext, nil
}

func (s *suaveRuntime) secp256k1Encrypt(pubkey []byte, message []byte) ([]byte, error) {
pubKey, err := secp256k1.ParsePubKey(pubkey)
if err != nil {
return nil, err
}

// Derive an ephemeral public/private keypair for performing ECDHE with
// the recipient.
ephemeralPrivKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
fmt.Println(err)
return nil, err
}
ephemeralPubKey := ephemeralPrivKey.PubKey().SerializeCompressed()

// Using ECDHE, derive a shared symmetric key for encryption of the plaintext.
cipherKey := sha256.Sum256(secp256k1.GenerateSharedSecret(ephemeralPrivKey, pubKey))

// Seal the message using an AEAD. Here we use AES-256-GCM.
// The ephemeral public key must be included in this message, and becomes
// the authenticated data for the AEAD.
//
// Note that unless a unique nonce can be guaranteed, the ephemeral
// and/or shared keys must not be reused to encrypt different messages.
// Doing so destroys the security of the scheme. Random nonces may be
// used if XChaCha20-Poly1305 is used instead, but the message must then
// also encode the nonce (which we don't do here).
//
// Since a new ephemeral key is generated for every message ensuring there
// is no key reuse and AES-GCM permits the nonce to be used as a counter,
// the nonce is intentionally initialized to all zeros so it acts like the
// first (and only) use of a counter.

aead, err := newAEAD(cipherKey[:])
if err != nil {
fmt.Println(err)
return nil, err
}
nonce := make([]byte, aead.NonceSize())
ciphertext := make([]byte, 4+len(ephemeralPubKey))
binary.LittleEndian.PutUint32(ciphertext, uint32(len(ephemeralPubKey)))
copy(ciphertext[4:], ephemeralPubKey)
ciphertext = aead.Seal(ciphertext, nonce, message, ephemeralPubKey)

return ciphertext, 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) secp256k1Decrypt(privkey common.Hash, ciphertext []byte) ([]byte, error) {
return []byte{0x1}, nil
}

func (m *mockRuntime) secp256k1Encrypt(pubkey []byte, plaintext []byte) ([]byte, error) {
return []byte{0x1}, nil
}

func TestRuntimeAdapter(t *testing.T) {
adapter := &SuaveRuntimeAdapter{
impl: &mockRuntime{},
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: secp256k1Encrypt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a generic encrypt/decrypt pair of precompiles and put both AES and this implementation there?

address: "0x000000000000000000000000000000005770000e"
description: "Encrypts a message using the given secp256k1 public key"
input:
- name: pubkey
type: bytes
description: "Uncompressed pubkey used to encrypt the message: encoded as abi-packed hex-string with no 0x prefix. Example: `abi.encodePacked('04115c42e757b2efb7671c578530ec191a1359381e6a71127a9d37c486fd30dae57e76dc58f693bd7e7010358ce6b165e483a2921010db67ac11b1b51b651953d2')`"
- name: message
type: bytes
description: "Message to encrypt"
output:
fields:
- name: ciphertext
type: bytes
description: "Encrypted message"
- name: secp256k1Decrypt
address: "0x000000000000000000000000000000005770000d"
description: "Decrypts a message using the given secp256k1 private key"
input:
- name: privateKey
type: bytes32
description: "Private key used to decrypt the message."
- 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 @@ -138,6 +138,10 @@ library Suave {

address public constant RANDOM_BYTES = 0x000000000000000000000000000000007770000b;

address public constant SECP256K1_DECRYPT = 0x000000000000000000000000000000005770000D;

address public constant SECP256K1_ENCRYPT = 0x000000000000000000000000000000005770000E;

address public constant SIGN_ETH_TRANSACTION = 0x0000000000000000000000000000000040100001;

address public constant SIGN_MESSAGE = 0x0000000000000000000000000000000040100003;
Expand Down Expand Up @@ -372,6 +376,32 @@ library Suave {
return abi.decode(data, (bytes));
}

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

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

/// @notice Encrypts a message using the given secp256k1 public key
/// @param pubkey Uncompressed pubkey used to encrypt the message: encoded as abi-packed hex-string with no 0x prefix. Example: `abi.encodePacked('04115c42e757b2efb7671c578530ec191a1359381e6a71127a9d37c486fd30dae57e76dc58f693bd7e7010358ce6b165e483a2921010db67ac11b1b51b651953d2')`
/// @param message Message to encrypt
/// @return ciphertext Encrypted message
function secp256k1Encrypt(bytes memory pubkey, bytes memory message) internal returns (bytes memory) {
(bool success, bytes memory data) = SECP256K1_ENCRYPT.call(abi.encode(pubkey, message));
if (!success) {
revert PeekerReverted(SECP256K1_ENCRYPT, data);
}

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

/// @notice Signs an Ethereum Transaction, 1559 or Legacy, and returns raw signed transaction bytes. `txn` is binary encoding of the transaction.
/// @param txn Transaction to sign (RLP encoded)
/// @param chainId Id of the chain to sign for (hex encoded, with 0x prefix)
Expand Down