Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

feat: add true randomness #119

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
66 changes: 13 additions & 53 deletions fhevm/operators_rand.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package fhevm

import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/big"
"math/bits"
"unsafe"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -60,7 +58,7 @@ func applyUpperBound(rand uint64, bitsInRand int, upperBound *uint64) uint64 {
return rand >> shift
}

func generateRandom(environment EVMEnvironment, caller common.Address, resultType tfhe.FheUintType, upperBound *uint64) ([]byte, error) {
func generateRandom(environment EVMEnvironment, caller common.Address, resultType tfhe.FheUintType, numberOfBits uint64) ([]byte, error) {
// If we are doing gas estimation, skip execution and insert a random ciphertext as a result.
if !environment.IsCommitting() {
return insertRandomCiphertext(environment, resultType), nil
Expand All @@ -84,58 +82,15 @@ func generateRandom(environment EVMEnvironment, caller common.Address, resultTyp
if err != nil {
return nil, err
}
// The RNG nonce bytes are of size chacha20.NonceSizeX, which is assumed to be 24 bytes (see init() above).
// Since uint256.Int.z[0] is the least significant byte and since uint256.Int.Bytes32() serializes
// in order of z[3], z[2], z[1], z[0], we want to essentially ignore the first byte, i.e. z[3], because
// it will always be 0 as the nonce size is 24.
cipher, err := chacha20.NewUnauthenticatedCipher(seed.Bytes(), currentRngNonceBytes[32-chacha20.NonceSizeX:32])
if err != nil {
return nil, err
}

// XOR a byte array of 0s with the stream from the cipher and receive the result in the same array.
// Apply upperBound, if set.
var randUint uint64
switch resultType {
case tfhe.FheUint4:
randBytes := make([]byte, 1)
cipher.XORKeyStream(randBytes, randBytes)
randUint = uint64(randBytes[0])
randUint = uint64(applyUpperBound(randUint, 4, upperBound))
case tfhe.FheUint8:
randBytes := make([]byte, 1)
cipher.XORKeyStream(randBytes, randBytes)
randUint = uint64(randBytes[0])
randUint = uint64(applyUpperBound(randUint, 8, upperBound))
case tfhe.FheUint16:
randBytes := make([]byte, 2)
cipher.XORKeyStream(randBytes, randBytes)
randUint = uint64(binary.BigEndian.Uint16(randBytes))
randUint = uint64(applyUpperBound(randUint, 16, upperBound))
case tfhe.FheUint32:
randBytes := make([]byte, 4)
cipher.XORKeyStream(randBytes, randBytes)
randUint = uint64(binary.BigEndian.Uint32(randBytes))
randUint = uint64(applyUpperBound(randUint, 32, upperBound))
case tfhe.FheUint64:
randBytes := make([]byte, 8)
cipher.XORKeyStream(randBytes, randBytes)
randUint = uint64(binary.BigEndian.Uint64(randBytes))
randUint = uint64(applyUpperBound(randUint, 64, upperBound))
default:
return nil, fmt.Errorf("generateRandom() invalid type requested: %d", resultType)
}

// Trivially encrypt the random integer.
randCt := new(tfhe.TfheCiphertext)
randBigInt := big.NewInt(0)
randBigInt.SetUint64(randUint)
randCt.TrivialEncrypt(*randBigInt, resultType)
insertCiphertextToMemory(environment, randCt)
randCt, err := tfhe.GenerateObliviousPseudoRandom(resultType, *(*uint64)(unsafe.Pointer(&seed.Bytes()[0])), numberOfBits)
Copy link
Contributor

@dartdart26 dartdart26 Jun 18, 2024

Choose a reason for hiding this comment

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

AFAIU, the seed can just be a counter. Right now, we hash a fixed global seed and the caller address, leading to the same seed every time. We should double check with tfhe-rs and, if that's the case, change the seed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK you mean we could use directly globalRngSeed ?

Choose a reason for hiding this comment

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

Is globalRngSeed incremented after each use?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe nextRngNonce is a better choice. I don't see where we use it right now

Choose a reason for hiding this comment

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

I am getting it right, that globalRngSeed/nextRngNonce is local state? For the coprocessor we might want to avoid this, and instead use a counter from Solidity. This would make the coprocessor computation stateless and reproducible. To generate the symbolic values for random encryptions, we need a Solidity counter (plaintext integer) anyways. This counter could be used here as well.


if err != nil {
return nil, err
}

insertCiphertextToMemory(environment, randCt)

ctHash := randCt.GetHash()
return ctHash[:], nil
}
Expand All @@ -156,7 +111,7 @@ func fheRandRun(environment EVMEnvironment, caller common.Address, addr common.A
}
resultType := tfhe.FheUintType(input[0])
otelDescribeOperandsFheTypes(runSpan, resultType)
var noUpperBound *uint64 = nil
var noUpperBound uint64 = uint64(resultType.NumBits())
return generateRandom(environment, caller, resultType, noUpperBound)
}

Expand All @@ -177,5 +132,10 @@ func fheRandBoundedRun(environment EVMEnvironment, caller common.Address, addr c
return nil, errors.New(msg)
}
bound64 := bound.Uint64()
return generateRandom(environment, caller, randType, &bound64)
numberOfBits := uint64(1);
for bound64 > uint64(1) {
bound64 = bound64 / uint64(2);
numberOfBits++;
}
return generateRandom(environment, caller, randType, numberOfBits)
}
79 changes: 79 additions & 0 deletions fhevm/tfhe/tfhe_ciphertext.go
Original file line number Diff line number Diff line change
Expand Up @@ -2680,3 +2680,82 @@ func EqArray(lhs []*TfheCiphertext, rhs []*TfheCiphertext) (*TfheCiphertext, err
}
return result, nil
}

