Bitshares的各种operation中有个memo数据,memo是加密的,传一下自己的私钥和对方的公钥就能解密,而对方也可以这样解密,很多人觉得很神奇,来看看是怎么实现的!
memo.cpp -> sign_memo
memo_data sign_memo(string from, string to, string memo)
{
FC_ASSERT( !self.is_locked() );
memo_data md = memo_data();
// get account memo key, if that fails, try a pubkey
try {
account_object from_account = get_account(from);
md.from = from_account.options.memo_key;
} catch (const fc::exception& e) {
md.from = self.get_public_key( from );
}
// same as above, for destination key
try {
account_object to_account = get_account(to);
md.to = to_account.options.memo_key;
} catch (const fc::exception& e) {
md.to = self.get_public_key( to );
}
md.set_message(get_private_key(md.from), md.to, memo);
return md;
}
从sign_memo函数发现,会使用from的私钥和to的公钥,调用set_message()函数,要看看这函数内部怎么处理的?
void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::public_key& pub,
const string& msg, uint64_t custom_nonce)
{
if( priv != fc::ecc::private_key() && public_key_type(pub) != public_key_type() )
{
from = priv.get_public_key();
to = pub;
if( custom_nonce == 0 )
{
uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0];
entropy <<= 32;
entropy &= 0xff00000000000000;
nonce = (fc::time_point::now().time_since_epoch().count() & 0x00ffffffffffffff) | entropy;
} else
nonce = custom_nonce;
auto secret = priv.get_shared_secret(pub);
auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
string text = memo_message(digest_type::hash(msg)._hash[0], msg).serialize();
message = fc::aes_encrypt( nonce_plus_secret, vector<char>(text.begin(), text.end()) );
}
else
{
auto text = memo_message(0, msg).serialize();
message = vector<char>(text.begin(), text.end());
}
}
如果传进来的私钥和公钥是缺省对象,则memo消息直接serialize。
如果传进来有私钥和公钥,则会生成一个共享密钥,然后用aes加密数据!注意是AES对称加密!
而AES加密密钥nonce_plus_secret是nonce和secret通过secp256k1算法得到的,如下:
static const private_key_secret empty_priv;
fc::sha512 private_key::get_shared_secret( const public_key& other )const
{
FC_ASSERT( my->_key != empty_priv );
FC_ASSERT( other.my->_key != nullptr );
public_key_data pub(other.serialize());
FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( detail::_get_context(), (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) );
return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 );
}
nonce代码中有注释说明:
/**
* 64 bit nonce format:
* [ 8 bits | 56 bits ]
* [ entropy | timestamp ]
* Timestamp is number of microseconds since the epoch
* Entropy is a byte taken from the hash of a new private key
*
* This format is not mandated or verified; it is chosen to ensure uniqueness of key-IV pairs only. This should
* be unique with high probability as long as the generating host has a high-resolution clock OR a strong source
* of entropy for generating private keys.
*/
生成的memo数据最前方是消息的hash值,后面跟消息数据,看看序列化函数:
string memo_message::serialize() const
{
auto serial_checksum = string(sizeof(checksum), ' ');
(uint32_t&)(*serial_checksum.data()) = checksum;
return serial_checksum + text;
}
string memo_data::get_message(const fc::ecc::private_key& priv,
const fc::ecc::public_key& pub)const
{
if( from != public_key_type() )
{
auto secret = priv.get_shared_secret(pub);
auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str());
auto plain_text = fc::aes_decrypt( nonce_plus_secret, message );
auto result = memo_message::deserialize(string(plain_text.begin(), plain_text.end()));
FC_ASSERT( result.checksum == uint32_t(digest_type::hash(result.text)._hash[0]) );
return result.text;
}
else
{
return memo_message::deserialize(string(message.begin(), message.end())).text;
}
}
这个取memo消息就是与刚才加密相反操作。
那转帐时的memo接收方怎么能解呢?看看transfer函数:
signed_transaction transfer(string from, string to, string amount,
string asset_symbol, string memo, bool broadcast = false)
{
...
if( memo.size() )
{
xfer_op.memo = memo_data();
xfer_op.memo->from = from_account.options.memo_key;
xfer_op.memo->to = to_account.options.memo_key;
xfer_op.memo->set_message(get_private_key(from_account.options.memo_key),
to_account.options.memo_key, memo);
}
...
}
实际发送交易时,memo_data对象也发送到节点了!节点只计算了memo的费用,没有做其它处理!
wallet.cpp中有一段解析operation的memo代码,如下:
string operation_printer::operator()(const transfer_operation& op) const
{
out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount)
<< " from " << wallet.get_account(op.from).name << " to " << wallet.get_account(op.to).name;
std::string memo;
if( op.memo )
{
if( wallet.is_locked() )
{
out << " -- Unlock wallet to see memo.";
} else {
try {
FC_ASSERT(wallet._keys.count(op.memo->to) || wallet._keys.count(op.memo->from), "Memo is encrypted to a key ${to} or ${from} not in this wallet.", ("to", op.memo->to)("from",op.memo->from));
if( wallet._keys.count(op.memo->to) ) {
auto my_key = wif_to_key(wallet._keys.at(op.memo->to));
FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted.");
memo = op.memo->get_message(*my_key, op.memo->from);
out << " -- Memo: " << memo;
} else {
auto my_key = wif_to_key(wallet._keys.at(op.memo->from));
FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted.");
memo = op.memo->get_message(*my_key, op.memo->to);
out << " -- Memo: " << memo;
}
} catch (const fc::exception& e) {
out << " -- could not decrypt memo";
}
}
}
fee(op.fee);
return memo;
}
要求钱包中有发送方或者接收方的memo key,如果是接收方,则调用get_message传入接收方私钥和发送方公钥,如果是发送方,则调用get_message传入发送方私钥和接收方公钥,都可以解析出memo数据。
memo的加密最重要的就是用双方密钥得到共同加密密钥,也即ECDH算法。
摘抄一段ECDH说明:
ECDH
ECDH是EC是“ elliptic curves”的意思,DH是“ Diffie-Hellman”的意思。它实际上是密钥协商算法,而不是加解密算法。
该算法可以用来解决如下问题:两端(Alice 和 Bob)想要安全的交换信息并且第三方不能获取到该信息。当然这是TLS协议中的目的之一,我们给出一个例子。
(其实下面的描述其实是ECDHE,而不是ECDH)
(1)Alice 和 Bob 生成他们自己的私钥和公钥,即Alice 有 da、Ha = da*G;Bob有db、Hb = db *G
(2)Alice把Ha发给Bob,Bob把Hb发给Alice。这样Alice 有da,Ha,Hb,Bob有db,Ha,Hb。
(3)Alice计算S = da*Hb(即自己的私钥乘上Bob的公钥),同样的,Bob计算S = db*Ga(自己的私钥乘上Alice的公钥)。两边计算的S是相同的。
S = da*Hb = da (db G) = db *(da *G) = db*Hb 等式1
中间人支持到Ha和Hb,无法计算出共享密钥S。即离散对数问题为:
中间人 要计算 S,必须通过上述 等式1 中的一个等式来计算。显然必须知道da或者db,而中间人至知道Ha和Hb,即中间人为了获得da或者db需要从H或Hb中分离出da或db,显然这就是之前所说的离散对数问题。
现在Alice和Bob得到了共享密钥,后续可以使用共享密钥进行对称加密进行数据传输。通常情况下,点S中x向量被作为共享密钥。
更多参考:https://blog.csdn.net/mrpre/article/details/72850644
感谢您阅读 @chaimyu 的帖子,期待您能留言交流!