How to fhe encrypt a private key using Concrete Python

I am trying to use Concrete: zamafhe/concrete-python:v2.0.0, I get the function add (to add C1 and C2) and the compiler as follows:

def add(x: int, y: int):
    return x + y

compiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})


inputset = [(48915617476484211273115281063704461783033490425405257564258124598871191647089, 48915617476484211273115281063704461783033490425405257564258124598871191647089), (0x0, 0x0), (0x3350, 0x3350)]
circuit = compiler.compile(inputset)

It works correctly if the inputset has “small numbers” integer, however, I am getting an error when the compiler tries to compile the circuit based on the above input data:
" … of type int which is not acceptable either because of the type or because of overflow"

I need that to encrypt a private key of an Ethereum account. In the input set I put an example of private key.

Any idea or clue is much appreciated. Many thanks

Hi @zakwanj,

You can split your numbers into small chunks and work on chunks :slightly_smiling_face:

import numpy as np
from concrete import fhe

def to_chunks(number, width=256, chunk_size=8):
    assert width % chunk_size == 0
    return [
        (number >> i) & ((2**chunk_size) - 1)
        for i in range(0, width, chunk_size)
    ]

def to_number(chunks, chunk_size=8):
    return sum(byte << (chunk_size * i) for i, byte in enumerate(chunks))


def add(x, y):
    return x + y

compiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})


inputset = [
    (
        48915617476484211273115281063704461783033490425405257564258124598871191647089,
        48915617476484211273115281063704461783033490425405257564258124598871191647089,
    ),
    (
        0x0,
        0x0,
    ),
    (
        0x3350,
        0x3350,
    )
]
chunked_inputset = [tuple(to_chunks(value) for value in input) for input in inputset]

circuit = compiler.compile(chunked_inputset, show_graph=True)
circuit.keys.generate()


sample = (0x123, 0x456)
chunked_sample = tuple(to_chunks(value) for value in sample)

chunked_result = circuit.encrypt_run_decrypt(*chunked_sample)
result = to_number(chunked_result)

assert result == sample[0] + sample[1]

Hope this helps!

1 Like

Many thanks @umutsahin for your quick reply and great help. Nice catch. You code works for me perfectly for the example of sample = (0x123, 0x456). However, it seems there is an issue with calculating the results for real private key (big numbers) as follows:

from concrete import fhe
import secrets
from ast import literal_eval

import numpy as np
from concrete import fhe

def to_chunks(number, width=256, chunk_size=8):
    assert width % chunk_size == 0
    return [
        (number >> i) & ((2**chunk_size) - 1)
        for i in range(0, width, chunk_size)
    ]

def to_number(chunks, chunk_size=8):
    return sum(byte << (chunk_size * i) for i, byte in enumerate(chunks))


def add(x, y):
    return x + y

compiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})


inputset = [
    (
        48915617476484211273115281063704461783033490425405257564258124598871191647089,
        48915617476484211273115281063704461783033490425405257564258124598871191647089,
    ),
    (
        0x0,
        0x0,
    ),
    (
        0x3350,
        0x3350,
    )
]
chunked_inputset = [tuple(to_chunks(value) for value in input) for input in inputset]

circuit = compiler.compile(chunked_inputset, show_graph=True)
circuit.keys.generate()


sk1 = secrets.token_hex(32)
print(sk1)

sk2 = secrets.token_hex(32)
print(sk2)

sample = (literal_eval("0x" + sk1), literal_eval("0x" + sk2))
chunked_sample = tuple(to_chunks(value) for value in sample)

chunked_result = circuit.encrypt_run_decrypt(*chunked_sample)
result = to_number(chunked_result)

print (result)
print (sample)

assert result == sample[0] + sample[1]

That’s to be expected as the body of the loop isn’t adjusted for chunked addition :slightly_smiling_face:

If you change the body to:

CHUNK_SIZE = 8

def add(x, y):
    carry = fhe.zero()
    raw_addition = x + y

    result_chunks = []
    for i in range(raw_addition.size):
        raw_addition_and_carry = raw_addition[i] + carry

        carry = raw_addition_and_carry >> CHUNK_SIZE
        result_chunk = raw_addition_and_carry - (carry * (2**CHUNK_SIZE))

        result_chunks.append(result_chunk)

    return fhe.array(result_chunks)

addition should work as you’d expect.

Note that execution will be a lot slower because it’s using table lookups now.

Hope this helps!

1 Like

Thank you @umutsahin again. Yes, indeed it is much slower now. Even I waited for ages and I didn’t get it run. I just killed the process. I see that the statement:

fhe.array(result_chunks)

takes ages to complete and never complete for me. I am wondering if there is any other alternative because I know that it should be much faster based on this post FHE-DKSAP: Fully Homomorphic Encryption based Dual Key Stealth Address Protocol - #8 by Mason-Mind - Cryptography - Ethereum Research

I am trying to replicate their work. Any idea or clue is much much appreciated.

Hey, it’s probably because chunk size of 8 results in 9-bit intermediate integers, which are very slow. Could you try with chunk size of 4 please?

1 Like

Also, feel free to check GitHub - zama-ai/tfhe-rs: TFHE-rs: A Pure Rust implementation of the TFHE Scheme for Boolean and Integer Arithmetics Over Encrypted Data. as it has built in large integers with fast addition!

1 Like

Ok thank you very much for your help.

1 Like