由于EOS防护机制,相同的data使用相同的私钥签出的签名是一样的,避免重复交易攻击,
但由于某些需求场景,即使data一致,也想相同签名每次签的不一致,那我们跟一下代码把
根据关键词Duplicate transaction error
https://github.com/EOSIO/eos/blob/eb88d033c0abbc481b8a481485ef4218cdaa033a/libraries/chain/controller.cpp#L2990:18
bool controller::is_known_unexpired_transaction( const transaction_id_type& id) const {
return db().find<transaction_object, by_trx_id>(id);
}
查看计算trx_id的方法
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L1306
get_transaction_id->set_callback([&] {
try {
fc::variant trx_var = json_from_file_or_string(trx_to_check);
if( trx_var.is_object() ) {
fc::variant_object& vo = trx_var.get_object();
// if actions.data & actions.hex_data provided, use the hex_data since only currently support unexploded data
if( vo.contains("actions") ) {
if( vo["actions"].is_array() ) {
fc::mutable_variant_object mvo = vo;
fc::variants& action_variants = mvo["actions"].get_array();
for( auto& action_v : action_variants ) {
if( !action_v.is_object() ) {
std::cerr << "Empty 'action' in transaction" << endl;
return;
}
fc::variant_object& action_vo = action_v.get_object();
if( action_vo.contains( "data" ) && action_vo.contains( "hex_data" ) ) {
fc::mutable_variant_object maction_vo = action_vo;
maction_vo["data"] = maction_vo["hex_data"];
action_vo = maction_vo;
vo = mvo;
} else if( action_vo.contains( "data" ) ) {
if( !action_vo["data"].is_string() ) {
std::cerr << "get transaction_id only supports un-exploded 'data' (hex form)" << std::endl;
return;
}
}
}
} else {
std::cerr << "transaction json 'actions' is not an array" << std::endl;
return;
}
} else {
std::cerr << "transaction json does not include 'actions'" << std::endl;
return;
}
auto trx = trx_var.as<transaction>();
transaction_id_type id = trx.id();
if( id == transaction().id() ) {
std::cerr << "file/string does not represent a transaction" << std::endl;
} else {
std::cout << string( id ) << std::endl;
}
} else {
std::cerr << "file/string does not represent a transaction" << std::endl;
}
所以相同的data会的到相同的trx_id,以及报Duplicate transaction error
那如何解决呢,查看eos有没有相应的解决方案
查看 cleos工具
-f,--force-unique force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times
支持相应参数,那我们看下实现的细节
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L325
if (tx_force_unique) {
trx.context_free_actions.emplace_back( generate_nonce_action() );
}
https://github.com/EOSIO/eos/blob/1418543149b7caf8fc69a23621e3db7f3c6d18ad/programs/cleos/main.cpp#L278:15
chain::action generate_nonce_action() {
return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count()));
}
查看eosio.null
账户执行 https://bloks.io/account/eosio.null
解决方案
每次发送相同的data时,交易体添加一个针对 eosio.null
的nonce的context_free_actions
使用例子
describe('custom transactions', function () {
const authorization = [{
actor: 'inita',
permission: 'active'
}]
const eos = Eos({
keyProvider: wif
})
it('context_free_actions', async function() {
await eos.transaction({
context_free_actions: [{
account: "eosio.null",
name: 'nonce',
data: "652511569",
authorization: []
}],
actions: [{
account: "bcskillsurou",
name: 'hi',
authorization: [{
actor: "bcskillsurou",
permission: 'active'
}],
data: {
"user": "bcskillsurou"
}]
})
})
it('nonce', async function() {
const trx = await eos.transaction({
actions: [ Object.assign({}, nonce, {authorization}) ],
})
})
})
备注
eosio.null
账户为系统默认创建账户
https://github.com/EOSIO/eos/blob/eb88d033c0abbc481b8a481485ef4218cdaa033a/libraries/chain/controller.cpp#L928
create_native_account( config::null_account_name, empty_authority, empty_authority );
对于ecdsa.sign
同样数据签名,然后签名一样是因为nonce
为固定值。
https://github.com/EOSIO/eosjs-ecc/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/signature.js#L207
nonce = 0;
e = BigInteger.fromBuffer(dataSha256);
while (true) {
ecsignature = ecdsa.sign(curve, dataSha256, privateKey.d, nonce++);
context_free_actions
通过对eosio.null账户的nouce动作,可以将无签名的数据打包进入context_free_action字段,结果区块信息如下:
$ cleos --wallet-url http://127.0.0.1:6666 --url http://127.0.0.1:8000 get block 440
{
"timestamp": "2018-08-14T08:47:09.000",
"producer": "eosio",
"confirmed": 0,
"previous": "000001b760e4a6610d122c5aa5d855aa49e29f3052ac3e40b9e1ef78e0f1fd02",
"transaction_mroot": "32cb43abd7863f162f4d8f3ab9026623ea99d3f8261d2c8b4d8bf920ab97e3d1",
"action_mroot": "09afeaf40d6988a14e9e92817d2ccf4023b280075c99f13782a6535ccc58cbb0",
"schedule_version": 0,
"new_producers": null,
"header_extensions": [],
"producer_signature": "SIG_K1_K2eFDzbxCg3hmQzpzPuLYmiesrciPmTHdeNsQDyFgcHUMFeMC3PntXTqiup5VuNmyb7qmH18FBdMuNKsc7jgCm1TSPFbaj",
"transactions": [{
"status": "executed",
"cpu_usage_us": 290,
"net_usage_words": 16,
"trx": {
"id": "d74843749d1e255f13572b7a3b95af9ddd6df23d1d0ad19d88e1496091d4be2b",
"signatures": [
"SIG_K1_KVzwg3QRH6ZmempNsvAxpPQa42hF4tDpV5cqwqo7EY4oSU7NMrEFwG7gdSDCnUHHhmH1EwtVAmV1z9bqtTvvQNSXiSgaWG"
],
"compression": "none",
"packed_context_free_data": "",
"context_free_data": [],
"packed_trx": "8497725bb601973ea96f0000000100408c7a02ea3055000000000085269d000706686168616861010082c95865ea3055000000000000806b010082c95865ea305500000000a8ed3232080000000000d08cf200",
"transaction": {
"expiration": "2018-08-14T08:49:08",
"ref_block_num": 438,
"ref_block_prefix": 1873362583,
"max_net_usage_words": 0,
"max_cpu_usage_ms": 0,
"delay_sec": 0,
"context_free_actions": [{
"account": "eosio.null",
"name": "nonce",
"authorization": [],
"data": "06686168616861"
}
],
"actions": [{
"account": "eosiotesta1",
"name": "hi",
"authorization": [{
"actor": "eosiotesta1",
"permission": "active"
}
],
"data": {
"user": "yeah"
},
"hex_data": "0000000000d08cf2"
}
],
"transaction_extensions": []
}
}
}
],
"block_extensions": [],
"id": "000001b8d299602b289a9194bd698476c5d39c5ad88235460908e9d43d04edc8",
"block_num": 440,
"ref_block_prefix": 2492570152
}
正常的actions的内容是hi智能合约的调用,而context_free_action中包含了无签名的data数据,是已做数字摘要后的形态。源码中的操作:
//lets also push a context free action, the multi chain test will then also include a context free action
("context_free_actions", fc::variants({
fc::mutable_variant_object()
("account", name(config::null_account_name))
("name", "nonce")
("data", fc::raw::pack(v))
})
)
参考
https://github.com/EOSIO/eos/pull/422
https://github.com/EOSIO/eosjs-ecc/commit/01419633630da42bd76c21503cadb4298cee1ad9
https://github.com/EOSIO/eos/pull/6829
https://github.com/ganioc/eosjs3/blob/203cdae9a70198b53c86e35b18a10560a99c1c12/src/index.test.js
https://github.com/EOSIO/eosjs-ecc/issues/20