BitShares-Core  4.0.0
BitShares blockchain implementation and command-line interface software
wallet_sign.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017 Cryptonomex, Inc., and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 #include <fc/crypto/aes.hpp>
26 
27 #include "wallet_api_impl.hpp"
29 
30 /***
31  * These methods handle signing and keys
32  */
33 
34 namespace graphene { namespace wallet { namespace detail {
35 
37  {
38  uint32_t x = addr.addr._hash[0].value();
39  static const char hd[] = "0123456789abcdef";
40  string result;
41 
42  result += hd[(x >> 0x1c) & 0x0f];
43  result += hd[(x >> 0x18) & 0x0f];
44  result += hd[(x >> 0x14) & 0x0f];
45  result += hd[(x >> 0x10) & 0x0f];
46  result += hd[(x >> 0x0c) & 0x0f];
47  result += hd[(x >> 0x08) & 0x0f];
48  result += hd[(x >> 0x04) & 0x0f];
49  result += hd[(x ) & 0x0f];
50 
51  return result;
52  }
53 
54  fc::ecc::private_key derive_private_key( const std::string& prefix_string, int sequence_number )
55  {
56  std::string sequence_string = std::to_string(sequence_number);
57  fc::sha512 h = fc::sha512::hash(prefix_string + " " + sequence_string);
59  return derived_key;
60  }
61 
62  string normalize_brain_key( string s )
63  {
64  size_t i = 0, n = s.length();
65  std::string result;
66  char c;
67  result.reserve( n );
68 
69  bool preceded_by_whitespace = false;
70  bool non_empty = false;
71  while( i < n )
72  {
73  c = s[i++];
74  switch( c )
75  {
76  case ' ': case '\t': case '\r': case '\n': case '\v': case '\f':
77  preceded_by_whitespace = true;
78  continue;
79 
80  case 'a': c = 'A'; break;
81  case 'b': c = 'B'; break;
82  case 'c': c = 'C'; break;
83  case 'd': c = 'D'; break;
84  case 'e': c = 'E'; break;
85  case 'f': c = 'F'; break;
86  case 'g': c = 'G'; break;
87  case 'h': c = 'H'; break;
88  case 'i': c = 'I'; break;
89  case 'j': c = 'J'; break;
90  case 'k': c = 'K'; break;
91  case 'l': c = 'L'; break;
92  case 'm': c = 'M'; break;
93  case 'n': c = 'N'; break;
94  case 'o': c = 'O'; break;
95  case 'p': c = 'P'; break;
96  case 'q': c = 'Q'; break;
97  case 'r': c = 'R'; break;
98  case 's': c = 'S'; break;
99  case 't': c = 'T'; break;
100  case 'u': c = 'U'; break;
101  case 'v': c = 'V'; break;
102  case 'w': c = 'W'; break;
103  case 'x': c = 'X'; break;
104  case 'y': c = 'Y'; break;
105  case 'z': c = 'Z'; break;
106 
107  default:
108  break;
109  }
110  if( preceded_by_whitespace && non_empty )
111  result.push_back(' ');
112  result.push_back(c);
113  preceded_by_whitespace = false;
114  non_empty = true;
115  }
116  return result;
117  }
118 
120  {
121  if( !is_locked() )
122  {
123  plain_keys data;
124  data.keys = _keys;
125  data.checksum = _checksum;
126  auto plain_txt = fc::raw::pack(data);
127  _wallet.cipher_keys = fc::aes_encrypt( data.checksum, plain_txt );
128  }
129  }
130 
131  memo_data wallet_api_impl::sign_memo(string from, string to, string memo)
132  {
133  FC_ASSERT( !self.is_locked() );
134 
135  memo_data md = memo_data();
136 
137  // get account memo key, if that fails, try a pubkey
138  try {
139  account_object from_account = get_account(from);
140  md.from = from_account.options.memo_key;
141  } catch (const fc::exception& e) {
142  md.from = self.get_public_key( from );
143  }
144  // same as above, for destination key
145  try {
146  account_object to_account = get_account(to);
147  md.to = to_account.options.memo_key;
148  } catch (const fc::exception& e) {
149  md.to = self.get_public_key( to );
150  }
151 
152  md.set_message(get_private_key(md.from), md.to, memo);
153  return md;
154  }
155 
157  {
158  FC_ASSERT(!is_locked());
159  std::string clear_text;
160 
161  const memo_data *memo = &md;
162 
163  try {
164  FC_ASSERT( _keys.count(memo->to) || _keys.count(memo->from),
165  "Memo is encrypted to a key ${to} or ${from} not in this wallet.",
166  ("to", memo->to)("from",memo->from) );
167  if( _keys.count(memo->to) ) {
168  auto my_key = wif_to_key(_keys.at(memo->to));
169  FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted.");
170  clear_text = memo->get_message(*my_key, memo->from);
171  } else {
172  auto my_key = wif_to_key(_keys.at(memo->from));
173  FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted.");
174  clear_text = memo->get_message(*my_key, memo->to);
175  }
176  } catch (const fc::exception& e) {
177  elog("Error when decrypting memo: ${e}", ("e", e.to_detail_string()));
178  }
179 
180  return clear_text;
181  }
182 
183  signed_message wallet_api_impl::sign_message(string signer, string message)
184  {
185  FC_ASSERT( !self.is_locked() );
186 
187  const account_object from_account = get_account(signer);
188  auto dynamic_props = get_dynamic_global_properties();
189 
190  signed_message msg;
191  msg.message = message;
192  msg.meta.account = from_account.name;
193  msg.meta.memo_key = from_account.options.memo_key;
194  msg.meta.block = dynamic_props.head_block_number;
195  msg.meta.time = dynamic_props.time.to_iso_string() + "Z";
196  msg.signature = get_private_key( from_account.options.memo_key ).sign_compact( msg.digest() );
197  return msg;
198  }
199 
200  bool wallet_api_impl::verify_message( const string& message, const string& account, int block, const string& time,
201  const compact_signature& sig )
202  {
203  const account_object from_account = get_account( account );
204 
205  signed_message msg;
206  msg.message = message;
207  msg.meta.account = from_account.name;
208  msg.meta.memo_key = from_account.options.memo_key;
209  msg.meta.block = block;
210  msg.meta.time = time;
211  msg.signature = sig;
212 
213  return verify_signed_message( msg );
214  }
215 
217  {
218  if( !message.signature.valid() ) return false;
219 
220  const account_object from_account = get_account( message.meta.account );
221 
222  const public_key signer( *message.signature, message.digest() );
223  if( !( message.meta.memo_key == signer ) ) return false;
224  FC_ASSERT( from_account.options.memo_key == signer,
225  "Message was signed by contained key, but it doesn't belong to the contained account!" );
226 
227  return true;
228  }
229 
230  /* meta contains lines of the form "key=value".
231  * Returns the value for the corresponding key, throws if key is not present. */
232  static string meta_extract( const string& meta, const string& key )
233  {
234  FC_ASSERT( meta.size() > key.size(), "Key '${k}' not found!", ("k",key) );
235  size_t start;
236  if( meta.substr( 0, key.size() ) == key && meta[key.size()] == '=' )
237  start = 0;
238  else
239  {
240  start = meta.find( "\n" + key + "=" );
241  FC_ASSERT( start != string::npos, "Key '${k}' not found!", ("k",key) );
242  ++start;
243  }
244  start += key.size() + 1;
245  size_t lf = meta.find( "\n", start );
246  if( lf == string::npos ) lf = meta.size();
247  return meta.substr( start, lf - start );
248  }
249 
250  bool wallet_api_impl::verify_encapsulated_message( const string& message )
251  {
252  signed_message msg;
253  size_t begin_p = message.find( ENC_HEADER );
254  FC_ASSERT( begin_p != string::npos, "BEGIN MESSAGE line not found!" );
255  size_t meta_p = message.find( ENC_META, begin_p );
256  FC_ASSERT( meta_p != string::npos, "BEGIN META line not found!" );
257  FC_ASSERT( meta_p >= begin_p + ENC_HEADER.size() + 1, "Missing message!?" );
258  size_t sig_p = message.find( ENC_SIG, meta_p );
259  FC_ASSERT( sig_p != string::npos, "BEGIN SIGNATURE line not found!" );
260  FC_ASSERT( sig_p >= meta_p + ENC_META.size(), "Missing metadata?!" );
261  size_t end_p = message.find( ENC_FOOTER, meta_p );
262  FC_ASSERT( end_p != string::npos, "END MESSAGE line not found!" );
263  FC_ASSERT( end_p >= sig_p + ENC_SIG.size() + 1, "Missing signature?!" );
264 
265  msg.message = message.substr( begin_p + ENC_HEADER.size(), meta_p - begin_p - ENC_HEADER.size() - 1 );
266  const string meta = message.substr( meta_p + ENC_META.size(), sig_p - meta_p - ENC_META.size() );
267  const string sig = message.substr( sig_p + ENC_SIG.size(), end_p - sig_p - ENC_SIG.size() - 1 );
268 
269  msg.meta.account = meta_extract( meta, "account" );
270  msg.meta.memo_key = public_key_type( meta_extract( meta, "memokey" ) );
271  msg.meta.block = boost::lexical_cast<uint32_t>( meta_extract( meta, "block" ) );
272  msg.meta.time = meta_extract( meta, "timestamp" );
273  msg.signature = variant(sig).as< fc::ecc::compact_signature >( 5 );
274 
275  return verify_signed_message( msg );
276  }
277 
279  bool broadcast )
280  {
281  set<public_key_type> approving_key_set = get_owned_required_keys(tx, false);
282 
283  if ( ( ( tx.ref_block_num == 0 && tx.ref_block_prefix == 0 ) ||
284  tx.expiration == fc::time_point_sec() ) &&
285  tx.signatures.empty() )
286  {
287  auto dyn_props = get_dynamic_global_properties();
288  auto parameters = get_global_properties().parameters;
289  fc::time_point_sec now = dyn_props.time;
290  tx.set_reference_block( dyn_props.head_block_id );
291  tx.set_expiration( now + parameters.maximum_time_until_expiration );
292  }
293  for ( const public_key_type &key : approving_key_set )
294  tx.sign( get_private_key( key ), _chain_id );
295 
296  if ( broadcast )
297  {
298  try
299  {
300  _remote_net_broadcast->broadcast_transaction( tx );
301  }
302  catch ( const fc::exception &e )
303  {
304  elog( "Caught exception while broadcasting tx ${id}: ${e}",
305  ( "id", tx.id().str() )( "e", e.to_detail_string() ) );
306  FC_THROW( "Caught exception while broadcasting tx" );
307  }
308  }
309 
310  return tx;
311  }
312 
314  {
315  return sign_transaction2(tx, {}, broadcast);
316  }
317 
319  const vector<public_key_type>& signing_keys, bool broadcast)
320  {
321  set<public_key_type> approving_key_set = get_owned_required_keys(tx);
322 
323  // Add any explicit keys to the approving_key_set
324  for (const public_key_type& explicit_key : signing_keys) {
325  approving_key_set.insert(explicit_key);
326  }
327 
328  auto dyn_props = get_dynamic_global_properties();
329  tx.set_reference_block( dyn_props.head_block_id );
330 
331  // first, some bookkeeping, expire old items from _recently_generated_transactions
332  // since transactions include the head block id, we just need the index for keeping transactions unique
333  // when there are multiple transactions in the same block. choose a time period that should be at
334  // least one block long, even in the worst case. 2 minutes ought to be plenty.
335  fc::time_point_sec oldest_transaction_ids_to_track(dyn_props.time - fc::minutes(2));
336  auto oldest_transaction_record_iter =
337  _recently_generated_transactions.get<timestamp_index>().lower_bound(oldest_transaction_ids_to_track);
338  auto begin_iter = _recently_generated_transactions.get<timestamp_index>().begin();
339  _recently_generated_transactions.get<timestamp_index>().erase(begin_iter, oldest_transaction_record_iter);
340 
341  uint32_t expiration_time_offset = 0;
342  for (;;)
343  {
344  tx.set_expiration( dyn_props.time + fc::seconds(30 + expiration_time_offset) );
345  tx.clear_signatures();
346 
347  for( const public_key_type& key : approving_key_set )
348  tx.sign( get_private_key(key), _chain_id );
349 
350  graphene::chain::transaction_id_type this_transaction_id = tx.id();
351  auto iter = _recently_generated_transactions.find(this_transaction_id);
352  if (iter == _recently_generated_transactions.end())
353  {
354  // we haven't generated this transaction before, the usual case
355  recently_generated_transaction_record this_transaction_record;
356  this_transaction_record.generation_time = dyn_props.time;
357  this_transaction_record.transaction_id = this_transaction_id;
358  _recently_generated_transactions.insert(this_transaction_record);
359  break;
360  }
361 
362  // else we've generated a dupe, increment expiration time and re-sign it
363  ++expiration_time_offset;
364  }
365 
366  if( broadcast )
367  {
368  try
369  {
370  _remote_net_broadcast->broadcast_transaction( tx );
371  }
372  catch (const fc::exception& e)
373  {
374  elog("Caught exception while broadcasting tx ${id}: ${e}",
375  ("id", tx.id().str())("e", e.to_detail_string()) );
376  throw;
377  }
378  }
379 
380  return tx;
381  }
382 
384  {
385  auto it = _keys.find(id);
386  FC_ASSERT( it != _keys.end() );
387 
388  fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );
389  FC_ASSERT( privkey );
390  return *privkey;
391  }
392 
394  {
395  vector<public_key_type> active_keys = account.active.get_keys();
396  if (active_keys.size() != 1)
397  FC_THROW("Expecting a simple authority with one active key");
398  return get_private_key(active_keys.front());
399  }
400 
401  // imports the private key into the wallet, and associate it in some way (?) with the
402  // given account name.
403  // @returns true if the key matches a current active/owner/memo key for the named
404  // account, false otherwise (but it is stored either way)
405  bool wallet_api_impl::import_key(string account_name_or_id, string wif_key)
406  {
407  fc::optional<fc::ecc::private_key> optional_private_key = wif_to_key(wif_key);
408  if (!optional_private_key)
409  FC_THROW("Invalid private key");
410  graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key();
411 
412  account_object account = get_account( account_name_or_id );
413 
414  // make a list of all current public keys for the named account
415  flat_set<public_key_type> all_keys_for_account;
416  std::vector<public_key_type> active_keys = account.active.get_keys();
417  std::vector<public_key_type> owner_keys = account.owner.get_keys();
418  std::copy(active_keys.begin(), active_keys.end(),
419  std::inserter(all_keys_for_account, all_keys_for_account.end()));
420  std::copy(owner_keys.begin(), owner_keys.end(),
421  std::inserter(all_keys_for_account, all_keys_for_account.end()));
422  all_keys_for_account.insert(account.options.memo_key);
423 
424  _keys[wif_pub_key] = wif_key;
425 
426  _wallet.update_account(account);
427 
428  _wallet.extra_keys[account.id].insert(wif_pub_key);
429 
430  return all_keys_for_account.find(wif_pub_key) != all_keys_for_account.end();
431  }
432 
449  bool erase_existing_sigs )
450  {
451  set<public_key_type> pks = _remote_db->get_potential_signatures( tx );
452  flat_set<public_key_type> owned_keys;
453  owned_keys.reserve( pks.size() );
454  std::copy_if( pks.begin(), pks.end(),
455  std::inserter( owned_keys, owned_keys.end() ),
456  [this]( const public_key_type &pk ) {
457  return _keys.find( pk ) != _keys.end();
458  } );
459 
460  if ( erase_existing_sigs )
461  tx.signatures.clear();
462 
463  return _remote_db->get_required_signatures( tx, owned_keys );
464  }
465 
466  flat_set<public_key_type> wallet_api_impl::get_transaction_signers(const signed_transaction &tx) const
467  {
468  return tx.get_signature_keys(_chain_id);
469  }
470 
471  signed_transaction wallet_api_impl::approve_proposal( const string& fee_paying_account, const string& proposal_id,
472  const approval_delta& delta, bool broadcast )
473  {
474  proposal_update_operation update_op;
475 
476  update_op.fee_paying_account = get_account(fee_paying_account).id;
477  update_op.proposal = fc::variant(proposal_id, 1).as<proposal_id_type>( 1 );
478  // make sure the proposal exists
479  get_object( update_op.proposal );
480 
481  for( const std::string& name : delta.active_approvals_to_add )
482  update_op.active_approvals_to_add.insert( get_account( name ).id );
483  for( const std::string& name : delta.active_approvals_to_remove )
484  update_op.active_approvals_to_remove.insert( get_account( name ).id );
485  for( const std::string& name : delta.owner_approvals_to_add )
486  update_op.owner_approvals_to_add.insert( get_account( name ).id );
487  for( const std::string& name : delta.owner_approvals_to_remove )
488  update_op.owner_approvals_to_remove.insert( get_account( name ).id );
489  for( const std::string& k : delta.key_approvals_to_add )
490  update_op.key_approvals_to_add.insert( public_key_type( k ) );
491  for( const std::string& k : delta.key_approvals_to_remove )
492  update_op.key_approvals_to_remove.insert( public_key_type( k ) );
493 
495  tx.operations.push_back(update_op);
496  set_operation_fees(tx, get_global_properties().parameters.get_current_fees());
497  tx.validate();
498  return sign_transaction(tx, broadcast);
499  }
500 
501 }}} // namespace graphene::wallet::detail
bool import_key(string account_name_or_id, string wif_key)
boost::endian::little_uint32_buf_t _hash[5]
Definition: ripemd160.hpp:71
set< public_key_type > get_owned_required_keys(signed_transaction &tx, bool erase_existing_sigs=true)
memo_data sign_memo(string from, string to, string memo)
string address_to_shorthash(const graphene::protocol::address &addr)
Definition: wallet_sign.cpp:36
dynamic_global_property_object get_dynamic_global_properties() const
unsigned aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext)
Definition: aes.cpp:181
virtual const transaction_id_type & id() const
Definition: transaction.cpp:70
signed_transaction sign_transaction2(signed_transaction tx, const vector< public_key_type > &signing_keys=vector< public_key_type >(), bool broadcast=false)
microseconds minutes(int64_t m)
Definition: time.hpp:36
T as(uint32_t max_depth) const
Definition: variant.hpp:336
fc::time_point_sec expiration
Definition: transaction.hpp:87
void pack(Stream &s, const flat_set< T, A... > &value, uint32_t _max_depth)
Definition: flat.hpp:11
vector< operation > operations
Definition: transaction.hpp:89
virtual void validate() const
Definition: transaction.cpp:58
This class represents an account on the object graphAccounts are the primary unit of authority on the...
global_property_object get_global_properties() const
signed_transaction add_transaction_signature(signed_transaction tx, bool broadcast)
Definition: api.cpp:56
#define elog(FORMAT,...)
Definition: logger.hpp:129
fc::optional< fc::ecc::private_key > wif_to_key(const std::string &wif_key)
compact_signature sign_compact(const fc::sha256 &digest, bool require_canonical=true) const
string read_memo(const memo_data &md)
virtual const flat_set< public_key_type > & get_signature_keys(const chain_id_type &chain_id) const
Extract public keys from signatures with given chain ID.
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
fc::optional< fc::ecc::compact_signature > signature
bool valid() const
Definition: optional.hpp:186
static sha512 hash(const char *d, uint32_t dlen)
Definition: sha512.cpp:34
#define FC_THROW(...)
Definition: exception.hpp:366
account_object get_account(account_id_type id) const
flat_set< public_key_type > get_transaction_signers(const signed_transaction &tx) const
fc::ecc::private_key get_private_key_for_account(const account_object &account) const
void set_reference_block(const block_id_type &reference_block)
Definition: transaction.cpp:97
public_key get_public_key() const
fc::api< network_broadcast_api > _remote_net_broadcast
static sha256 hash(const char *d, uint32_t dlen)
Definition: sha256.cpp:41
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
vector< string > owner_approvals_to_remove
public_key_type to
Definition: memo.hpp:43
flat_set< public_key_type > key_approvals_to_remove
Definition: proposal.hpp:134
vector< string > active_approvals_to_remove
fc::ecc::private_key derive_private_key(const std::string &prefix_string, int sequence_number)
Definition: wallet_sign.cpp:54
void set_expiration(fc::time_point_sec expiration_time)
Definition: transaction.cpp:92
signed_transaction approve_proposal(const string &fee_paying_account, const string &proposal_id, const approval_delta &delta, bool broadcast=false)
object_id_type id
Definition: object.hpp:73
flat_set< account_id_type > active_approvals_to_remove
Definition: proposal.hpp:130
contains only the public point of an elliptic curve key.
Definition: elliptic.hpp:35
microseconds seconds(int64_t s)
Definition: time.hpp:34
string str() const
Definition: ripemd160.cpp:21
bool update_account(const account_object &acct)
flat_set< account_id_type > owner_approvals_to_remove
Definition: proposal.hpp:132
signed_transaction sign_transaction(signed_transaction tx, bool broadcast=false)
std::string get_message(const fc::ecc::private_key &priv, const fc::ecc::public_key &pub) const
Definition: memo.cpp:58
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
string name
The account&#39;s name. This name must be unique among all account names on the graph. May not be empty.
adds a signature to a transaction
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
fc::ecc::private_key get_private_key(const public_key_type &id) const
static private_key regenerate(const fc::sha256 &secret)
string normalize_brain_key(string s)
Definition: wallet_sign.cpp:62
vector< public_key_type > get_keys() const
Definition: authority.hpp:85
bool verify_message(const string &message, const string &account, int block, const string &time, const compact_signature &sig)
flat_set< public_key_type > key_approvals_to_add
Definition: proposal.hpp:133
std::string to_string(double)
Definition: string.cpp:73
void set_message(const fc::ecc::private_key &priv, const fc::ecc::public_key &pub, const string &msg, uint64_t custom_nonce=0)
Definition: memo.cpp:31
graphene::db::object_downcast_t< ID > get_object(ID id) const
The proposal_update_operation updates an existing transaction proposalThis operation allows accounts ...
Definition: proposal.hpp:119
map< public_key_type, string > _keys
public_key_type from
Definition: memo.hpp:42
a 160 bit hash of a public key
Definition: address.hpp:44
signed_message sign_message(string signer, string message)
fc::sha256 digest() const
Definition: wallet.cpp:88
void copy(const path &from, const path &to)
Definition: filesystem.cpp:241
flat_set< account_id_type > owner_approvals_to_add
Definition: proposal.hpp:131
an elliptic curve private key.
Definition: elliptic.hpp:89
map< account_id_type, set< public_key_type > > extra_keys
zero_initialized_array< unsigned char, 65 > compact_signature
Definition: elliptic.hpp:27
bool verify_encapsulated_message(const string &message)
const signature_type & sign(const private_key_type &key, const chain_id_type &chain_id)
Definition: transaction.cpp:77
void set_operation_fees(signed_transaction &tx, const fee_schedule &s)
flat_set< account_id_type > active_approvals_to_add
Definition: proposal.hpp:129
bool verify_signed_message(const signed_message &message)
vector< signature_type > signatures
map< public_key_type, string > keys
defines the keys used to derive the shared secret
Definition: memo.hpp:40