经过了几个昼夜的学习和测试,终于搞定了memo的加密解密。原本以为这块不会有多复杂,但是实际沉浸下去才发现涉及的内容还很多。
(图源 :pixabay)
涉及内容
简单回顾一下涉及的主要内容:
- 理论基础:
Pub(Alice) * Priv(Bob) = Pub(Bob) * Priv(Alice)
- 内容加密:AES (Advanced Encryption Standard),CBC模式
- nonce、check:打包时字节序 / Byte Order的问题
- 打包长内容时长度编码的问题:Varint编码
回头再看,可能都不是有多难,可是为了把他们弄明白,我还是下了一番苦功夫的,一调试就调试到下半夜。
MEMO“协议”
把这些都弄懂之后,自己写了程序能加密/解密/打包/解包MEMO了,然而发送给自己测试账户的钱包里的memo要么显示Invalid memo
要么内容短缺一大块。
其实原因我是清楚的,涉及到通信,一个最重要的地方就是通信双方要依据相同的标准,这就是所谓的协议。我自己的打包解包都是自己的标准当然没问题,但是钱包中用的流程未必和我一样啊。
为了搞明白钱包中用的流程,我读了steem-python的代码/beem的代码/cli_wallet的代码,最终还是找到BM的一篇早期文章,才彻底搞懂。
BM文章中memo_data结构如下:
public_key from
public_key to
uint64_t nonce
uint32_t check
vector<char> encrypted
BM文章中核心代码如下:
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 varintplain_text = pack( memo_text)
encrypted = aes_encrypt( encryption_key, plain_text )
最后使用memo_data打包并转换成base58编码:
string result = '#' + to_base58( pack( memodata ) );
而我的代码之所以不被识别,是因为BM的代码中先对memo_text原文进行了一次处理,变成了加上varint前缀的二进制串;然后pack( memodata )
再对encrypted数据加上varint前缀,我缺少了其中第一个步骤,所以内容总是没法别识别。
测试代码
有了上述理论基础以及通信双方遵循的协议,再实现起来就好办多了,当然,说是好办也不容易,测试N多次经历N次失败后,总算完成了两个函数:
encode_memo(wif, pk, message)
decode_memo(wif, message)
其中wif
是账户的私钥,pk
是账户的公钥。加密memo时,使用的是发送方私钥和接收方公钥,解密memo时可以使用任何一方私钥。
用如下代码测试一下:
message = "12345678"
memo_encoded = encode_memo(wif_test, receiver_pk, message)
print(memo_encoded)
print("decode with sender's memo private key: ", decode_memo(wif_test, memo_encoded))
print("decode with receiver's memo private key: ", decode_memo(wif_abc, memo_encoded))
client.transfer("oflyhigh.test", "oflyhigh.abc", "0.001 HBD", memo_encoded, wif_test_active)
我们会得到如下编码后的memo:
#C3JiuC9zrPkJRTK3bfz1HHJHvUuLPw4yiWS3bGYMABMvV84UYvmF7jqR58Hg5nor2F1uoSJjWMDsbuAjZJBxLyafj6qLqQcJW8hReTuLt48dpR7iG7kMWQr6VaQg4d7C4
分别用发送方和接收方私钥解密:
广播出去的transaction:
可见,和普通的transaction无什么区别。在https://hiveblocks.com/ 查看一下:
登录钱包查看一下,发现解密是正常的:
长文本
另外,一个就是长文本的支持,既然可以用varint表示长度,理论上memo可以很长,然而我打算放一篇《出师表》进去,却被提示如下错误:
‘Assert Exception:memo.size() < STEEM_MAX_MEMO_SIZE: Memo is too large’
查了一下代码,有如下定义:
#define STEEM_MAX_MEMO_SIZE 2048
所以长度不能太长哦,好在我试着放点其它文章还是没问题的,比如如下两条:
其实长文本和memo加密这块关系不大,不过想到哪就写到哪吧,记下来以免以后忘记了。
其它
代码中使用了手工encode/decode,这需要进一步改进,比如说消息前边有#号的自动进行encode处理,没有#号的则忽略。把这个功能放到transfer函数中,那么我们就可以使用如下方法直接进行了。
client.transfer("oflyhigh.test", "oflyhigh.abc", "0.001 HBD", "#Hello world", wif_test_active)
不过这样一改,transfer函数就有些复杂,比如我想传递明文的#文本,transfer就无能无力了,而现在的transfer是支持的。
有意思的是,网页钱包反而把这个识别成为:Invalid memo。
哎,还是老老实实按着标准来吧,凡是#号开头,一律先加密:)
相关链接
This page is synchronized from the post: ‘每天进步一点点:终于搞定了memo加密/解密’