How To Secure Messages On EOS

Blockgenic
Coinmonks
5 min readAug 31, 2018

--

A blockchain is a distributed ledger and every block of data is publicly available for everyone to see. You may be wondering what if I want to encrypt messages on blockchain, specifically, on EOS?

How securing messages on a blockchain works

We can incorporate NuCypher. NuCypher helps dApp developers store, share, and manage private data on public blockchains with decentralized proxy re-encryption as a service.

Here is how it works.

  1. Alice has sensitive data that she wants to be able to delegate access to.
  2. Alice encrypts her data using her own public key and stores it in the cloud, or decentralized storage.
  3. Alice delegates access to Bob. The data is rekeyed to Bob’s key in storage.
  4. Bob downloads the data and decrypts it with his private key.

Securing a message on EOS

We’ll start with the scenario where Alice and Bob both have access to the private key and Alice wants to send sensitive data to Bob, and leverage pyUmbral which is NuCypher’s reference implementation of Umbral (split-key threshold proxy re-encryption).

Let’s first build a message queue smart contract on EOS.

It construct one table called messages, which contains uint64_t msg_id, account_name from, account_name to, string ciphertext, and string capsule. msg_id is the primary key.

It provides two actions: sendmsg and deletemsg. sendmsg requires account_name from and to, msg_id, ciphertext and capsule. Ciphertext is the encrypted message and capsule is the concept in Umbral, which is generated using pyUmbral. deletemsgbasically takes a msg_id, verifies the given to account_name, then deletes the record.

class queue: public eosio::contract {    public:
using contract::contract;
//@abi action
void sendmsg( account_name from, account_name to, uint64_t msg_id, const std::string & ciphertext, const std::string & capsule) {
require_auth( from );
messages_index messages( _self, _self );
auto itr = messages.find(msg_id);
if (itr != messages.end()) {
std::string error = "msg_id already exists: " + std::to_string(msg_id);
eosio_assert(false, error.c_str());
}
messages.emplace( from, [&](auto& msg){
msg.msg_id = msg_id;
msg.from = from;
msg.to = to;
msg.ciphertext = ciphertext;
msg.capsule = capsule;
});
} // @abi action
void deletemsg( account_name to, uint64_t msg_id) {
require_auth( to );
messages_index messages(_self, _self);
auto itr = messages.find(msg_id);
if ( itr == messages.end() ) {
std::string error = "msg_id does not exist: " + std::to_string(msg_id);
eosio_assert(false, error.c_str());
}

if ( itr->to != to ) {
std::string error = "Receipient not correct: " + eosio::name{itr->to}.to_string();
eosio_assert( false, error.c_str());
}

messages.erase(itr);
}
private:
//@abi table messages i64
struct messages {
uint64_t msg_id;
account_name from;
account_name to;
std::string ciphertext;
std::string capsule;
uint64_t primary_key() const { return msg_id;}
};

typedef eosio::multi_index<N(messages), messages> messages_index;
};EOSIO_ABI( queue, (sendmsg)(deletemsg) )

Compile it:

eosiocpp -o queue.wast queue.cpp
eosiocpp -g queue.abi queue.cpp

Create an account to upload the smart contract:

