您正在查看: 2019年6月

一个交易,多个签名的调研

先做笔记,后面整理更新
相同的需求如
https://eosio.stackexchange.com/questions/4012/double-sign-a-transaction-first-on-client-second-on-server?rq=1

想做的是执行action时做用户客户端以及服务端分别的账号权限验证。
推测逻辑如下

  • 由客户通过scatter签名
  • 发送到后端
  • 由服务器签名
  • 广播到网络

参考下 eosj
https://gist.github.com/adyliu/492503b94d0306371298f24e15481da4

{
  "compression" : "none",
  "transaction" : {
    "expiration" : "2018-09-28T09:28:40.5",
    "ref_block_num" : 16659166,
    "ref_block_prefix" : 4253062493,
    "max_net_usage_words" : 0,
    "max_cpu_usage_ms" : 0,
    "delay_sec" : 0,
    "context_free_actions" : [ ],
    "actions" : [ {
      "account" : "eosio.token",
      "name" : "transfer",
      "authorization" : [ {
        "actor" : "shijiebangmm",
        "permission" : "active"
      } ],
      "data" : "20259be628f75cc3104208aee1a924e5290900000000000004454f53000000002f73656e7420627920656f732073646b202868747470733a2f2f6769746875622e636f6d2f6164796c69752f6a656f73"
    } ],
    "transaction_extensions" : [ ],
    "context_free_data" : [ ]
  },
  "signatures" : [ "SIG_K1_KjY8Cs8zVdg4MNjGdJ51v252V6YVUVnhyDuDGjLuhfsuv4GQTnmu9uV59SiLMmNAAiaSmT7orb1iyZfXYHES15MFWRiBvy" ]
}

最后交易中 signatures 会有多个签名
https://github.com/adyliu/jeos/blob/53dbd027cd59d367d9a197cbff5a58bdd9bf7195/src/main/java/io/jafka/jeos/impl/LocalApiImpl.java

// ⑤ build the packed transaction
PackedTransaction packedTransaction = new PackedTransaction();
packedTransaction.setExpiration(arg.getHeadBlockTime().plusSeconds(arg.getExpiredSecond()));
packedTransaction.setRefBlockNum(arg.getLastIrreversibleBlockNum());
packedTransaction.setRefBlockPrefix(arg.getRefBlockPrefix());

packedTransaction.setMaxNetUsageWords(0);
packedTransaction.setMaxCpuUsageMs(0);
packedTransaction.setDelaySec(0);
packedTransaction.setActions(actions);

String hash = sign(privateKey, arg, packedTransaction);
PushTransactionRequest req = new PushTransactionRequest();
req.setTransaction(packedTransaction);
req.setSignatures(Arrays.asList(hash));
return req;

先解data,然后用服务端私钥签名后,将签名附加上去,

合约内增加对应的 require_auth

其他不必要的记录

 auto res = ::check_transaction_authorization( prop.packed_transaction.data(), prop.packed_transaction.size(),
                                                 (const char*)0, 0,
                                                 packed_provided_approvals.data(), packed_provided_approvals.size()
                                                 );

扒扒链代码,找一下有没有相关的代码

transaction_id_type no_assert_id;
   {
      signed_transaction trx;
      trx.actions.emplace_back( vector<permission_level>{{N(asserter),config::active_name}},
                                assertdef {1, "Should Not Assert!"} );
      trx.actions[0].authorization = {{N(asserter),config::active_name}};

      set_transaction_headers(trx);
      trx.sign( get_private_key( N(asserter), "active" ), control->get_chain_id() );
      auto result = push_transaction( trx );
      BOOST_CHECK_EQUAL(result->receipt->status, transaction_receipt::executed);
      BOOST_CHECK_EQUAL(result->action_traces.size(), 1u);
      BOOST_CHECK_EQUAL(result->action_traces.at(0).receipt.receiver.to_string(),  name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.account.to_string(), name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.name.to_string(),  name(N(procassert)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.size(),  1u );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).actor.to_string(),  name(N(asserter)).to_string() );
      BOOST_CHECK_EQUAL(result->action_traces.at(0).act.authorization.at(0).permission.to_string(),  name(config::active_name).to_string() );
      no_assert_id = trx.id();
   }

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/unittests/wasm_tests.cpp#L85