func GenerateObliviousPseudoRandom(generatedType FheUintType, seed uint64, numberOfBits uint64) (*TfheCiphertext, error) {
result := new(TfheCiphertext)

// Do the FHE computation.
var resultPtr unsafe.Pointer
switch generatedType {
case FheUint4:
resultPtr = C.generate_oblivious_pseudo_random_uint4(C.uint64_t(seed), C.uint64_t(numberOfBits), sks)
if resultPtr == nil {
return nil, errors.New("GenerateObliviousPseudoRandom: generation failed")
}
defer C.destroy_fhe_uint4(resultPtr)
case FheUint8:
resultPtr = C.generate_oblivious_pseudo_random_uint8(C.uint64_t(seed), C.uint64_t(numberOfBits), sks)
if resultPtr == nil {
return nil, errors.New("GenerateObliviousPseudoRandom: generation failed")
}
defer C.destroy_fhe_uint8(resultPtr)
case FheUint16:
resultPtr = C.generate_oblivious_pseudo_random_uint16(C.uint64_t(seed), C.uint64_t(numberOfBits), sks)
if resultPtr == nil {
return nil, errors.New("GenerateObliviousPseudoRandom: generation failed")
}
defer C.destroy_fhe_uint16(resultPtr)
case FheUint32:
resultPtr = C.generate_oblivious_pseudo_random_uint32(C.uint64_t(seed), C.uint64_t(numberOfBits), sks)
if resultPtr == nil {
return nil, errors.New("GenerateObliviousPseudoRandom: generation failed")
}
defer C.destroy_fhe_uint32(resultPtr)
case FheUint64:
resultPtr = C.generate_oblivious_pseudo_random_uint64(C.uint64_t(seed), C.uint64_t(numberOfBits), sks)
if resultPtr == nil {
return nil, errors.New("GenerateObliviousPseudoRandom: generation failed")
}
defer C.destroy_fhe_uint64(resultPtr)
default:
return nil, fmt.Errorf("GenerateObliviousPseudoRandom: unsupported ciphertext type %d", generatedType)
}

ser := &C.DynamicBuffer{}

switch generatedType {
case FheUint4:
ret := C.serialize_fhe_uint4(resultPtr, ser)
if ret != 0 {
return nil, errors.New("GenerateObliviousPseudoRandom: serialization failed")
}
case FheUint8:
ret := C.serialize_fhe_uint8(resultPtr, ser)
if ret != 0 {
return nil, errors.New("GenerateObliviousPseudoRandom: serialization failed")
}
case FheUint16:
ret := C.serialize_fhe_uint16(resultPtr, ser)
if ret != 0 {
return nil, errors.New("GenerateObliviousPseudoRandom: serialization failed")
}
case FheUint32:
ret := C.serialize_fhe_uint32(resultPtr, ser)
if ret != 0 {
return nil, errors.New("GenerateObliviousPseudoRandom: serialization failed")
}
case FheUint64:
ret := C.serialize_fhe_uint64(resultPtr, ser)
if ret != 0 {
return nil, errors.New("GenerateObliviousPseudoRandom: serialization failed")
}
default:
return nil, fmt.Errorf("GenerateObliviousPseudoRandom: unsupported ciphertext type %d", generatedType)
}

defer C.destroy_dynamic_buffer(ser)
result.Serialization = C.GoBytes(unsafe.Pointer(ser.pointer), C.int(ser.length))
result.FheUintType = generatedType
result.computeHash()
return result, nil
}
56 changes: 56 additions & 0 deletions fhevm/tfhe/tfhe_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ void* deserialize_fhe_uint8(DynamicBufferView in) {
return ct;
}


