# How to decrypt an encrypted transaction message created by the Radix Wallet

**URL:** <https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189>\
**Category:** Developers\
**Tags:** howto, python, api, replit\
**Created:** [31 December 2021 20:45 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189 "2021-12-31T20:45:24Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Stuart](https://radixtalk.com/letter_avatar_proxy/v4/letter/s/cab0a1/32.png) [@Stuart](https://radixtalk.com/u/Stuart)\
**Post date:** [31 December 2021 20:45 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/1 "2021-12-31T20:45:24Z")

</div>

# How to decrypt an encrypted transaction message created by the Radix Wallet

## 0. Introduction

The encryption scheme used by the Radix Wallet is called `DH_ADD_EPH_AESGCM256_SCRYPT_000`

Where:

- DH → [(Elliptic Curve) Diffie-Hellman](https://cryptobook.nakov.com/asymmetric-key-ciphers/ecdh-key-exchange) (A key exchange protocol that allows untrusted parties to construct a shared secret)
- ADD → Elliptic Curve Point Addition (The operation we use to create the shared secret)
- EPH → Ephemeral Public Key (A temporary public key that is only used for the purpose of encrypting/decrypting the message)
- AESGCM256 → The symmetrical encryption algorithm used to encrypt/decrypt the message (Advanced Encryption Standard, Galios Counter Mode, 256 bits)
- SCRYPT → The Key Derivation Function (KDF) used (Scrypt)
- 000 → The version of the scheme

The encryption scheme allows both the sender and receiver to decrypt the message - but no one else.

Alice sends the following encrypted message to Bob:

`01ff02663a6aaf4d5ec607330b9b74a840bf5c13b0a7357202fa85be56b1326065561657d6ee46d4d84e94ec615b425a472dd8c813bad125335a097d29b64b72319357406b2b04491b4ca1a5a05fe8772b0c05f4633b399914348c5b03af58445d42c2f740f8407e572775a571805e582c6b96ffd4ccca764f2002510abddaab735ee4fb0b18c26d`

Alice’s Wallet Address: `rdx1qsp8n0nx0muaewav2ksx99wwsu9swq5mlndjmn3gm9vl9q2mzmup0xqm2ylge`

Bob’s Wallet Address: `rdx1qspvvprlj3q76ltdxpz5qm54cp7dshrh3e9cemeu5746czdet3cfaegp8alwf`

For Bob to decrypt the message, he needs:

- Alice’s Public Key
- Bob’s Private Key

## 1. Convert Alice’s Wallet address into a her Public Key:

```python
import bech32

alice_wallet_address = "rdx1qsp8n0nx0muaewav2ksx99wwsu9swq5mlndjmn3gm9vl9q2mzmup0xqm2ylge"

_hrp, alice_readdr_5bit = bech32.bech32_decode(alice_wallet_address)
alice_readdr_bytes = bech32.convertbits(alice_readdr_5bit, 5, 8, pad=False)

## Remove the REAddr 04 prefix byte
alice_public_key_bytes = bytes(alice_readdr_bytes)[1:34]

alice_public_key_hex = bytes(alice_public_key_bytes).hex()
print("Alice Public Key:", alice_public_key_hex)

# Alice Public Key: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

```

## 2. Use Elliptic Curve Diffie-Helmann to combine Alice’s Public Key with Bob’s Private Key:

```python
import hashlib
from ecdsa.curves import SECP256k1
from ecdsa.keys import SigningKey, VerifyingKey

alice_public_key_hex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
alice_public_key = VerifyingKey.from_string(bytearray.fromhex(alice_public_key_hex), curve=SECP256k1, hashfunc=hashlib.sha256 )

bob_private_key_hex = "0000000000000000000000000000000000000000000000000000000000000002"
bob_private_key = SigningKey.from_string(bytearray.fromhex(bob_private_key_hex), curve=SECP256k1, hashfunc=hashlib.sha256)

# Diffie-Hellman - we need to use lower level functions here so that we get back dh as a curve point
dh = alice_public_key.pubkey.point * bob_private_key.privkey.secret_multiplier

```

_Note: Although it looks like a simple multiplication operation, the \* operator has been overridden in the ECDSA library to perform Elliptic Curve Diffie-Hellman calculation_

## 3. Slice the encrypted message into its component parts:

```python
encrypted_message = "01ff02663a6aaf4d5ec607330b9b74a840bf5c13b0a7357202fa85be56b1326065561657d6ee46d4d84e94ec615b425a472dd8c813bad125335a097d29b64b72319357406b2b04491b4ca1a5a05fe8772b0c05f4633b399914348c5b03af58445d42c2f740f8407e572775a571805e582c6b96ffd4ccca764f2002510abddaab735ee4fb0b18c26d"

# Message Type: 01 (1 byte)
message_type = encrypted_message[0:2]

# Encryption Type: ff (1 byte)
encryption_type = encrypted_message[2:4]

# Ephemeral Public Key: 02663a6aaf4d5ec607330b9b74a840bf5c13b0a7357202fa85be56b13260655616 (33 bytes)
ephemeral_public_key = encrypted_message[4:70]

# Nonce: 57d6ee46d4d84e94ec615b42 (12 bytes)
nonce = encrypted_message[70:94]

# Auth Tag: 5a472dd8c813bad125335a097d29b64b (16 bytes)
auth_tag = encrypted_message[94:126]

# Ciphertext: 72319357406b2b04491b4ca1a5a05fe8772b0c05f4633b399914348c5b03af58445d42c2f740f8407e572775a571805e582c6b96ffd4ccca764f2002510abddaab735ee4fb0b18c26d
ciphertext = encrypted_message[126:]

```

## 4. Convert the Ephemeral Public Key into a Curve Point

```python
# Convert Ephemeral Public key into a Curve Point
ephemeral_curve = VerifyingKey.from_string(bytearray.fromhex(ephemeral_public_key), curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256)
ephemeral_point = ephemeral_curve.pubkey.point

```

## 5. Use Elliptic Curve Point Addition to combine the DH Point with the Ephemeral Point

```python
# Elliptic Curve Point Addition
shared_secret_point = dh + ephemeral_point

```

_Note: Although it looks like a simple addition operation, the + operator has been overridden in the ECDSA library to perform Elliptic Curve Point Addition_

## 6. Use the x co-ordinate of the Shared Secret Point as the Shared Secret

```python
shared_secret_integer = shared_secret_point.x()
shared_secret_hex = hex(shared_secret_integer)[2:]

print("Shared Secret:", shared_secret_hex)

# Shared Secret: 64567aaa53dadd0d9dade89aca0beba4ea00ff987d986f5af421ff2bd636ca9d

```

## 7. Use the Scrypt Key Derivation Function (KDF) to convert the shared secret into the message decryption key:

```python
from Crypto.Protocol.KDF import scrypt

# Create the Scrypt salt from the SHA256 of the nonce
salt = hashlib.sha256(bytearray.fromhex(nonce)).digest()

# Get the Decryption Key
key = scrypt(bytearray.fromhex(shared_secret_hex), salt, 32, 8192, 8 ,1)

print("Decryption Key:", key.hex())

# Decryption Key: 7a294fd759ffdba936f46f5c773c7c8bd3e44064572b926e4e3e0b7291897a30

```

## 8. Decrypt the cipher text with the decryption key using AES-GCM:

```python
from Crypto.Cipher import AES

decrypt = AES.new(key, AES.MODE_GCM, nonce=bytearray.fromhex(nonce))

# Add the Ephemeral Public Key as Associated Data
decrypt.update(bytearray.fromhex(ephemeral_public_key))

msg = decrypt.decrypt_and_verify(bytearray.fromhex(ciphertext), bytearray.fromhex(auth_tag))

print("Plaintext Message:", msg)

# Plaintext Message: b'Hey Bob, this is Alice, you and I can read this message, but no one else.'

```

## 8. The Entire Script:

```python
import bech32
import ecdsa
import hashlib
from ecdsa.curves import SECP256k1
from ecdsa.keys import SigningKey, VerifyingKey
from Crypto.Protocol.KDF import scrypt
from Crypto.Cipher import AES

encrypted_message = "01ff02663a6aaf4d5ec607330b9b74a840bf5c13b0a7357202fa85be56b1326065561657d6ee46d4d84e94ec615b425a472dd8c813bad125335a097d29b64b72319357406b2b04491b4ca1a5a05fe8772b0c05f4633b399914348c5b03af58445d42c2f740f8407e572775a571805e582c6b96ffd4ccca764f2002510abddaab735ee4fb0b18c26d"
alice_wallet_address = "rdx1qsp8n0nx0muaewav2ksx99wwsu9swq5mlndjmn3gm9vl9q2mzmup0xqm2ylge"
bob_private_key_hex = "0000000000000000000000000000000000000000000000000000000000000002"

_hrp, alice_readdr_5bit = bech32.bech32_decode(alice_wallet_address)
alice_readdr_bytes = bech32.convertbits(alice_readdr_5bit, 5, 8, pad=False)

## Remove the REAddr 04 prefix byte
alice_public_key_bytes = bytes(alice_readdr_bytes)[1:34]

alice_public_key_hex = bytes(alice_public_key_bytes).hex()
print("Alice Public Key:", alice_public_key_hex)

alice_public_key = VerifyingKey.from_string(bytearray.fromhex(alice_public_key_hex), curve=SECP256k1, hashfunc=hashlib.sha256 )

bob_private_key_hex = "0000000000000000000000000000000000000000000000000000000000000002"
bob_private_key = SigningKey.from_string(bytearray.fromhex(bob_private_key_hex), curve=SECP256k1, hashfunc=hashlib.sha256)

# Diffie-Hellman - we need to use lower level functions here so that we get back dh as a curve point
dh = alice_public_key.pubkey.point * bob_private_key.privkey.secret_multiplier

# Message Type: 01 (1 byte)
message_type = encrypted_message[1:2]

# Encryption Type: ff (1 byte)
encryption_type = encrypted_message[2:4]

# Ephemeral Public Key: 02663a6aaf4d5ec607330b9b74a840bf5c13b0a7357202fa85be56b13260655616 (33 bytes)
ephemeral_public_key = encrypted_message[4:70]

# Nonce: 57d6ee46d4d84e94ec615b42 (12 bytes)
nonce = encrypted_message[70:94]

# Auth Tag: 5a472dd8c813bad125335a097d29b64b (16 bytes)
auth_tag = encrypted_message[94:126]

# Ciphertext: 72319357406b2b04491b4ca1a5a05fe8772b0c05f4633b399914348c5b03af58445d42c2f740f8407e572775a571805e582c6b96ffd4ccca764f2002510abddaab735ee4fb0b18c26d
ciphertext = encrypted_message[126:]

# Convert Ephemeral Public key into a Curve Point
ephemeral_curve = VerifyingKey.from_string(bytearray.fromhex(ephemeral_public_key), curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256)
ephemeral_point = ephemeral_curve.pubkey.point

# Elliptic Curve Point Addition
shared_secret_point = dh + ephemeral_point

shared_secret_integer = shared_secret_point.x()
shared_secret_hex = hex(shared_secret_integer)[2:]

print("Shared Secret:", shared_secret_hex)

# Create the Scrypt salt from the SHA256 of the nonce
salt = hashlib.sha256(bytearray.fromhex(nonce)).digest()

# Get the Decryption Key
key = scrypt(bytearray.fromhex(shared_secret_hex), salt, 32, 8192, 8 ,1)

print("Decryption Key:", key.hex())

decrypt = AES.new(key, AES.MODE_GCM, nonce=bytearray.fromhex(nonce))

# Add the Ephemeral Public Key as Associated Data
decrypt.update(bytearray.fromhex(ephemeral_public_key))

msg = decrypt.decrypt_and_verify(bytearray.fromhex(ciphertext), bytearray.fromhex(auth_tag))

print("Plaintext Message:", msg)

```

## Run this code on Replit

https://replit.com/@radixtalk/Decrypt-Transaction-Message?embed=true

Also see: [How to encrypt a transaction message that can be read by the Radix Wallet](https://radixtalk.com/t/how-to-encrypt-a-transaction-message-that-can-be-read-by-the-radix-wallet/202)

This work by [RadixPool.com](https://radixpool.com) is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).

---

<div class="post-metadata">

**Author:** ![Stuart](https://radixtalk.com/letter_avatar_proxy/v4/letter/s/cab0a1/32.png) [@Stuart](https://radixtalk.com/u/Stuart)\
**Post date:** [1 January 2022 19:37 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/2 "2022-01-01T19:37:50Z")

</div>

Similarly, Alice can decrypt her message that she sent to Bob with:

- Alice’s Private Key
- Bob’s Public Key

DH = Alice Private Key \* Bob Public Key = Alice Public Key \* Bob Private Key

---

<div class="post-metadata">

**Author:** ![minhnn](https://radixtalk.com/user_avatar/radixtalk.com/minhnn/32/857_2.png) [@minhnn](https://radixtalk.com/u/minhnn)\
**Post date:** [3 January 2022 05:29 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/3 "2022-01-03T05:29:54Z")

</div>

> [@Stuart](#):
>
> `message_type = encrypted_message[1:2]`

I have a small suggestion that this should be changed to message\_type = encrypted\_message[0:2] to get the string value of “01”

Also could you be able to verify the auth\_tag ? I test with your hard coded data, then if we do AES encrypt with empty associatedData, then the auth\_tag should be “101416c4f912797fe53f87c3f96f4374”

If the auth\_tag is “5a472dd8c813bad125335a097d29b64b” then it means Alice encrypted via AES with some value of associatedData (this is a parameter name of AEADParameters in the pointycastle library)

---

<div class="post-metadata">

**Author:** ![Stuart](https://radixtalk.com/letter_avatar_proxy/v4/letter/s/cab0a1/32.png) [@Stuart](https://radixtalk.com/u/Stuart)\
**Post date:** [3 January 2022 10:15 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/4 "2022-01-03T10:15:44Z")

</div>

> [@minhnn](#):
>
> I have a small suggestion that this should be changed to message\_type = encrypted\_message[0:2] to get the string value of “01”

Doh! Thanks, I’ve edited the code to fix that.

> [@minhnn](#):
>
> Also could you be able to verify the auth\_tag ?

I originally used `decrypt.decrypt_and_verify(bytearray.fromhex(ciphertext), bytearray.fromhex(auth_tag))` but the MAC (Message Authentication Code) check always failed. I didn’t notice the message was decrypting “correctly” until I split the operation into separate `decrypt` and then `verify` steps. Of course the `verify` step using the auth\_tag should be working to ensure that the decrypted message is verifiably correct.

> [@minhnn](#):
>
> If the auth\_tag is “5a472dd8c813bad125335a097d29b64b” then it means Alice encrypted via AES with some value of associatedData

Yes, I think you’re right. The problem is almost certainly related to associated data. Associated data is not encrypted as part of the ciphertext but is used when calculating the auth\_tag.

Once the auth\_tag problem is fixed then I’ll be able to write up a guide to encrypting messages. Hopefully that will be very soon™…

---

<div class="post-metadata">

**Author:** ![Stuart](https://radixtalk.com/letter_avatar_proxy/v4/letter/s/cab0a1/32.png) [@Stuart](https://radixtalk.com/u/Stuart)\
**Post date:** [3 January 2022 15:08 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/5 "2022-01-03T15:08:05Z")

</div>

The `decrypt_and_verify` function works correctly now. I had to add the following line to include the associated data before calling the `decrypt_and_verify` function:

`decrypt.update(bytearray.fromhex(ephemeral_public_key))`

---

<div class="post-metadata">

**Author:** ![minhnn](https://radixtalk.com/user_avatar/radixtalk.com/minhnn/32/857_2.png) [@minhnn](https://radixtalk.com/u/minhnn)\
**Post date:** [3 January 2022 17:35 UTC](https://radixtalk.com/t/how-to-decrypt-an-encrypted-transaction-message-created-by-the-radix-wallet/189/6 "2022-01-03T17:35:22Z")

</div>

Thanks, I tested and including the associated data will make the verify successfully, and port your code to pointycastle library also work correctly.
