How to encrypt a memo when transferring Steem

in #steem8 years ago

Normally steem memos are sent as plain text, this article describes how to format your memos for encryption.

CLI Wallet

When using the cli wallet, if you include '#' as the first character of your memo will be encrypted.

unlocked >>> transfer from to "1.000 STEEM" "#encrypted memo" false
{
    "operations": [[ "transfer",{
         "from": "steemit",
         "to": "steem",
         "amount": "1.000 STEEM",
         "memo": "#CqojqJfFwawzgnYSFUWokj...xAfynekJtySMsqK8A9doD4gHA5fDKgQTEfUKZgAm8Dx"
    } ]  ]
}

When the wallet is unlocked and get_account_history is called, the "memo" field will be replaced with "encrypted memo" without the initial '#'. This means that exchanges that upgrade to the latest CLI wallet can transparently support encrypted memos.

Algorithm

The algorithm for encrypting the memo involves the following steps:
1. Generate a memo_data struct containing:

      public_key   from
      public_key   to
      uint64_t     nonce
      uint32_t     check
      vector<char> encrypted
  1. Calculate the AES encryption key as:
  shared_secret = from_private_key.get_shared_secret( to );
  /// concatenate nonce and shared secret (binary)
  encryption_key = sha512( nonce + shared_secret )
  ///< check is first 64 bit of sha256 hash treated as uint64_t truncated to 32 bits.
  check = sha256( encryption_key )._hash[0]
  /// pack the memo as a length-prefixed string, length is serialized as varint   
  plain_text = pack( memo_text) 
  encrypted = aes_encrypt( encryption_key, plain_text )
  1. Serialize memo_data
    Now convert the memo data into a vector and then convert it to base58
 string result = '#' + to_base58( pack( memodata ) );

The to and from keys should be fetched as the memo_key property on the respective accounts.

Sort:  

This is a random nonce with a time-based component. It returns a unique 64 bit unsigned number string.

    uniqueNonce() {
        if(unique_nonce_entropy === null) {
            const b = secureRandom.randomUint8Array(2)
            unique_nonce_entropy = parseInt(b[0] << 8 | b[1], 10)
        }
        let long = Long.fromNumber(Date.now())
        const entropy = ++unique_nonce_entropy % 0xFFFF
        long = long.shiftLeft(16).or(Long.fromNumber(entropy));
        return long.toString()
    }
    let unique_nonce_entropy = null

npm libraries used: long and secure-random

The purpose of the 'nonce' field is to generate a unique encryption key for every transfer between two accounts. I it should be a random number.

Is this working properly? As far as I can tell the same key is used for every transfer. See test (both used plaintext of "abc"): https://steemd.com/@aaa