先做笔记,后面整理更新
相同的需求如
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