From 59377cba1688f5832a1b6a45d92ed0aed937d491 Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Mon, 1 Jun 2020 12:48:57 +0300 Subject: [PATCH 01/20] Update encryption-specs.md --- docs/encryption-specs.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index af8dc4b9d..76205eaa9 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -278,23 +278,19 @@ TODO reasoning - When a contract is deployed (i.e., on contract init), `contract_id` is generated inside of the enclave as follows: ```js -contract_id_payload = sha256(concat(msg_sender, block_height, contract_code)); +contract_id_payload = sha256(concat(msg_sender, block_height)); encryption_key = hkdf({ salt: hkfd_salt, ikm: concat(consensus_state_ikm, contract_id_payload), }); -iv = sha256(concat(consensus_state_iv, contract_id_payload)).slice(0, 12); // truncate because iv is only 96 bits - -encrypted_contract_id_payload = aes_256_gcm_encrypt({ - iv: iv, +authenticated_contract_id_payload = HMAC_SHA256({ key: encryption_key, - data: null, - aad: concat(contract_id_payload, code_hash, iv), + data: concat(contract_id_payload, code_hash), }); -contract_id = concat(contract_id_payload, encrypted_contract_id_payload, iv); +contract_id = concat(contract_id_payload, authenticated_contract_id_payload); ``` - Every time a contract execution is called, `contract_id` should be sent to the enclave. From 6dac5a3f4f97e7acc369a37ef47160566ba11186 Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Mon, 1 Jun 2020 13:01:48 +0300 Subject: [PATCH 02/20] Update encryption-specs.md --- docs/encryption-specs.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 76205eaa9..ef972c850 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -280,40 +280,40 @@ TODO reasoning ```js contract_id_payload = sha256(concat(msg_sender, block_height)); -encryption_key = hkdf({ +authentication_key = hkdf({ salt: hkfd_salt, + info: b"contract_id", ikm: concat(consensus_state_ikm, contract_id_payload), }); authenticated_contract_id_payload = HMAC_SHA256({ - key: encryption_key, + key: authentication_key, data: concat(contract_id_payload, code_hash), }); -contract_id = concat(contract_id_payload, authenticated_contract_id_payload); +tm_contract_id = concat(contract_id_payload, authenticated_contract_id_payload); ``` - Every time a contract execution is called, `contract_id` should be sent to the enclave. - In the enclave, the following verification needs to happen: ```js -contract_id_payload = contract_id.slice(0, 32); -encrypted_contract_id_payload = contract_id.slice(32, 64); -iv = contract_id.slice(64); +contract_id_payload = tm_contract_id.slice(0, 32); +expected_contract_id_payload = tm_contract_id.slice(32, 64); -encryption_key = hkdf({ +authentication_key = hkdf({ salt: hkfd_salt, - ikm: concat(consensus_state_ikm,contract_id_payload), + info: b"contract_id", + ikm: concat(consensus_state_ikm, contract_id_payload), }); -(interpreted_payload, interpreted_code_hash) = aes_256_gcm_decrypt({ - iv: iv, - key: encryption_key, - data: encrypted_contract_id_payload +calculated_contract_id_payload = HMAC_SHA256({ + key: authentication_key, + data: concat(contract_id_payload, code_hash), }); -assert(interpreted_payload == contract_id_payload); -assert(interpreted_code_hash == sha256(contract_code); +assert(code_hash == sha256(contract_code)); // doesn't this step happen already? +assert(authentication_key == calculated_contract_id_payload); ``` ## write_db(field_name, value) From 8151d20974e8e87334df3fcf0a3ac0745b5dc63c Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Mon, 1 Jun 2020 13:02:13 +0300 Subject: [PATCH 03/20] Update encryption-specs.md --- docs/encryption-specs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index ef972c850..50bd27211 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -313,7 +313,7 @@ calculated_contract_id_payload = HMAC_SHA256({ }); assert(code_hash == sha256(contract_code)); // doesn't this step happen already? -assert(authentication_key == calculated_contract_id_payload); +assert(calculated_contract_id_payload == expected_contract_id_payload); ``` ## write_db(field_name, value) From dc7b7da5bae99cea1ff663683ff8ddbd6deeb2b0 Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Mon, 1 Jun 2020 13:47:48 +0300 Subject: [PATCH 04/20] Update encryption-specs.md --- docs/encryption-specs.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 50bd27211..a45c56649 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -274,7 +274,7 @@ TODO reasoning ## `contract_id` -- `contract_id` is a concatenation of two values: `contract_id_payload || encrypted_contract_id_payload`. +- `contract_id` is a concatenation of two values: `contract_id_payload || authenticated_contract_id_payload`. - When a contract is deployed (i.e., on contract init), `contract_id` is generated inside of the enclave as follows: ```js @@ -312,7 +312,6 @@ calculated_contract_id_payload = HMAC_SHA256({ data: concat(contract_id_payload, code_hash), }); -assert(code_hash == sha256(contract_code)); // doesn't this step happen already? assert(calculated_contract_id_payload == expected_contract_id_payload); ``` From 7af19aa4f1a93b8511855870d9c588ead4c44287 Mon Sep 17 00:00:00 2001 From: Assaf Morami Date: Tue, 2 Jun 2020 10:53:47 +0300 Subject: [PATCH 05/20] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 010615c1d..f29301cc7 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Secret Network is secured by the SCRT coin (Secret), which is used for fees, sta # Implementation Discussions - [An Update on the Encryption Protocol](https://forum.enigma.co/t/an-update-on-the-encryption-protocol/1641) +- [Hard Forks and Network Upgrades](https://forum.enigma.co/t/hard-forks-and-network-upgrades/1670) +- [Don’t trust, verify (an untrusted host)](https://forum.scrt.network/t/dont-trust-verify-an-untrusted-host/1669) - [Secret Contracts on Secret Network](https://forum.enigma.co/t/secret-contracts-on-enigma-blockchain/1284) - [Network key management/agreement](https://forum.enigma.co/t/network-key-management-agreement/1324) - [Input/Output/State Encryption/Decryption protocol](https://forum.enigma.co/t/input-output-state-encryption-decryption-protocol/1325) From b81afcf0a0ff685c77db00f0d225a2bd87491672 Mon Sep 17 00:00:00 2001 From: Tom Langer Date: Tue, 2 Jun 2020 15:39:43 +0300 Subject: [PATCH 06/20] Updated read_db & write_db to AES_SIV --- docs/encryption-specs.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index a45c56649..1cbe7322e 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -322,34 +322,32 @@ current_state_ciphertext = internal_read_db(field_name); encryption_key = hkdf({ salt: hkfd_salt, - ikm: concat(consensus_state_ikm, field_name, contact_id), + ikm: concat(consensus_state_ikm, field_name, contract_id), }); if (current_state_ciphertext == null) { // field_name doesn't yet initialized in state - iv = sha256(concat(consensus_state_iv, value)).slice(0, 12); // truncate because iv is only 96 bits + ad = sha256(concat(consensus_state_ikm, value)).slice(0, 12); // truncate because iv is only 96 bits } else { - // read previous_iv, verify it, calculate new iv - previous_iv = current_state_ciphertext.slice(0, 12); // first 12 bytes + // read previous_ad, verify it, calculate new iv + previous_ad = current_state_ciphertext.slice(0, 12); // first 12 bytes current_state_ciphertext = current_state_ciphertext.slice(12); // skip first 12 bytes aes_256_gcm_decrypt({ - iv: previous_iv, key: encryption_key, data: current_state_ciphertext, - aad: previous_iv, + ad: previous_ad, }); // just to authenticate previous_iv - iv = sha256(concat(consensus_state_iv, value, previous_iv)).slice(0, 12); // truncate because iv is only 96 bits + ad = sha256(concat(consensus_state_ikm, value, previous_ad)).slice(0, 12); // truncate because iv is only 96 bits } -new_state_ciphertext = aes_256_gcm_encrypt({ - iv: iv, +new_state_ciphertext = aes_128_siv_encrypt({ key: encryption_key, data: value, - aad: iv, + ad: ad, }); -new_state = concat(iv, new_state_ciphertext); +new_state = concat(ad, new_state_ciphertext); internal_write_db(field_name, new_state); ``` @@ -361,7 +359,7 @@ current_state_ciphertext = internal_read_db(field_name); encryption_key = hkdf({ salt: hkfd_salt, - ikm: concat(consensus_state_ikm, field_name, sha256(contract_wasm_binary)), // TODO diffrentiate between same binaries for different contracts + ikm: concat(consensus_state_ikm, field_name, contract_id), }); if (current_state_ciphertext == null) { @@ -369,14 +367,13 @@ if (current_state_ciphertext == null) { return null; } -// read iv, verify it, calculate new iv -iv = current_state_ciphertext.slice(0, 12); // first 12 bytes +// read ad, verify it +ad = current_state_ciphertext.slice(0, 12); // first 12 bytes current_state_ciphertext = current_state_ciphertext.slice(12); // skip first 12 bytes -current_state_plaintext = aes_256_gcm_decrypt({ - iv: iv, +current_state_plaintext = aes_128_siv_decrypt({ key: encryption_key, data: current_state_ciphertext, - aad: iv, + ad: ad, }); return current_state_plaintext; From f8e8b6890c5b7d356bee64743904b700696e9774 Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Tue, 2 Jun 2020 20:38:12 +0300 Subject: [PATCH 07/20] Update encryption-specs.md --- docs/encryption-specs.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 1cbe7322e..715a302d4 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -272,47 +272,47 @@ TODO reasoning - Ciphertext is prepended with the `iv` so that the next read will be able to decrypt it. `iv` is also authenticated with the AES-256-GCM AAD. - `iv` is derive from `sha256(consensus_state_iv || value || previous_iv)` in order to prevent tx rollback attacks that can force `iv` and `encryption_key` reuse. This also prevents using the same `iv` in different instances of the same contract. `consensus_state_iv` prevents exposing `value` by comparing `iv` to `previos_iv`. -## `contract_id` +## `contract_key` -- `contract_id` is a concatenation of two values: `contract_id_payload || authenticated_contract_id_payload`. -- When a contract is deployed (i.e., on contract init), `contract_id` is generated inside of the enclave as follows: +- `contract_key` is a concatenation of two values: `signer_id || authenticated_contract_id`. +- When a contract is deployed (i.e., on contract init), `contract_key` is generated inside of the enclave as follows: ```js -contract_id_payload = sha256(concat(msg_sender, block_height)); +signer_id = sha256(concat(msg_sender, block_height)); authentication_key = hkdf({ salt: hkfd_salt, info: b"contract_id", - ikm: concat(consensus_state_ikm, contract_id_payload), + ikm: concat(consensus_state_ikm, signer_id), }); -authenticated_contract_id_payload = HMAC_SHA256({ +authenticated_contract_id = HMAC_SHA256({ key: authentication_key, - data: concat(contract_id_payload, code_hash), + data: code_hash, }); -tm_contract_id = concat(contract_id_payload, authenticated_contract_id_payload); +contract_key = concat(signer_id, authenticated_contract_id); ``` -- Every time a contract execution is called, `contract_id` should be sent to the enclave. +- Every time a contract execution is called, `contract_key` should be sent to the enclave. - In the enclave, the following verification needs to happen: ```js -contract_id_payload = tm_contract_id.slice(0, 32); -expected_contract_id_payload = tm_contract_id.slice(32, 64); +signer_id = contract_key.slice(0, 32); +expected_contract_id = contract_key.slice(32, 64); authentication_key = hkdf({ salt: hkfd_salt, info: b"contract_id", - ikm: concat(consensus_state_ikm, contract_id_payload), + ikm: concat(consensus_state_ikm, signer_id), }); -calculated_contract_id_payload = HMAC_SHA256({ +calculated_contract_id = HMAC_SHA256({ key: authentication_key, - data: concat(contract_id_payload, code_hash), + data: code_hash, }); -assert(calculated_contract_id_payload == expected_contract_id_payload); +assert(calculated_contract_id == expected_contract_id); ``` ## write_db(field_name, value) From 300c3a0ebb6351829b4c0fe60eaba2e5244a3976 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 11:38:27 +0300 Subject: [PATCH 08/20] tx input siv --- docs/encryption-specs.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 715a302d4..0a45de846 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -22,7 +22,7 @@ - [Decrypting `encrypted_consensus_seed`](#decrypting-encrypted_consensus_seed) - [New Node Registration Epilogue](#new-node-registration-epilogue) - [Contracts State Encryption](#contracts-state-encryption) - - [`contract_id`](#contract_id) + - [`contract_key`](#contract_key) - [write_db(field_name, value)](#write_dbfield_name-value) - [read_db(field_name)](#read_dbfield_name) - [Transaction Encryption](#transaction-encryption) @@ -194,7 +194,7 @@ TODO reasoning - The output of the `enigmacli tx register auth` transaction is `consensus_seed` encrypted with AES-SIV-128, `seed_exchange_key` as the encryption key, using the public key of the registering node for the AD. ```js -encrypted_consensus_seed = aes_siv_128_encrypt({ +encrypted_consensus_seed = aes_128_siv_encrypt({ key: seed_exchange_key, data: consensus_seed, ad: new_node_public_key, @@ -213,8 +213,9 @@ TODO reasoning - `seed_exchange_key`: An AES-SIV-128 encryption key. Will be used to decrypt `consensus_seed`. - `seed_exchange_key` is derived the following way: + - `seed_exchange_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_seed_exchange_pubkey` (public in `genesis.json`) and `registration_privkey` (available only inside the new node's Enclave). - + - `seed_exchange_key` is derived using HKDF-SHA256 with `seed_exchange_ikm` and `nonce`. ```js @@ -238,7 +239,7 @@ TODO reasoning - Seal `consensus_seed` to disk at `"$HOME/.enigmad/sgx-secrets/consensus_seed.sealed"`. ```js -consensus_seed = aes_256_gcm_decrypt({ +consensus_seed = aes_128_siv_decrypt({ key: seed_exchange_key, data: encrypted_consensus_seed, ad: new_node_public_key, @@ -282,11 +283,11 @@ signer_id = sha256(concat(msg_sender, block_height)); authentication_key = hkdf({ salt: hkfd_salt, - info: b"contract_id", + info: contract_id, ikm: concat(consensus_state_ikm, signer_id), }); -authenticated_contract_id = HMAC_SHA256({ +authenticated_contract_id = hmac_sha256({ key: authentication_key, data: code_hash, }); @@ -307,7 +308,7 @@ authentication_key = hkdf({ ikm: concat(consensus_state_ikm, signer_id), }); -calculated_contract_id = HMAC_SHA256({ +calculated_contract_id = hmac_sha256({ key: authentication_key, data: code_hash, }); @@ -333,7 +334,7 @@ if (current_state_ciphertext == null) { previous_ad = current_state_ciphertext.slice(0, 12); // first 12 bytes current_state_ciphertext = current_state_ciphertext.slice(12); // skip first 12 bytes - aes_256_gcm_decrypt({ + aes_128_siv_decrypt({ key: encryption_key, data: current_state_ciphertext, ad: previous_ad, @@ -383,12 +384,10 @@ return current_state_plaintext; TODO reasoning -- `tx_encryption_key`: An AES-256-GCM encryption key. Will be used to encrypt tx inputs and decrypt tx outpus. +- `tx_encryption_key`: An AES-128-SIV encryption key. Will be used to encrypt tx inputs and decrypt tx outpus. - `tx_encryption_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_io_exchange_pubkey` and `tx_sender_wallet_privkey` (on the sender's side). - `tx_encryption_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_io_exchange_privkey` and `tx_sender_wallet_pubkey` (inside the enclave of every full node). - `tx_encryption_key` is derived using HKDF-SHA256 with `tx_encryption_ikm` and a random number `nonce`. This is to prevent using the same key for the same tx sender multiple times. -- `iv_input` for the input is randomly generated on the client side by the transation sender. -- `iv`s for the output are derived from `iv_input` using HKDF-SHA256. ## Input @@ -409,16 +408,15 @@ tx_encryption_key = hkdf({ iv_input = true_random({ bytes: 12 }); -aad = concat(iv_input, nonce, tx_sender_wallet_pubkey); +ad = concat(nonce, tx_sender_wallet_pubkey); -encrypted_msg = aes_256_gcm_encrypt({ - iv: iv_input, +encrypted_msg = aes_128_siv_encrypt({ key: tx_encryption_key, data: msg, - aad: aad, + ad: ad, }); -tx_input = concat(aad, encrypted_msg); +tx_input = concat(ad, encrypted_msg); ``` ### On the consensus layer, inside the Enclave of every full node From e0c78d101b29704429775f9e6f3fbfc1c6c1152f Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 11:44:41 +0300 Subject: [PATCH 09/20] tx input decryption --- docs/encryption-specs.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 0a45de846..87d528b92 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -422,10 +422,9 @@ tx_input = concat(ad, encrypted_msg); ### On the consensus layer, inside the Enclave of every full node ```js -iv_input = tx_input.slice(0, 12); // 12 bytes -nonce = tx_input.slice(12, 44); // 32 bytes -tx_sender_wallet_pubkey = tx_input.slice(44, 77); // 33 bytes, compressed secp256k1 public key -encrypted_msg = tx_input.slice(77); +nonce = tx_input.slice(0, 32); // 32 bytes +tx_sender_wallet_pubkey = tx_input.slice(32, 65); // 33 bytes, compressed secp256k1 public key +encrypted_msg = tx_input.slice(65); tx_encryption_ikm = ecdh({ privkey: consensus_io_exchange_privkey, @@ -437,11 +436,10 @@ tx_encryption_key = hkdf({ ikm: concat(tx_encryption_ikm, nonce), }); // 256 bits -msg = aes_256_gcm_decrypt({ - iv: iv_input, +msg = aes_128_siv_decrypt({ key: tx_encryption_key, data: encrypted_msg, - aad: concat(iv_input, nonce, tx_sender_wallet_pubkey), // or: tx_input.slice(0, 77) + ad: concat(nonce, tx_sender_wallet_pubkey), // or: tx_input.slice(0, 65) }); ``` From 7b5d9012219260a64efb12d070d6709e0a414ce0 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 11:45:53 +0300 Subject: [PATCH 10/20] remove unused iv --- docs/encryption-specs.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 87d528b92..5ea58bbed 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -406,8 +406,6 @@ tx_encryption_key = hkdf({ ikm: concat(tx_encryption_ikm, nonce), }); // 256 bits -iv_input = true_random({ bytes: 12 }); - ad = concat(nonce, tx_sender_wallet_pubkey); encrypted_msg = aes_128_siv_encrypt({ From 47f6f2f3c6a48470a213fbf1768d309d031d7473 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 11:53:53 +0300 Subject: [PATCH 11/20] a bit of reasoning behind AES-128-SIV --- docs/encryption-specs.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 5ea58bbed..2499a5ce0 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -170,7 +170,11 @@ TODO reasoning TODO reasoning -- `seed_exchange_key`: An AES-SIV-128 encryption key. Will be used to send `consensus_seed` to the new node. +- `seed_exchange_key`: An [AES-128-SIV](https://tools.ietf.org/html/rfc5297) encryption key. Will be used to send `consensus_seed` to the new node. +- AES-128-SIV was chosen to prevent IV misuse by client libraries. + - https://tools.ietf.org/html/rfc5297 + - https://github.com/miscreant/meta + - The input key is 256 bits, but half of it is used to derive the internal IV. - `seed_exchange_key` is derived the following way: - `seed_exchange_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_seed_exchange_privkey` and `registration_pubkey`. - `seed_exchange_key` is derived using HKDF-SHA256 from `seed_exchange_ikm` and `nonce`. @@ -191,7 +195,7 @@ seed_exchange_key = hkdf({ TODO reasoning -- The output of the `enigmacli tx register auth` transaction is `consensus_seed` encrypted with AES-SIV-128, `seed_exchange_key` as the encryption key, using the public key of the registering node for the AD. +- The output of the `enigmacli tx register auth` transaction is `consensus_seed` encrypted with AES-128-SIV, `seed_exchange_key` as the encryption key, using the public key of the registering node for the AD. ```js encrypted_consensus_seed = aes_128_siv_encrypt({ @@ -211,7 +215,7 @@ return encrypted_consensus_seed; TODO reasoning -- `seed_exchange_key`: An AES-SIV-128 encryption key. Will be used to decrypt `consensus_seed`. +- `seed_exchange_key`: An AES-128-SIV encryption key. Will be used to decrypt `consensus_seed`. - `seed_exchange_key` is derived the following way: - `seed_exchange_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_seed_exchange_pubkey` (public in `genesis.json`) and `registration_privkey` (available only inside the new node's Enclave). @@ -234,7 +238,7 @@ seed_exchange_key = hkdf({ TODO reasoning -- `encrypted_consensus_seed` is encrypted with AES-SIV-128, `seed_exchange_key` as the encryption key and the public key of the registering node as the `ad` as the decryption additional data. +- `encrypted_consensus_seed` is encrypted with AES-128-SIV, `seed_exchange_key` as the encryption key and the public key of the registering node as the `ad` as the decryption additional data. - The new node now has all of these^ parameters inside its Enclave, so it's able to decrypt `consensus_seed` from `encrypted_consensus_seed`. - Seal `consensus_seed` to disk at `"$HOME/.enigmad/sgx-secrets/consensus_seed.sealed"`. From 260832d1b8b3ab83fd39c52c8a2930d0245934a3 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 12:19:10 +0300 Subject: [PATCH 12/20] now state field_name is encrypted, rename contract_id to contract_key --- docs/encryption-specs.md | 57 ++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 2499a5ce0..af9f30c07 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -269,17 +269,14 @@ TODO reasoning - While executing a function call inside the Enclave as part of a transaction, the contract code can call `write_db(field_name, value)` and `read_db(field_name)`. - Contracts' state is stored on-chain inside a key-value store, thus the `field_name` must remain constant between calls. -- Good encryption doesn't use the same `encryption_key` and `iv` together more than once. This means that encrypting the same input twice yields different outputs, and therefore we cannot encrypt the `field_name` because the next time we want to query it we won't know where to look for it. - `encryption_key` is derived using HKDF-SHA256 from: - `consensus_state_ikm` - `field_name` - - `contact_id` -- Ciphertext is prepended with the `iv` so that the next read will be able to decrypt it. `iv` is also authenticated with the AES-256-GCM AAD. -- `iv` is derive from `sha256(consensus_state_iv || value || previous_iv)` in order to prevent tx rollback attacks that can force `iv` and `encryption_key` reuse. This also prevents using the same `iv` in different instances of the same contract. `consensus_state_iv` prevents exposing `value` by comparing `iv` to `previos_iv`. + - `contact_key` ## `contract_key` -- `contract_key` is a concatenation of two values: `signer_id || authenticated_contract_id`. +- `contract_key` is a concatenation of two values: `signer_id || authenticated_contract_key`. - When a contract is deployed (i.e., on contract init), `contract_key` is generated inside of the enclave as follows: ```js @@ -287,16 +284,16 @@ signer_id = sha256(concat(msg_sender, block_height)); authentication_key = hkdf({ salt: hkfd_salt, - info: contract_id, + info: "contract_key", ikm: concat(consensus_state_ikm, signer_id), }); -authenticated_contract_id = hmac_sha256({ +authenticated_contract_key = hmac_sha256({ key: authentication_key, data: code_hash, }); -contract_key = concat(signer_id, authenticated_contract_id); +contract_key = concat(signer_id, authenticated_contract_key); ``` - Every time a contract execution is called, `contract_key` should be sent to the enclave. @@ -304,46 +301,51 @@ contract_key = concat(signer_id, authenticated_contract_id); ```js signer_id = contract_key.slice(0, 32); -expected_contract_id = contract_key.slice(32, 64); +expected_contract_key = contract_key.slice(32, 64); authentication_key = hkdf({ salt: hkfd_salt, - info: b"contract_id", + info: "contract_key", ikm: concat(consensus_state_ikm, signer_id), }); -calculated_contract_id = hmac_sha256({ +calculated_contract_key = hmac_sha256({ key: authentication_key, data: code_hash, }); -assert(calculated_contract_id == expected_contract_id); +assert(calculated_contract_key == expected_contract_key); ``` ## write_db(field_name, value) ```js -current_state_ciphertext = internal_read_db(field_name); - encryption_key = hkdf({ salt: hkfd_salt, - ikm: concat(consensus_state_ikm, field_name, contract_id), + ikm: concat(consensus_state_ikm, field_name, contract_key), }); +encrypted_field_name = aes_128_siv_encrypt({ + key: encryption_key, + data: field_name, +}); + +current_state_ciphertext = internal_read_db(encrypted_field_name); + if (current_state_ciphertext == null) { // field_name doesn't yet initialized in state - ad = sha256(concat(consensus_state_ikm, value)).slice(0, 12); // truncate because iv is only 96 bits + ad = sha256(concat(encryption_key, value)); } else { // read previous_ad, verify it, calculate new iv - previous_ad = current_state_ciphertext.slice(0, 12); // first 12 bytes - current_state_ciphertext = current_state_ciphertext.slice(12); // skip first 12 bytes + previous_ad = current_state_ciphertext.slice(0, 32); // first 32 bytes/256 bits + current_state_ciphertext = current_state_ciphertext.slice(32); // skip first 32 bytes aes_128_siv_decrypt({ key: encryption_key, data: current_state_ciphertext, ad: previous_ad, }); // just to authenticate previous_iv - ad = sha256(concat(consensus_state_ikm, value, previous_ad)).slice(0, 12); // truncate because iv is only 96 bits + ad = sha256(concat(encryption_key, value, previous_ad)); } new_state_ciphertext = aes_128_siv_encrypt({ @@ -354,27 +356,32 @@ new_state_ciphertext = aes_128_siv_encrypt({ new_state = concat(ad, new_state_ciphertext); -internal_write_db(field_name, new_state); +internal_write_db(encrypted_field_name, new_state); ``` ## read_db(field_name) ```js -current_state_ciphertext = internal_read_db(field_name); - encryption_key = hkdf({ salt: hkfd_salt, - ikm: concat(consensus_state_ikm, field_name, contract_id), + ikm: concat(consensus_state_ikm, field_name, contract_key), }); +encrypted_field_name = aes_128_siv_encrypt({ + key: encryption_key, + data: field_name, +}); + +current_state_ciphertext = internal_read_db(encrypted_field_name); + if (current_state_ciphertext == null) { // field_name doesn't yet initialized in state return null; } // read ad, verify it -ad = current_state_ciphertext.slice(0, 12); // first 12 bytes -current_state_ciphertext = current_state_ciphertext.slice(12); // skip first 12 bytes +ad = current_state_ciphertext.slice(0, 32); // first 32 bytes/256 bits +current_state_ciphertext = current_state_ciphertext.slice(32); // skip first 32 bytes current_state_plaintext = aes_128_siv_decrypt({ key: encryption_key, data: current_state_ciphertext, From 7bbd2780f21817c3ab2bcb5e00b4c43d1825bd2b Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 12:32:10 +0300 Subject: [PATCH 13/20] gcm->siv for tx output encryption --- docs/encryption-specs.md | 67 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index af9f30c07..437fbabde 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -460,8 +460,7 @@ msg = aes_128_siv_decrypt({ - Messages can also instruct to send funds from the contract's wallet - There's a data section which is free form bytes to be inerperted by the client (or dApp) - And there's also an error section -- Therefore the output must be part-encrypted, so we need to use a new `iv` for each part. -- We'll use HKDF-SHA256 in combination with the `input_iv` and an `iv_counter` to derive a new `iv` for each part. +- Therefore the output must be partialy-encrypted. - An example output for an execution: ```js { @@ -503,8 +502,8 @@ msg = aes_128_siv_decrypt({ } } ``` -- Notice on a `Contract` message, the `msg` value should be the same `msg` as in our `tx_input`, so we need to prepend the new `iv_input`, the `nonce` and `tx_sender_wallet_pubkey` just like we did on the tx sender. -- For the rest of the encrypted outputs we only need to prepend the new `iv` for each encrypted value, as the tx sender can get `consensus_io_exchange_prubkey` from `genesis.json` and `nonce` from the `tx_input` that is attached to the `tx_output`. +- Notice on a `Contract` message, the `msg` value should be the same `msg` as in our `tx_input`, so we need to prepend the `nonce` and `tx_sender_wallet_pubkey` just like we did on the tx sender above. +- For the rest of the encrypted outputs we only need to send the ciphertext, as the tx sender can get `consensus_io_exchange_prubkey` from `genesis.json` and `nonce` from the `tx_input` that is attached to the `tx_output`. - An example output with an error: ```js { @@ -523,60 +522,62 @@ msg = aes_128_siv_decrypt({ ```js // already have from tx_input: // - tx_encryption_key -// - iv_input // - nonce -iv_counter = 1; - if (typeof output["err"] == "string") { - iv = hkdf({ - salt: hkfd_salt, - ikm: concat(input_iv, [iv_counter]), - }).slice(0, 12); // 96 bits - iv_counter += 1; - - encrypted_err = aes_256_gcm_encrypt({ - iv: iv, + encrypted_err = aes_128_siv_encrypt({ key: tx_encryption_key, data: output["err"], - aad: iv, }); - - output["err"] = base64_encode(concat(iv, encrypted_err)); // needs to be a string + output["err"] = base64_encode(encrypted_err); // needs to be a JSON string } else if (typeof output["ok"] == "string") { // query - // same as output["err"]... + // output["ok"] is handled the same way as output["err"]... + encrypted_query_result = aes_128_siv_encrypt({ + key: tx_encryption_key, + data: output["ok"], + }); + output["ok"] = base64_encode(encrypted_query_result); // needs to be a JSON string } else if (typeof output["ok"] == "object") { // execute for (m in output["ok"]["messages"]) { if (m["type"] == "Contract") { - iv_input = hkdf({ - salt: hkfd_salt, - ikm: concat(input_iv, [iv_counter]), - }).slice(0, 12); // 96 bits - iv_counter += 1; - - encrypted_msg = aes_256_gcm_encrypt({ - iv: iv, + encrypted_msg = aes_128_siv_encrypt({ key: tx_encryption_key, data: m["msg"], - aad: concat(iv_input, nonce, tx_sender_wallet_pubkey), + ad: concat(nonce, tx_sender_wallet_pubkey), }); // base64_encode because needs to be a string - // also turns into a tx_input so we also need to prepend iv_input, nonce and tx_sender_wallet_pubkey + // also turns into a tx_input so we also need to prepend nonce and tx_sender_wallet_pubkey m["msg"] = base64_encode( - concat(iv_input, nonce, tx_sender_wallet_pubkey, encrypted_msg) + concat(nonce, tx_sender_wallet_pubkey, encrypted_msg) ); } } for (l in output["ok"]["log"]) { - // l["key"] is the same as output["err"]... - // l["value"] is the same as output["err"]... + // l["key"] is handled the same way as output["err"]... + encrypted_log_key_name = aes_128_siv_encrypt({ + key: tx_encryption_key, + data: l["key"], + }); + l["key"] = base64_encode(encrypted_log_key_name); // needs to be a JSON string + + // l["value"] is handled the same way as output["err"]... + encrypted_log_value = aes_128_siv_encrypt({ + key: tx_encryption_key, + data: l["value"], + }); + l["value"] = base64_encode(encrypted_log_value); // needs to be a JSON string } - // output["ok"]["data"] is the same as output["err"]... + // output["ok"]["data"] is handled the same way as output["err"]... + encrypted_output_data = aes_128_siv_encrypt({ + key: tx_encryption_key, + data: output["ok"]["data"], + }); + output["ok"]["data"] = base64_encode(encrypted_output_data); // needs to be a JSON string } return output; From 8c51719fe970e3ee6766a84985692c73bd7c6b86 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 12:36:05 +0300 Subject: [PATCH 14/20] gcm->siv tx output decryption using --- docs/encryption-specs.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 437fbabde..2d59955cc 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -277,7 +277,7 @@ TODO reasoning ## `contract_key` - `contract_key` is a concatenation of two values: `signer_id || authenticated_contract_key`. -- When a contract is deployed (i.e., on contract init), `contract_key` is generated inside of the enclave as follows: +- When a contract is deployed (i.e., on contract init), `contract_key` is generated inside of the Enclave as follows: ```js signer_id = sha256(concat(msg_sender, block_height)); @@ -296,8 +296,8 @@ authenticated_contract_key = hmac_sha256({ contract_key = concat(signer_id, authenticated_contract_key); ``` -- Every time a contract execution is called, `contract_key` should be sent to the enclave. -- In the enclave, the following verification needs to happen: +- Every time a contract execution is called, `contract_key` should be sent to the Enclave. +- In the Enclave, the following verification needs to happen: ```js signer_id = contract_key.slice(0, 32); @@ -397,7 +397,7 @@ TODO reasoning - `tx_encryption_key`: An AES-128-SIV encryption key. Will be used to encrypt tx inputs and decrypt tx outpus. - `tx_encryption_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_io_exchange_pubkey` and `tx_sender_wallet_privkey` (on the sender's side). - - `tx_encryption_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_io_exchange_privkey` and `tx_sender_wallet_pubkey` (inside the enclave of every full node). + - `tx_encryption_ikm` is derived using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) with `consensus_io_exchange_privkey` and `tx_sender_wallet_pubkey` (inside the Enclave of every full node). - `tx_encryption_key` is derived using HKDF-SHA256 with `tx_encryption_ikm` and a random number `nonce`. This is to prevent using the same key for the same tx sender multiple times. ## Input @@ -586,7 +586,7 @@ return output; ### Back on the transaction sender - The transaction output is written to the chain -- Only the wallet with the right `tx_sender_wallet_privkey` can derive `tx_encryption_key`, so for everyone else it'll just be encrypted. +- Only the wallet with the right `tx_sender_wallet_privkey` can derive `tx_encryption_key`, so for everyone else it will just be encrypted. - Every encrypted value can be decrypted the following way: ```js @@ -596,15 +596,11 @@ return output; // output["ok"]["log"][i]["value"] // output["ok"] if input is a query -bytes = base64_encode(encrypted_output); -iv = bytes.slice(0, 12); -encrypted_bytes = bytes.slice(12); +encrypted_bytes = base64_encode(encrypted_output); -aes_256_gcm_decrypt({ - iv: iv, +aes_128_siv_decrypt({ key: tx_encryption_key, data: encrypted_bytes, - aad: iv, }); ``` @@ -612,6 +608,8 @@ aes_256_gcm_decrypt({ # Blockchain Upgrades -TODO reasoning +TODO # Theoretical Attacks + +TODO From 0d612b67bdeddf67ec47cf8e068aef5fee012255 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 13:36:26 +0300 Subject: [PATCH 15/20] simplify write_db ad --- docs/encryption-specs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 2d59955cc..3a7402172 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -334,7 +334,7 @@ current_state_ciphertext = internal_read_db(encrypted_field_name); if (current_state_ciphertext == null) { // field_name doesn't yet initialized in state - ad = sha256(concat(encryption_key, value)); + ad = sha256(encrypted_field_name); } else { // read previous_ad, verify it, calculate new iv previous_ad = current_state_ciphertext.slice(0, 32); // first 32 bytes/256 bits @@ -345,7 +345,7 @@ if (current_state_ciphertext == null) { data: current_state_ciphertext, ad: previous_ad, }); // just to authenticate previous_iv - ad = sha256(concat(encryption_key, value, previous_ad)); + ad = sha256(previous_ad); } new_state_ciphertext = aes_128_siv_encrypt({ From e14591f652ad0214ebdefa7f0230006491aad770 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 13:50:51 +0300 Subject: [PATCH 16/20] some Theoretical Attacks --- docs/encryption-specs.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 3a7402172..5d84ea861 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -613,3 +613,6 @@ TODO # Theoretical Attacks TODO + +- No encryption padding, so a value of e.g. "yes" or "no" can be deanonymized by its byte count. +- If an attacker can create a contract with the same `contract_key` as another contract, they can potentially deanonymize the state of the second contract. E.g. if there's a permissioned getter in the original contract, in the new contract the attacker can set themselves as the owner in the new contract and ask the new contract to decrypt the state of the original contract via that permissioned getter. From 10943d19eb1ccd4a1bf89c4736d09a6d9d63e2a1 Mon Sep 17 00:00:00 2001 From: assafmo Date: Wed, 3 Jun 2020 15:31:56 +0300 Subject: [PATCH 17/20] readability for Theoretical Attacks --- docs/encryption-specs.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 5d84ea861..cf98fc0b1 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -34,6 +34,9 @@ - [Back on the transaction sender](#back-on-the-transaction-sender) - [Blockchain Upgrades](#blockchain-upgrades) - [Theoretical Attacks](#theoretical-attacks) + - [Deanonymizing with ciphertext byte count](#deanonymizing-with-ciphertext-byte-count) + - [Two contracts with the same `contract_key` could deanonymize each other's states](#two-contracts-with-the-same-contract_key-could-deanonymize-each-others-states) + - [Tx Replay attacks?](#tx-replay-attacks) # Bootstrap Process @@ -612,7 +615,16 @@ TODO # Theoretical Attacks -TODO +TODO add more + +## Deanonymizing with ciphertext byte count + +No encryption padding, so a value of e.g. "yes" or "no" can be deanonymized by its byte count. + +## Two contracts with the same `contract_key` could deanonymize each other's states + +If an attacker can create a contract with the same `contract_key` as another contract, the state of the original contract can potentially be deanonymized. + +For example, An original contract with a permissioned getter, such that only whitelisted addresses can query the getter. In the malicious contract the attacker can set themselves as the owner and ask the malicious contract to decrypt the state of the original contract via that permissioned getter. -- No encryption padding, so a value of e.g. "yes" or "no" can be deanonymized by its byte count. -- If an attacker can create a contract with the same `contract_key` as another contract, they can potentially deanonymize the state of the second contract. E.g. if there's a permissioned getter in the original contract, in the new contract the attacker can set themselves as the owner in the new contract and ask the new contract to decrypt the state of the original contract via that permissioned getter. +## Tx Replay attacks? From 7213530f4e3205ace1f456aec95ae6fd5859acd7 Mon Sep 17 00:00:00 2001 From: Assaf Morami Date: Wed, 3 Jun 2020 18:09:32 +0300 Subject: [PATCH 18/20] No need for ad in tx i/o --- docs/encryption-specs.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index cf98fc0b1..427fdd84b 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -451,7 +451,6 @@ tx_encryption_key = hkdf({ msg = aes_128_siv_decrypt({ key: tx_encryption_key, data: encrypted_msg, - ad: concat(nonce, tx_sender_wallet_pubkey), // or: tx_input.slice(0, 65) }); ``` @@ -548,7 +547,6 @@ if (typeof output["err"] == "string") { encrypted_msg = aes_128_siv_encrypt({ key: tx_encryption_key, data: m["msg"], - ad: concat(nonce, tx_sender_wallet_pubkey), }); // base64_encode because needs to be a string From 4ef1212f370da709af8ed0fdb1f82c57535826bd Mon Sep 17 00:00:00 2001 From: Assaf Morami Date: Wed, 3 Jun 2020 18:59:30 +0300 Subject: [PATCH 19/20] hkfd_salt no need to sha256 it --- docs/encryption-specs.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index 427fdd84b..b9f63f0f5 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -67,9 +67,7 @@ TODO reasoning - The HKDF-SHA256 [salt](https://tools.ietf.org/html/rfc5869#section-3.1) is chosen to be Bitcoin's halving block hash. ```js -hkfd_salt = sha256( - 0x000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d -); +hkfd_salt = 0x000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d; ``` - Using HKDF-SHA256, `hkfd_salt` and `consensus_seed`, derive the following keys: From 118ca0125c5f532f9e04be7ba5becd277cf41c76 Mon Sep 17 00:00:00 2001 From: Cashmaney Date: Thu, 4 Jun 2020 16:09:08 +0300 Subject: [PATCH 20/20] Update encryption-specs.md --- docs/encryption-specs.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/encryption-specs.md b/docs/encryption-specs.md index b9f63f0f5..33efa9411 100644 --- a/docs/encryption-specs.md +++ b/docs/encryption-specs.md @@ -74,7 +74,7 @@ hkfd_salt = 0x000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d; ### `consensus_seed_exchange_privkey` -- `consensus_seed_exchange_privkey`: A secp256k1 curve private key. Will be used to derive encryption keys in order to securely share `consensus_seed` with new nodes in the network. +- `consensus_seed_exchange_privkey`: A curve25519 private key. Will be used to derive encryption keys in order to securely share `consensus_seed` with new nodes in the network. - From `consensus_seed_exchange_privkey` calculate `consensus_seed_exchange_pubkey`. ```js @@ -83,14 +83,14 @@ consensus_seed_exchange_privkey = hkdf({ ikm: consensus_seed.append(uint8(1)), }); // 256 bits -consensus_seed_exchange_pubkey = calculate_secp256k1_pubkey( +consensus_seed_exchange_pubkey = calculate_curve25519_pubkey( consensus_seed_exchange_privkey ); ``` ### `consensus_io_exchange_privkey` -- `consensus_io_exchange_privkey`: A secp256k1 curve private key. Will be used to derive encryption keys in order to decrypt transaction inputs and encrypt transaction outputs. +- `consensus_io_exchange_privkey`: A curve25519 curve private key. Will be used to derive encryption keys in order to decrypt transaction inputs and encrypt transaction outputs. - From `consensus_io_exchange_privkey` calculate `consensus_io_exchange_pubkey`. ```js @@ -99,7 +99,7 @@ consensus_io_exchange_privkey = hkdf({ ikm: consensus_seed.append(uint8(2)), }); // 256 bits -consensus_io_exchange_pubkey = calculate_secp256k1_pubkey( +consensus_io_exchange_pubkey = calculate_curve25519_pubkey( consensus_io_exchange_privkey ); ``` @@ -152,7 +152,7 @@ TODO reasoning - Verify the remote attestation proof of the bootstrap node from `genesis.json`. - Create a remote attestation proof that the node's Enclave is genuine. -- Generate inside the node's Enclave a true random secp256k1 curve private key: `registration_privkey`. +- Generate inside the node's Enclave a true random curve25519 curve private key: `registration_privkey`. - From `registration_privkey` calculate `registration_pubkey`. - Send an `enigmacli tx register auth` transaction with the following inputs: - The remote attestation proof that the node's Enclave is genuine. @@ -433,8 +433,8 @@ tx_input = concat(ad, encrypted_msg); ```js nonce = tx_input.slice(0, 32); // 32 bytes -tx_sender_wallet_pubkey = tx_input.slice(32, 65); // 33 bytes, compressed secp256k1 public key -encrypted_msg = tx_input.slice(65); +tx_sender_wallet_pubkey = tx_input.slice(32, 32); // 32 bytes, compressed curve25519 public key +encrypted_msg = tx_input.slice(64); tx_encryption_ikm = ecdh({ privkey: consensus_io_exchange_privkey,