const signature_type& signed_transaction::sign(const private_key_type& key, const chain_id_type& chain_id) {
   signatures.push_back(key.sign(sig_digest(chain_id, context_free_data)));
   return signatures.back();
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/libraries/chain/transaction.cpp#L138

fc::microseconds
signed_transaction::get_signature_keys( const chain_id_type& chain_id, fc::time_point deadline,
                                        flat_set<public_key_type>& recovered_pub_keys,
                                        bool allow_duplicate_keys)const
{
   return transaction::get_signature_keys(signatures, chain_id, deadline, context_free_data, recovered_pub_keys, allow_duplicate_keys);
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/libraries/chain/transaction.cpp#L147

看链代码,应该是支持一个交易多个签名的,那继续跟。

那看下eosjs

/** Sign a transaction */
    public async sign({ chainId, requiredKeys, serializedTransaction }: SignatureProviderArgs) {
        const signBuf = Buffer.concat([
            new Buffer(chainId, 'hex'), new Buffer(serializedTransaction), new Buffer(new Uint8Array(32)),
        ]);
        const signatures = requiredKeys.map(
            (pub) => ecc.Signature.sign(signBuf, this.keys.get(convertLegacyPublicKey(pub))).toString(),
        );
        return { signatures, serializedTransaction };
    }

https://github.com/EOSIO/eosjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/eosjs-jssig.ts#L33
也是支持多个私钥给同一个交易签名的。

 it('signs a transaction', async () => {
        const eccSignatureSign = jest.spyOn(ecc.Signature, 'sign');
        eccSignatureSign.mockImplementation((buffer, signKey) => signKey);

        const provider = new JsSignatureProvider(privateKeys);
        const chainId = '12345';
        const requiredKeys = [
            publicKeys[0],
            publicKeys[2],
        ];
        const serializedTransaction = new Uint8Array([
            0, 16, 32, 128, 255,
        ]);
        const abis: any[] = [];

        const signOutput = await provider.sign({ chainId, requiredKeys, serializedTransaction, abis });

        expect(eccSignatureSign).toHaveBeenCalledTimes(2);
        expect(signOutput).toEqual({ signatures: [privateKeys[0], privateKeys[2]], serializedTransaction });
    });

https://github.com/EOSIO/eosjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/tests/eosjs-jssig.test.ts#L37

template <typename T>
transaction_trace_ptr CallAction(TESTER& test, T ac, const vector<account_name>& scope = {N(testapi)}) {
   signed_transaction trx;


   auto pl = vector<permission_level>{{scope[0], config::active_name}};
   if (scope.size() > 1)
      for (size_t i = 1; i < scope.size(); i++)
         pl.push_back({scope[i], config::active_name});

   action act(pl, ac);
   trx.actions.push_back(act);

   test.set_transaction_headers(trx);
   auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.control->get_chain_id());
   flat_set<public_key_type> keys;
   trx.get_signature_keys(test.control->get_chain_id(), fc::time_point::maximum(), keys);
   auto res = test.push_transaction(trx);
   BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed);
   test.produce_block();
   return res;
}

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/unittests/api_tests.cpp#L198

action data pack/unpack

// pack action data
   string unpacked_action_data_account_string;
   string unpacked_action_data_name_string;
   string unpacked_action_data_string;
   auto pack_action_data = convert->add_subcommand("pack_action_data", localized("From json action data to packed form"));
   pack_action_data->add_option("account", unpacked_action_data_account_string, localized("The name of the account that hosts the contract"))->required();
   pack_action_data->add_option("name", unpacked_action_data_name_string, localized("The name of the function that's called by this action"))->required();
   pack_action_data->add_option("unpacked_action_data", unpacked_action_data_string, localized("The action data expressed as json"))->required();
   pack_action_data->set_callback([&] {
      fc::variant unpacked_action_data_json;
      try {
         unpacked_action_data_json = json_from_file_or_string(unpacked_action_data_string);
      } EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON")
      bytes packed_action_data_string = variant_to_bin(unpacked_action_data_account_string, unpacked_action_data_name_string, unpacked_action_data_json);
      std::cout << fc::to_hex(packed_action_data_string.data(), packed_action_data_string.size()) << std::endl;
   });

   // unpack action data
   string packed_action_data_account_string;
   string packed_action_data_name_string;
   string packed_action_data_string;
   auto unpack_action_data = convert->add_subcommand("unpack_action_data", localized("From packed to json action data form"));
   unpack_action_data->add_option("account", packed_action_data_account_string, localized("The name of the account that hosts the contract"))->required();
   unpack_action_data->add_option("name", packed_action_data_name_string, localized("The name of the function that's called by this action"))->required();
   unpack_action_data->add_option("packed_action_data", packed_action_data_string, localized("The action data expressed as packed hex string"))->required();
   unpack_action_data->set_callback([&] {
      EOS_ASSERT( packed_action_data_string.size() >= 2, transaction_type_exception, "No packed_action_data found" );
      vector<char> packed_action_data_blob(packed_action_data_string.size()/2);
      fc::from_hex(packed_action_data_string, packed_action_data_blob.data(), packed_action_data_blob.size());
      fc::variant unpacked_action_data_json = bin_to_variant(packed_action_data_account_string, packed_action_data_name_string, packed_action_data_blob);
      std::cout << fc::json::to_pretty_string(unpacked_action_data_json) << std::endl;
   });

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/programs/cleos/main.cpp#L2401

https://github.com/EOSIO/eos/blob/686f0deb5dac097cc292f735ccb47c238e763de0/programs/cleos/main.cpp#L2418

EOS 插入数据的RAM消耗

context.add_ram_usage(
    l.account,
    (int64_t)(config::billable_size_v<permission_link_object>)
);

https://github.com/EOSIO/eos/blob/3fddb727b8f3615917707281dfd3dd3cc5d3d66d/libraries/chain/eosio_contract.cpp#L340

eosio action abi 部分语义化

账号 合约 Action 语义 备注
eosio eosio.system delegatebw 抵押资源
undelegatebw 赎回资源
buyramkbytes 购买内存
sellram 卖出内存
refund 赎回退款
voteproducer 投票
claimrewards 申请奖励
bidname 竞拍账户
bidrefund 竞拍退还
newaccount 创建账户
updateauth 更新授权
deleteauth 删除授权
linkauth 链接授权
unlinkauth 取消链接授权
canceldelay 取消延迟交易
regproducer 注册生产者
unregprod 取消生产者
eosio.token eosio.token issue 代币增发
transfer 代币转账
create 创建代币

ref_block_num 和 ref_block_prefix

先简单记一下,后面再做补充

主要作用是判断下前后交易是不是在一个链上,也就是有没有分叉。

参考

https://github.com/EOSIO/eos/issues/4659
https://eos.live/detail/17094
https://github.com/EOSIO/eosjs/issues/151
http://www.zhongruitech.com/955126221.html