$ cleos create account eosio queue EOS5aEqZf22dfThTR8GGMnD8oFvsyzssQnhawNwPzfPv4fwDWhf7H
executed transaction: a767af2c66857... 200 bytes 3309 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"queue","owner":{"threshold":1,"keys":[{"key":"EOS5aEqZf22dfThTR8GGMnD8oFv...
$ cleos set contract queue ../queue
Reading WAST/WASM from ../queue/queue.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 38e94741c... 13824 bytes 9561 us
# eosio <= eosio::setcode {"account":"queue","vmtype":0,"vmversion":0,"code":"00617ee7e...
# eosio <= eosio::setabi {"account":"queue","abi":"0e656f73696f3a3a6162692f9640675...

Create test accounts alice and bob:

$ cleos create account eosio alice EOS6NU3XEvosgRVEbhrBHrkbYVteW7DDVewhjo9jgiYoSqUZamnZe
executed transaction: f0c42065f6d9fc... 200 bytes 243 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"alice","owner":{"threshold":1,"keys":[{"key":"EOS6NU3XEvosgRVEbhrBHrkbYVt...
$ cleos create account eosio bob EOS7cX17CZ8V7yFobaVejAN7sMG39iiC5BmFk7b1NB1NNYcrEu1Go
executed transaction: 51d45916fa252e... 200 bytes 194 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"bob","owner":{"threshold":1,"keys":[{"key":"EOS7cX17CZ8V7yFobaVejAN7sMG39...

Let’s move on to build a client to encrypt/decrypt a message and interact with the smart contract.

  1. Generate a private key and write into a file.
from umbral import config, keysconfig.set_default_curve(SECP256K1)
private_key = keys.UmbralPrivateKey.gen_key()
f = open('priv.key', 'wb')
f.write(private_key.to_bytes())
f.close()

2. Construct a parser

def create_parser():
parser = argparse.ArgumentParser(description = 'messenger')
parser.add_argument('--private-key-file', type = str, dest = 'private_key_file', required = False, help = 'Path to the private key file.')
parser.add_argument('--send-msg-id', type = str, dest = 'send_msg_id', required = False, help = 'Send a message, msg_id')
parser.add_argument('--send-msg-from', type = str, dest = 'send_msg_from', required = False, help = 'Send a message, from which EOS account')
parser.add_argument('--send-msg-to', type = str, dest = 'send_msg_to', required = False, help = 'Send a message, to which EOS account')
parser.add_argument('--send-msg', type = str, dest = 'send_msg', required = False, help = 'Message to be sent')
parser.add_argument('--read-msg-to', type = str, dest = 'read_msg_to', required = False, help = 'Read a message, to which EOS account')
return parser
parser = create_parser()
args = parser.parse_args(argv)

3. Read from the privkey file.

def read_privkey_file(privkey_file):
if os.path.exists(privkey_file):
with open(privkey_file, 'rb') as f:
try:
privkey = f.read()
except Exception as e:
print("Cannot read: {}".format(e))
sys.exit(1)
f.close()
return privkey
else:
print('Cannot find file: {}'.format(privkey_file))
sys.exit(1)

3. Encrypt a message and send it.

def send_msg(private_key_file, msg, send_msg_from, send_msg_to, send_msg_id):
privkey_bytes = read_privkey_file(private_key_file)
privkey = keys.UmbralPrivateKey.from_bytes(privkey_bytes)
pubkey = privkey.get_pubkey()
plaintext = msg.encode()
ciphertext, capsule = pre.encrypt(pubkey, plaintext)
data = '{"from":"' + send_msg_from + '", "to":"' + send_msg_to + '", "msg_id":"' + send_msg_id + '", "ciphertext": "' + ciphertext.hex() + '", "capsule": "' + capsule.to_bytes().hex() + '"}'
subprocess.call(['cleos', 'push', 'action', DEFAULT_ACCOUNT, 'sendmsg', data , '-p', send_msg_from])

3. Read from messages table, if to matches read_msg_to, decrypt a message and then delete it.

def read_and_delete_msg(private_key_file, read_msg_to):
privkey_bytes = read_privkey_file(private_key_file)
privkey = keys.UmbralPrivateKey.from_bytes(privkey_bytes)

payload = '{"scope":"' + DEFAULT_ACCOUNT + '","code":"' + DEFAULT_ACCOUNT + '","table": "' + DEFAULT_TABLE+ '", "json":"true"}'
response = requests.request("POST", DEFAULT_URL, data=payload)

found = False
for msg in response.json()['rows']:
if msg['to'] == read_msg_to:
ciphertext = msg['ciphertext']
capsule = msg['capsule']
msg_id = msg['msg_id']
found = True
break
if found:
capule = pre.Capsule.from_bytes(bytes.fromhex(capsule), privkey.params)
cleartext = pre.decrypt(
ciphertext = bytes.fromhex(ciphertext),
capsule = capule,
decrypting_key = privkey)
print('Cleartext: {}'.format(cleartext))
print('Deleting msg_id: {}'.format(msg_id))
data = '{"to":"' + read_msg_to + '", "msg_id":"' + str(msg_id) + '"}'
subprocess.call(['cleos', 'push', 'action', DEFAULT_ACCOUNT, 'deletemsg', data , '-p', read_msg_to])

4. Default values.

DEFAULT_ACCOUNT = 'queue'DEFAULT_TABLE = 'messages'DEFAULT_URL= "http://127.0.0.1:8888/v1/chain/get_table_rows"

Let’s wrap them up and test!

$ python3 messenger.py --private-key-file priv.key --send-msg-id 1 --send-msg-from alice --send-msg-to bob --send-msg hello,bobexecuted transaction: dfe17144e105c54d192...  392 bytes  648 us
# queue <= queue::sendmsg {"from":"alice","to":"bob","msg_id":1,"ciphertext":"8c53a656c9...

Check the messages table.

$ cleos get table queue queue messages   
{
"rows": [{
"msg_id": 1,
"from": "alice",
"to": "bob",
"ciphertext": "8c8aa24196152b53da35d9fbf9e3c4d8e10f6b7153a656c9921cb440cc69a782c2ba0c2cf2",
"capsule": "028c4e8279c0919bdec4ea98b4251f15a74868d2ea7554ab796230af8f88e62cd9031c07e90c6183152e140c43a370f2c12526b6d62269f8673a35e6a19fc6ff78ac2f3a28dbed868858ec71228644f277c8ecfb691aa41dab863072e6f39c55d294"
}
],
"more": false
}

The encrypted message is there. Then let’s read it.

$ python3 messenger.py --private-key-file priv.key --read-msg-to bobCleartext: b'hello,bob'
Deleting msg_id: 1
executed transaction: 6cdad7694f3fe6c8...112 bytes 566 us
# queue <= queue::deletemsg {"to":"bob","msg_id":1}

Check the messages table, the message is gone.

$ cleos get table queue queue messages   
{
"rows": [],
"more": false
}

Thus we explored how to secure messages on EOS. Moving forward, we can work on scenario where Alice and Bob have their own keys, or send to multiple users.

Our socials

Website
Twitter
Youtube
Meetup
Steem

Written by Yannick Slenter for Blockgenic

--

--

Blockgenic
Coinmonks

We are a firm focused on Enterprise adoption of blockchain technology. EOS Block Producer name: ‘blockgenicbp’. www.blockgenic.io