diff --git a/core/types/suave_structs.go b/core/types/suave_structs.go index 70728aac8..6654ecb99 100755 --- a/core/types/suave_structs.go +++ b/core/types/suave_structs.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: 58abede869b35f32efbb95ee4b2c2eff30631ae4ecb4f2732cd972174fe95ced +// Hash: e5fd01ab775f407562325023bbd419e19a533cf988a68bb06bd1ec77ab06a9ae package types import "github.com/ethereum/go-ethereum/common" diff --git a/core/vm/contracts_suave.go b/core/vm/contracts_suave.go index 3c4992374..8e3c0b1dc 100644 --- a/core/vm/contracts_suave.go +++ b/core/vm/contracts_suave.go @@ -3,7 +3,11 @@ package vm import ( "bytes" "context" + "crypto/aes" + "crypto/cipher" "crypto/rand" + "crypto/sha256" + "encoding/binary" "fmt" "io" "net/http" @@ -11,6 +15,7 @@ import ( "strings" "time" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -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 +} diff --git a/core/vm/contracts_suave_runtime_adapter.go b/core/vm/contracts_suave_runtime_adapter.go index c8ced1850..79077bec5 100644 --- a/core/vm/contracts_suave_runtime_adapter.go +++ b/core/vm/contracts_suave_runtime_adapter.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: 58abede869b35f32efbb95ee4b2c2eff30631ae4ecb4f2732cd972174fe95ced +// Hash: e5fd01ab775f407562325023bbd419e19a533cf988a68bb06bd1ec77ab06a9ae package vm import ( @@ -32,6 +32,8 @@ type SuaveRuntime interface { newDataRecord(decryptionCondition uint64, allowedPeekers []common.Address, allowedStores []common.Address, dataType string) (types.DataRecord, error) privateKeyGen(crypto types.CryptoSignature) (string, error) randomBytes(numBytes uint8) ([]byte, error) + secp256k1Decrypt(privateKey common.Hash, ciphertext []byte) ([]byte, error) + secp256k1Encrypt(pubkey []byte, message []byte) ([]byte, error) signEthTransaction(txn []byte, chainId string, signingKey string) ([]byte, error) signMessage(digest []byte, crypto types.CryptoSignature, signingKey string) ([]byte, error) simulateBundle(bundleData []byte) (uint64, error) @@ -56,6 +58,8 @@ var ( newDataRecordAddr = common.HexToAddress("0x0000000000000000000000000000000042030000") privateKeyGenAddr = common.HexToAddress("0x0000000000000000000000000000000053200003") randomBytesAddr = common.HexToAddress("0x000000000000000000000000000000007770000b") + secp256k1DecryptAddr = common.HexToAddress("0x000000000000000000000000000000005770000d") + secp256k1EncryptAddr = common.HexToAddress("0x000000000000000000000000000000005770000e") signEthTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000040100001") signMessageAddr = common.HexToAddress("0x0000000000000000000000000000000040100003") simulateBundleAddr = common.HexToAddress("0x0000000000000000000000000000000042100000") @@ -65,7 +69,7 @@ var ( ) var addrList = []common.Address{ - buildEthBlockAddr, buildEthBlockToAddr, confidentialInputsAddr, confidentialRetrieveAddr, confidentialStoreAddr, contextGetAddr, doHTTPRequestAddr, ethcallAddr, extractHintAddr, fetchDataRecordsAddr, fillMevShareBundleAddr, newBuilderAddr, newDataRecordAddr, privateKeyGenAddr, randomBytesAddr, signEthTransactionAddr, signMessageAddr, simulateBundleAddr, simulateTransactionAddr, submitBundleJsonRPCAddr, submitEthBlockToRelayAddr, + buildEthBlockAddr, buildEthBlockToAddr, confidentialInputsAddr, confidentialRetrieveAddr, confidentialStoreAddr, contextGetAddr, doHTTPRequestAddr, ethcallAddr, extractHintAddr, fetchDataRecordsAddr, fillMevShareBundleAddr, newBuilderAddr, newDataRecordAddr, privateKeyGenAddr, randomBytesAddr, secp256k1DecryptAddr, secp256k1EncryptAddr, signEthTransactionAddr, signMessageAddr, simulateBundleAddr, simulateTransactionAddr, submitBundleJsonRPCAddr, submitEthBlockToRelayAddr, } type SuaveRuntimeAdapter struct { @@ -119,6 +123,12 @@ func (b *SuaveRuntimeAdapter) run(addr common.Address, input []byte) ([]byte, er case randomBytesAddr: return b.randomBytes(input) + case secp256k1DecryptAddr: + return b.secp256k1Decrypt(input) + + case secp256k1EncryptAddr: + return b.secp256k1Encrypt(input) + case signEthTransactionAddr: return b.signEthTransaction(input) @@ -743,6 +753,86 @@ func (b *SuaveRuntimeAdapter) randomBytes(input []byte) (res []byte, err error) } +func (b *SuaveRuntimeAdapter) secp256k1Decrypt(input []byte) (res []byte, err error) { + var ( + unpacked []interface{} + result []byte + ) + + _ = unpacked + _ = result + + unpacked, err = artifacts.SuaveAbi.Methods["secp256k1Decrypt"].Inputs.Unpack(input) + if err != nil { + err = errFailedToUnpackInput + return + } + + var ( + privateKey common.Hash + ciphertext []byte + ) + + privateKey = unpacked[0].(common.Hash) + ciphertext = unpacked[1].([]byte) + + var ( + message []byte + ) + + if message, err = b.impl.secp256k1Decrypt(privateKey, ciphertext); err != nil { + return + } + + result, err = artifacts.SuaveAbi.Methods["secp256k1Decrypt"].Outputs.Pack(message) + if err != nil { + err = errFailedToPackOutput + return + } + return result, nil + +} + +func (b *SuaveRuntimeAdapter) secp256k1Encrypt(input []byte) (res []byte, err error) { + var ( + unpacked []interface{} + result []byte + ) + + _ = unpacked + _ = result + + unpacked, err = artifacts.SuaveAbi.Methods["secp256k1Encrypt"].Inputs.Unpack(input) + if err != nil { + err = errFailedToUnpackInput + return + } + + var ( + pubkey []byte + message []byte + ) + + pubkey = unpacked[0].([]byte) + message = unpacked[1].([]byte) + + var ( + ciphertext []byte + ) + + if ciphertext, err = b.impl.secp256k1Encrypt(pubkey, message); err != nil { + return + } + + result, err = artifacts.SuaveAbi.Methods["secp256k1Encrypt"].Outputs.Pack(ciphertext) + if err != nil { + err = errFailedToPackOutput + return + } + return result, nil + +} + func (b *SuaveRuntimeAdapter) signEthTransaction(input []byte) (res []byte, err error) { var ( unpacked []interface{} diff --git a/core/vm/contracts_suave_runtime_adapter_test.go b/core/vm/contracts_suave_runtime_adapter_test.go index f7fb31124..17cd1ebb2 100644 --- a/core/vm/contracts_suave_runtime_adapter_test.go +++ b/core/vm/contracts_suave_runtime_adapter_test.go @@ -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{}, diff --git a/suave/artifacts/SuaveLib.json b/suave/artifacts/SuaveLib.json index 9dfc12a26..8a933784e 100644 --- a/suave/artifacts/SuaveLib.json +++ b/suave/artifacts/SuaveLib.json @@ -1 +1 @@ -[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"},{"name":"beaconRoot","type":"bytes32","internalType":"bytes32"},{"name":"fillPending","type":"bool","internalType":"bool"}]},{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"relayUrl","type":"string","internalType":"string"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"},{"name":"executionPayload","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"buildEthBlockTo","inputs":[{"name":"executionNodeURL","type":"string","internalType":"string"},{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"},{"name":"beaconRoot","type":"bytes32","internalType":"bytes32"},{"name":"fillPending","type":"bool","internalType":"bool"}]},{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"relayUrl","type":"string","internalType":"string"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"},{"name":"executionPayload","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"confindentialData","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"key","type":"string","internalType":"string"},{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"contextGet","inputs":[{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"},{"name":"withFlashbotsSignature","type":"bool","internalType":"bool"},{"name":"timeout","type":"uint64","internalType":"uint64"}]}],"outputs":[{"name":"httpResponse","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"callOutput","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"hints","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchDataRecords","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"dataRecords","type":"tuple[]","internalType":"struct Suave.DataRecord[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBuilder","outputs":[{"name":"sessionid","type":"string","internalType":"string"}]},{"type":"function","name":"newDataRecord","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"dataType","type":"string","internalType":"string"}],"outputs":[{"name":"dataRecord","type":"tuple","internalType":"struct Suave.DataRecord","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"privateKeyGen","inputs":[{"name":"crypto","type":"uint8","internalType":"struct Suave.CryptoSignature"}],"outputs":[{"name":"privateKey","type":"string","internalType":"string"}]},{"type":"function","name":"randomBytes","inputs":[{"name":"numBytes","type":"uint8","internalType":"uint8"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"signedTxn","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"signMessage","inputs":[{"name":"digest","type":"bytes","internalType":"bytes"},{"name":"crypto","type":"uint8","internalType":"struct Suave.CryptoSignature"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"signature","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"effectiveGasPrice","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"simulateTransaction","inputs":[{"name":"sessionid","type":"string","internalType":"string"},{"name":"txn","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"simulationResult","type":"tuple","internalType":"struct Suave.SimulateTransactionResult","components":[{"name":"egp","type":"uint64","internalType":"uint64"},{"name":"logs","type":"tuple[]","internalType":"struct Suave.SimulatedLog[]","components":[{"name":"data","type":"bytes","internalType":"bytes"},{"name":"addr","type":"address","internalType":"address"},{"name":"topics","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"success","type":"bool","internalType":"bool"},{"name":"error","type":"string","internalType":"string"}]}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"errorMessage","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file +[{"type":"error","name":"PeekerReverted","inputs":[{"name":"addr","type":"address"},{"name":"err","type":"bytes"}]},{"type":"function","name":"buildEthBlock","inputs":[{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"},{"name":"beaconRoot","type":"bytes32","internalType":"bytes32"},{"name":"fillPending","type":"bool","internalType":"bool"}]},{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"relayUrl","type":"string","internalType":"string"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"},{"name":"executionPayload","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"buildEthBlockTo","inputs":[{"name":"executionNodeURL","type":"string","internalType":"string"},{"name":"blockArgs","type":"tuple","internalType":"struct Suave.BuildBlockArgs","components":[{"name":"slot","type":"uint64","internalType":"uint64"},{"name":"proposerPubkey","type":"bytes","internalType":"bytes"},{"name":"parent","type":"bytes32","internalType":"bytes32"},{"name":"timestamp","type":"uint64","internalType":"uint64"},{"name":"feeRecipient","type":"address","internalType":"address"},{"name":"gasLimit","type":"uint64","internalType":"uint64"},{"name":"random","type":"bytes32","internalType":"bytes32"},{"name":"withdrawals","type":"tuple[]","internalType":"struct Suave.Withdrawal[]","components":[{"name":"index","type":"uint64","internalType":"uint64"},{"name":"validator","type":"uint64","internalType":"uint64"},{"name":"Address","type":"address","internalType":"address"},{"name":"amount","type":"uint64","internalType":"uint64"}]},{"name":"extra","type":"bytes","internalType":"bytes"},{"name":"beaconRoot","type":"bytes32","internalType":"bytes32"},{"name":"fillPending","type":"bool","internalType":"bool"}]},{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"relayUrl","type":"string","internalType":"string"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"},{"name":"executionPayload","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialInputs","outputs":[{"name":"confindentialData","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialRetrieve","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"confidentialStore","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"key","type":"string","internalType":"string"},{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"contextGet","inputs":[{"name":"key","type":"string","internalType":"string"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"doHTTPRequest","inputs":[{"name":"request","type":"tuple","internalType":"struct Suave.HttpRequest","components":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"headers","type":"string[]","internalType":"string[]"},{"name":"body","type":"bytes","internalType":"bytes"},{"name":"withFlashbotsSignature","type":"bool","internalType":"bool"},{"name":"timeout","type":"uint64","internalType":"uint64"}]}],"outputs":[{"name":"httpResponse","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"ethcall","inputs":[{"name":"contractAddr","type":"address","internalType":"address"},{"name":"input1","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"callOutput","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"extractHint","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"hints","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"fetchDataRecords","inputs":[{"name":"cond","type":"uint64","internalType":"uint64"},{"name":"namespace","type":"string","internalType":"string"}],"outputs":[{"name":"dataRecords","type":"tuple[]","internalType":"struct Suave.DataRecord[]","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"fillMevShareBundle","inputs":[{"name":"dataId","type":"bytes16","internalType":"struct Suave.DataId"}],"outputs":[{"name":"encodedBundle","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"newBuilder","outputs":[{"name":"sessionid","type":"string","internalType":"string"}]},{"type":"function","name":"newDataRecord","inputs":[{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"dataType","type":"string","internalType":"string"}],"outputs":[{"name":"dataRecord","type":"tuple","internalType":"struct Suave.DataRecord","components":[{"name":"id","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"salt","type":"bytes16","internalType":"struct Suave.DataId"},{"name":"decryptionCondition","type":"uint64","internalType":"uint64"},{"name":"allowedPeekers","type":"address[]","internalType":"address[]"},{"name":"allowedStores","type":"address[]","internalType":"address[]"},{"name":"version","type":"string","internalType":"string"}]}]},{"type":"function","name":"privateKeyGen","inputs":[{"name":"crypto","type":"uint8","internalType":"struct Suave.CryptoSignature"}],"outputs":[{"name":"privateKey","type":"string","internalType":"string"}]},{"type":"function","name":"randomBytes","inputs":[{"name":"numBytes","type":"uint8","internalType":"uint8"}],"outputs":[{"name":"value","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"secp256k1Decrypt","inputs":[{"name":"privateKey","type":"bytes32","internalType":"bytes32"},{"name":"ciphertext","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"message","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"secp256k1Encrypt","inputs":[{"name":"pubkey","type":"bytes","internalType":"bytes"},{"name":"message","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"ciphertext","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"signEthTransaction","inputs":[{"name":"txn","type":"bytes","internalType":"bytes"},{"name":"chainId","type":"string","internalType":"string"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"signedTxn","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"signMessage","inputs":[{"name":"digest","type":"bytes","internalType":"bytes"},{"name":"crypto","type":"uint8","internalType":"struct Suave.CryptoSignature"},{"name":"signingKey","type":"string","internalType":"string"}],"outputs":[{"name":"signature","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"simulateBundle","inputs":[{"name":"bundleData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"effectiveGasPrice","type":"uint64","internalType":"uint64"}]},{"type":"function","name":"simulateTransaction","inputs":[{"name":"sessionid","type":"string","internalType":"string"},{"name":"txn","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"simulationResult","type":"tuple","internalType":"struct Suave.SimulateTransactionResult","components":[{"name":"egp","type":"uint64","internalType":"uint64"},{"name":"logs","type":"tuple[]","internalType":"struct Suave.SimulatedLog[]","components":[{"name":"data","type":"bytes","internalType":"bytes"},{"name":"addr","type":"address","internalType":"address"},{"name":"topics","type":"bytes32[]","internalType":"bytes32[]"}]},{"name":"success","type":"bool","internalType":"bool"},{"name":"error","type":"string","internalType":"string"}]}]},{"type":"function","name":"submitBundleJsonRPC","inputs":[{"name":"url","type":"string","internalType":"string"},{"name":"method","type":"string","internalType":"string"},{"name":"params","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"errorMessage","type":"bytes","internalType":"bytes"}]},{"type":"function","name":"submitEthBlockToRelay","inputs":[{"name":"relayUrl","type":"string","internalType":"string"},{"name":"builderBid","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"blockBid","type":"bytes","internalType":"bytes"}]}] \ No newline at end of file diff --git a/suave/artifacts/addresses.go b/suave/artifacts/addresses.go index a2c83e423..de910ef14 100644 --- a/suave/artifacts/addresses.go +++ b/suave/artifacts/addresses.go @@ -1,5 +1,5 @@ // Code generated by suave/gen. DO NOT EDIT. -// Hash: 58abede869b35f32efbb95ee4b2c2eff30631ae4ecb4f2732cd972174fe95ced +// Hash: e5fd01ab775f407562325023bbd419e19a533cf988a68bb06bd1ec77ab06a9ae package artifacts import ( @@ -23,6 +23,8 @@ var ( newDataRecordAddr = common.HexToAddress("0x0000000000000000000000000000000042030000") privateKeyGenAddr = common.HexToAddress("0x0000000000000000000000000000000053200003") randomBytesAddr = common.HexToAddress("0x000000000000000000000000000000007770000b") + secp256k1DecryptAddr = common.HexToAddress("0x000000000000000000000000000000005770000d") + secp256k1EncryptAddr = common.HexToAddress("0x000000000000000000000000000000005770000e") signEthTransactionAddr = common.HexToAddress("0x0000000000000000000000000000000040100001") signMessageAddr = common.HexToAddress("0x0000000000000000000000000000000040100003") simulateBundleAddr = common.HexToAddress("0x0000000000000000000000000000000042100000") @@ -47,6 +49,8 @@ var SuaveMethods = map[string]common.Address{ "newDataRecord": newDataRecordAddr, "privateKeyGen": privateKeyGenAddr, "randomBytes": randomBytesAddr, + "secp256k1Decrypt": secp256k1DecryptAddr, + "secp256k1Encrypt": secp256k1EncryptAddr, "signEthTransaction": signEthTransactionAddr, "signMessage": signMessageAddr, "simulateBundle": simulateBundleAddr, @@ -87,6 +91,10 @@ func PrecompileAddressToName(addr common.Address) string { return "privateKeyGen" case randomBytesAddr: return "randomBytes" + case secp256k1DecryptAddr: + return "secp256k1Decrypt" + case secp256k1EncryptAddr: + return "secp256k1Encrypt" case signEthTransactionAddr: return "signEthTransaction" case signMessageAddr: diff --git a/suave/gen/suave_spec.yaml b/suave/gen/suave_spec.yaml index e9abe3b70..5c20f7381 100644 --- a/suave/gen/suave_spec.yaml +++ b/suave/gen/suave_spec.yaml @@ -445,3 +445,33 @@ functions: - name: value type: bytes description: "Randomly-generated bytes" + - name: secp256k1Encrypt + 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" diff --git a/suave/sol/libraries/Suave.sol b/suave/sol/libraries/Suave.sol index 4a6977798..1dcbb3e9e 100644 --- a/suave/sol/libraries/Suave.sol +++ b/suave/sol/libraries/Suave.sol @@ -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; @@ -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)