void* deserialize_compact_fhe_uint8(DynamicBufferView in) {
CompactFheUint8List* list = NULL;
FheUint8* ct = NULL;
Expand Down Expand Up @@ -426,6 +427,61 @@ void destroy_fhe_uint160(void* ct) {
assert(r == 0);
}

void* generate_oblivious_pseudo_random_uint4(uint64_t seed, uint64_t numberOfBits, void* sks) {
FheUint4* result = NULL;

checked_set_server_key(sks);

const int r = generate_oblivious_pseudo_random_bits_fhe_uint4(&result, seed, seed, numberOfBits);
if(r != 0) return NULL;

return result;
}

void* generate_oblivious_pseudo_random_uint8(uint64_t seed, uint64_t numberOfBits, void* sks) {
FheUint8* result = NULL;

checked_set_server_key(sks);

const int r = generate_oblivious_pseudo_random_bits_fhe_uint8(&result, seed, seed, numberOfBits);
if(r != 0) return NULL;

return result;
}

void* generate_oblivious_pseudo_random_uint16(uint64_t seed, uint64_t numberOfBits, void* sks) {
FheUint16* result = NULL;

checked_set_server_key(sks);

const int r = generate_oblivious_pseudo_random_bits_fhe_uint16(&result, seed, seed, numberOfBits);
if(r != 0) return NULL;

return result;
}

void* generate_oblivious_pseudo_random_uint32(uint64_t seed, uint64_t numberOfBits, void* sks) {
FheUint32* result = NULL;

checked_set_server_key(sks);

const int r = generate_oblivious_pseudo_random_bits_fhe_uint32(&result, seed, seed, numberOfBits);
if(r != 0) return NULL;

return result;
}

void* generate_oblivious_pseudo_random_uint64(uint64_t seed, uint64_t numberOfBits, void* sks) {
FheUint64* result = NULL;

checked_set_server_key(sks);

const int r = generate_oblivious_pseudo_random_bits_fhe_uint64(&result, seed, seed, numberOfBits);
if(r != 0) return NULL;

return result;
}

void* add_fhe_uint4(void* ct1, void* ct2, void* sks)
{
FheUint4* result = NULL;
Expand Down
10 changes: 10 additions & 0 deletions fhevm/tfhe/tfhe_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ void destroy_fhe_uint64(void* ct);

void destroy_fhe_uint160(void* ct);

void* generate_oblivious_pseudo_random_uint4(uint64_t seed, uint64_t numberOfBits, void* sks);

void* generate_oblivious_pseudo_random_uint8(uint64_t seed, uint64_t numberOfBits, void* sks);

void* generate_oblivious_pseudo_random_uint16(uint64_t seed, uint64_t numberOfBits, void* sks);

void* generate_oblivious_pseudo_random_uint32(uint64_t seed, uint64_t numberOfBits, void* sks);

void* generate_oblivious_pseudo_random_uint64(uint64_t seed, uint64_t numberOfBits, void* sks);

void* add_fhe_uint4(void* ct1, void* ct2, void* sks);

void* add_fhe_uint8(void* ct1, void* ct2, void* sks);
Expand Down
Loading