Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update string encryption/decryption #9

Closed
wants to merge 2 commits into from
Closed
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
39 changes: 31 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,30 @@ int_cipher_text, signature = build_input_text(plaintext, user_aes_key, sender, c
- `int_cipher_text`: The integer representation of the ciphertext.
- `signature`: The generated signature.

### 8. `generate_rsa_keypair()`
### 8. `build_string_input_text(plaintext, user_aes_key, sender, contract, func_sig, signing_key)`

**Purpose:** Builds input text by encrypting the plaintext and signing it.

**Usage:**

```python
int_cipher_text, signature = build_string_input_text(plaintext, user_aes_key, sender, contract, func_sig, signing_key)
```

**Parameters:**

- `plaintext`: The plaintext message.
- `user_aes_key`: The user's AES key.
- `sender`: The sender's address.
- `contract`: The contract address.
- `func_sig`: The function signature.
- `signing_key`: The private key used for signing.

**Returns:**

- `input_text`: A dictionary of the form { "ciphertext": { "value": int[] }, "signature": bytes[] }

### 9. `generate_rsa_keypair()`

**Purpose:** Generates an RSA key pair.

Expand All @@ -238,7 +261,7 @@ private_key_bytes, public_key_bytes = generate_rsa_keypair()
- `private_key_bytes`: The serialized private key.
- `public_key_bytes`: The serialized public key.

### 9. `encrypt_rsa(public_key_bytes, plaintext)`
### 10. `encrypt_rsa(public_key_bytes, plaintext)`

**Purpose:** Encrypts plaintext using RSA encryption with a provided public key.

Expand All @@ -257,7 +280,7 @@ ciphertext = encrypt_rsa(public_key_bytes, plaintext)

- `ciphertext`: The encrypted message.

### 10. `decrypt_rsa(private_key_bytes, ciphertext)`
### 11. `decrypt_rsa(private_key_bytes, ciphertext)`

**Purpose:** Decrypts ciphertext using RSA decryption with a provided private key.

Expand All @@ -276,7 +299,7 @@ plaintext = decrypt_rsa(private_key_bytes, ciphertext)

- `plaintext`: The decrypted message.

### 11. `keccak256(data)`
### 12. `keccak256(data)`

**Purpose:** Computes the Keccak-256 hash of the provided data.

Expand All @@ -294,7 +317,7 @@ hash_value = keccak256(data)

- `hash_value`: The computed hash.

### 12. `get_func_sig(function_signature)`
### 13. `get_func_sig(function_signature)`

**Purpose:** Computes the function signature hash using Keccak-256.

Expand All @@ -312,7 +335,7 @@ func_sig_hash = get_func_sig(function_signature)

- `func_sig_hash`: The first 4 bytes of the computed hash.

### 13. `decrypt_uint(ciphertext, user_key)`
### 14. `decrypt_uint(ciphertext, user_key)`

**Purpose:** Decrypts a value stored in a contract using a user key

Expand All @@ -331,7 +354,7 @@ plaintext = decrypt_uint(ciphertext, user_key)

- `result`: The decrypted value.

### 14. `decrypt_string(ciphertext, user_key)`
### 15. `decrypt_string(ciphertext, user_key)`

**Purpose:** Decrypts a value stored in a contract using a user key

Expand All @@ -343,7 +366,7 @@ plaintext = decrypt_string(ciphertext, user_key)

**Parameters:**

- `ciphertext`: The value to be decrypted.
- `ciphertext`: A dictionary of the form { "value": int[] } where each cell holds up to 8 characters (padded at the end with zeroes) encrypted
- `userKey`: The user's AES key.

**Returns:**
Expand Down
104 changes: 52 additions & 52 deletions coti/crypto_utils.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import binascii
from array import array

from attributedict.collections import AttributeDict
from Crypto.Cipher import AES
from Crypto.Hash import keccak
from Crypto.Random import get_random_bytes
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa
from eth_keys import keys
from math import ceil

block_size = AES.block_size
address_size = 20
func_sig_size = 4
function_selector_size = 4
ct_size = 32
key_size = 32

Expand All @@ -24,6 +22,7 @@ def encrypt(key, plaintext):

# Ensure key size is 128 bits (16 bytes)
if len(key) != block_size:
print(len(key), block_size)
raise ValueError("Key size must be 128 bits.")

# Create a new AES cipher block using the provided key
Expand Down Expand Up @@ -75,22 +74,24 @@ def generate_aes_key():
return key


def sign_input_text(sender, addr, func_sig, ct, key):
def sign_input_text(sender, addr, function_selector, ct, key):
function_selector_bytes = bytes.fromhex(function_selector[2:])

# Ensure all input sizes are the correct length
if len(sender) != address_size:
raise ValueError(f"Invalid sender address length: {len(sender)} bytes, must be {address_size} bytes")
if len(addr) != address_size:
raise ValueError(f"Invalid contract address length: {len(addr)} bytes, must be {address_size} bytes")
if len(func_sig) != func_sig_size:
raise ValueError(f"Invalid signature size: {len(func_sig)} bytes, must be {func_sig_size} bytes")
if len(function_selector_bytes) != function_selector_size:
raise ValueError(f"Invalid signature size: {len(function_selector_bytes)} bytes, must be {function_selector_size} bytes")
if len(ct) != ct_size:
raise ValueError(f"Invalid ct length: {len(ct)} bytes, must be {ct_size} bytes")
# Ensure the key is the correct length
if len(key) != key_size:
raise ValueError(f"Invalid key length: {len(key)} bytes, must be {key_size} bytes")

# Create the message to be signed by appending all inputs
message = sender + addr + func_sig + ct
message = sender + addr + function_selector_bytes + ct

return sign(message, key)

Expand All @@ -103,7 +104,7 @@ def sign(message, key):
return signature


def build_input_text(plaintext, user_aes_key, sender, contract, func_sig, signing_key):
def build_input_text(plaintext, user_aes_key, sender, contract, function_selector, signing_key):
sender_address_bytes = bytes.fromhex(sender.address[2:])
contract_address_bytes = bytes.fromhex(contract.address[2:])

Expand All @@ -114,26 +115,44 @@ def build_input_text(plaintext, user_aes_key, sender, contract, func_sig, signin
ciphertext, r = encrypt(user_aes_key, plaintext_bytes)
ct = ciphertext + r

# Create the function signature
func_hash = get_func_sig(func_sig)
# Sign the message
signature = sign_input_text(sender_address_bytes, contract_address_bytes, func_hash, ct, signing_key)
signature = sign_input_text(sender_address_bytes, contract_address_bytes, function_selector, ct, signing_key)

# Convert the ct to an integer
int_cipher_text = int.from_bytes(ct, byteorder='big')

return int_cipher_text, signature


def build_string_input_text(plaintext, user_aes_key, sender, contract, func_sig, signing_key):
encoded_plaintext = array('B', plaintext.encode('utf-8'))
encrypted_str = [{'ciphertext': 0, 'signature': b''} for _ in range(len(encoded_plaintext))]
for i in range(len(encoded_plaintext)):
ct_int, signature = build_input_text(int(encoded_plaintext[i]), user_aes_key, sender, contract,
func_sig, signing_key)
encrypted_str[i] = {'ciphertext': ct_int, 'signature': signature}
def build_string_input_text(plaintext, user_aes_key, sender, contract, function_selector, signing_key):
input_text = {
'ciphertext': {
'value': []
},
'signature': []
}

encoded_plaintext = bytearray(list(plaintext.encode('utf-8')))

for i in range(ceil(len(encoded_plaintext) / 8)):
start_idx = i * 8
end_idx = min(start_idx + 8, len(encoded_plaintext))

byte_arr = encoded_plaintext[start_idx:end_idx] + bytearray(8 - (end_idx - start_idx))

return encrypted_str
ct_int, sig = build_input_text(
int.from_bytes(byte_arr, 'big'),
user_aes_key,
sender,
contract,
function_selector,
signing_key
)

input_text['ciphertext']['value'].append(ct_int)
input_text['signature'].append(sig)

return input_text


def decrypt_uint(ciphertext, user_key):
Expand All @@ -154,18 +173,22 @@ def decrypt_uint(ciphertext, user_key):


def decrypt_string(ciphertext, user_key):
string_from_input_tx = ""
for input_text_from_tx in ciphertext:
decrypted_input_from_tx = decrypt_uint(input_text_from_tx, user_key)
byte_length = (decrypted_input_from_tx.bit_length() + 7) // 8 # calculate the byte length
print(ciphertext)

decrypted_string = ""

for value in ciphertext['value']:
decrypted = decrypt_uint(value, user_key)

byte_length = (decrypted.bit_length() + 7) // 8 # calculate the byte length

# Convert the integer to bytes
decrypted_bytes = decrypted_input_from_tx.to_bytes(byte_length, byteorder='big')
decrypted_bytes = decrypted.to_bytes(byte_length, byteorder='big')

# Decode the bytes to a string
string_from_input_tx += decrypted_bytes.decode('utf-8')

return string_from_input_tx
decrypted_string += decrypted_bytes.decode('utf-8')
return decrypted_string.strip('\0')


def generate_rsa_keypair():
Expand Down Expand Up @@ -205,26 +228,3 @@ def decrypt_rsa(private_key_bytes, ciphertext):
)
)
return plaintext


