eos linkauth unlinkauth
void apply_eosio_linkauth(apply_context& context) {
// context.require_write_lock( config::eosio_auth_scope );
auto requirement = context.act.data_as<linkauth>();
try {
EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty");
context.require_authorization(requirement.account); // only here to mark the single authority on this action as used
auto& db = context.db;
const auto *account = db.find<account_object, by_name>(requirement.account);
EOS_ASSERT(account != nullptr, account_query_exception,
"Failed to retrieve account: ${account}", ("account", requirement.account)); // Redundant?
const auto *code = db.find<account_object, by_name>(requirement.code);
EOS_ASSERT(code != nullptr, account_query_exception,
"Failed to retrieve code for account: ${account}", ("account", requirement.code));
if( requirement.requirement != config::eosio_any_name ) {
const auto *permission = db.find<permission_object, by_name>(requirement.requirement);
EOS_ASSERT(permission != nullptr, permission_query_exception,
"Failed to retrieve permission: ${permission}", ("permission", requirement.requirement));
}
auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);
if( link ) {
EOS_ASSERT(link->required_permission != requirement.requirement, action_validate_exception,
"Attempting to update required authority, but new requirement is same as old");
db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) {
link.required_permission = requirement;
});
} else {
const auto& l = db.create<permission_link_object>([&requirement](permission_link_object& link) {
link.account = requirement.account;
link.code = requirement.code;
link.message_type = requirement.type;
link.required_permission = requirement.requirement;
});
context.add_ram_usage(
l.account,
(int64_t)(config::billable_size_v<permission_link_object>)
);
}
} FC_CAPTURE_AND_RETHROW((requirement))
}
void apply_eosio_unlinkauth(apply_context& context) {
// context.require_write_lock( config::eosio_auth_scope );
auto& db = context.db;
auto unlink = context.act.data_as<unlinkauth>();
context.require_authorization(unlink.account); // only here to mark the single authority on this action as used
auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);
EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found");
context.add_ram_usage(
link->account,
-(int64_t)(config::billable_size_v<permission_link_object>)
);
db.remove(*link);
}
auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);
父级可以把自己已有的action执行权限link给子级,同一子级只能有一个权限有同一action执行权限,赋值给同一级其他的,会把原先的删除掉。如果子级有某个action执行权限,那父级也有。如果子级unlink了某一action执行权限,将从此级追溯到顶父级active或者owner,顶父级之前的此action执行权限都会删除掉(因为active和owner为特殊账户,不会被删除)。
因为account,code,type
三个值加起来是查询索引,所以只有一条记录,记录着当前action执行权限link到哪个子权限了,并且子级有的话,父级就有。
新创建的权限,没link的话,没有任何action执行权限。
一个交易,多个签名的调研
先做笔记,后面整理更新
相同的需求如
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();
}
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();
}
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);
}
看链代码,应该是支持一个交易多个签名的,那继续跟。
那看下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 });
});
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;
}
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;
});
EOS 插入数据的RAM消耗
context.add_ram_usage(
l.account,
(int64_t)(config::billable_size_v<permission_link_object>)
);