# Function to compute Keccak-256 hash
def keccak256(data):
# Create Keccak-256 hash object
hash_obj = keccak.new(digest_bits=256)

# Update hash object with data
hash_obj.update(data)

# Compute hash and return
return hash_obj.digest()


def get_func_sig(function_signature):
# Convert function signature to bytes
function_signature_bytes = function_signature.encode('utf-8')

# Compute Keccak-256 hash on the function signature
function_signature_bytes_hash = keccak256(function_signature_bytes)

# Take first 4 bytes of the hash
return function_signature_bytes_hash[:4]
8 changes: 0 additions & 8 deletions coti/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,6 @@ def is_gas_units_estimation_valid(web3, tx):
return False, estimate_gas


def get_function_signature(function_abi):
# Extract the input types from the ABI
input_types = ','.join([param['type'] for param in function_abi.get('inputs', [])])

# Generate the function signature
return f"{function_abi['name']}({input_types})"


def deploy_contract(contract, kwargs, tx_params):
func = contract.constructor(**kwargs)
return exec_func_via_transaction(func, tx_params)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
url='https://github.com/coti-io/coti-sdk-python',
keywords='COTI SDK Privacy',
install_requires=[
'pycryptodome==3.19.0', 'cryptography==3.4.8', 'eth-keys==0.4.0', 'eth-account==0.10.0', 'web3==6.11.2'
'pycryptodome==3.19.0', 'cryptography==3.4.8', 'eth-keys==0.4.0', 'eth-account==0.10.0', 'web3==6.11.2', 'attributedict==0.3.0'
],
python_requires=">=3.9",
)