rippled
app/misc/impl/Manifest.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/misc/Manifest.h>
21 #include <ripple/app/rdb/RelationalDBInterface_global.h>
22 #include <ripple/basics/Log.h>
23 #include <ripple/basics/StringUtilities.h>
24 #include <ripple/basics/base64.h>
25 #include <ripple/basics/contract.h>
26 #include <ripple/beast/rfc2616.h>
27 #include <ripple/core/DatabaseCon.h>
28 #include <ripple/json/json_reader.h>
29 #include <ripple/protocol/PublicKey.h>
30 #include <ripple/protocol/Sign.h>
31 #include <boost/algorithm/string/trim.hpp>
32 #include <numeric>
33 #include <stdexcept>
34 
35 namespace ripple {
36 
39 {
40  auto const mk = toBase58(TokenType::NodePublic, m.masterKey);
41 
42  if (m.revoked())
43  return "Revocation Manifest " + mk;
44 
45  return "Manifest " + mk + " (" + std::to_string(m.sequence) + ": " +
47 }
48 
51 {
52  if (s.empty())
53  return std::nullopt;
54 
55  static SOTemplate const manifestFormat{
56  // A manifest must include:
57  // - the master public key
59 
60  // - a signature with that public key
62 
63  // - a sequence number
65 
66  // It may, optionally, contain:
67  // - a version number which defaults to 0
69 
70  // - a domain name
72 
73  // - an ephemeral signing key that can be changed as necessary
75 
76  // - a signature using the ephemeral signing key, if it is present
78  };
79 
80  try
81  {
82  SerialIter sit{s};
83  STObject st{sit, sfGeneric};
84 
85  st.applyTemplate(manifestFormat);
86 
87  // We only understand "version 0" manifests at this time:
88  if (st.isFieldPresent(sfVersion) && st.getFieldU16(sfVersion) != 0)
89  return std::nullopt;
90 
91  auto const pk = st.getFieldVL(sfPublicKey);
92 
93  if (!publicKeyType(makeSlice(pk)))
94  return std::nullopt;
95 
96  Manifest m;
97  m.serialized.assign(reinterpret_cast<char const*>(s.data()), s.size());
98  m.masterKey = PublicKey(makeSlice(pk));
99  m.sequence = st.getFieldU32(sfSequence);
100 
101  if (st.isFieldPresent(sfDomain))
102  {
103  auto const d = st.getFieldVL(sfDomain);
104 
105  m.domain.assign(reinterpret_cast<char const*>(d.data()), d.size());
106 
108  return std::nullopt;
109  }
110 
111  bool const hasEphemeralKey = st.isFieldPresent(sfSigningPubKey);
112  bool const hasEphemeralSig = st.isFieldPresent(sfSignature);
113 
114  if (m.revoked())
115  {
116  // Revocation manifests should not specify a new signing key
117  // or a signing key signature.
118  if (hasEphemeralKey)
119  return std::nullopt;
120 
121  if (hasEphemeralSig)
122  return std::nullopt;
123  }
124  else
125  {
126  // Regular manifests should contain a signing key and an
127  // associated signature.
128  if (!hasEphemeralKey)
129  return std::nullopt;
130 
131  if (!hasEphemeralSig)
132  return std::nullopt;
133 
134  auto const spk = st.getFieldVL(sfSigningPubKey);
135 
136  if (!publicKeyType(makeSlice(spk)))
137  return std::nullopt;
138 
139  m.signingKey = PublicKey(makeSlice(spk));
140 
141  // The signing and master keys can't be the same
142  if (m.signingKey == m.masterKey)
143  return std::nullopt;
144  }
145 
146  return m;
147  }
148  catch (std::exception const&)
149  {
150  return std::nullopt;
151  }
152 }
153 
154 template <class Stream>
155 Stream&
157  Stream& s,
158  std::string const& action,
159  PublicKey const& pk,
160  std::uint32_t seq)
161 {
162  s << "Manifest: " << action
163  << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
164  << ";";
165  return s;
166 }
167 
168 template <class Stream>
169 Stream&
171  Stream& s,
172  std::string const& action,
173  PublicKey const& pk,
174  std::uint32_t seq,
175  std::uint32_t oldSeq)
176 {
177  s << "Manifest: " << action
178  << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq
179  << ";OldSeq: " << oldSeq << ";";
180  return s;
181 }
182 
183 bool
185 {
186  STObject st(sfGeneric);
188  st.set(sit);
189 
190  // Signing key and signature are not required for
191  // master key revocations
193  return false;
194 
195  return ripple::verify(
197 }
198 
199 uint256
201 {
202  STObject st(sfGeneric);
204  st.set(sit);
205  return st.getHash(HashPrefix::manifest);
206 }
207 
208 bool
210 {
211  /*
212  The maximum possible sequence number means that the master key
213  has been revoked.
214  */
216 }
217 
220 {
221  STObject st(sfGeneric);
223  st.set(sit);
224  if (!get(st, sfSignature))
225  return std::nullopt;
226  return st.getFieldVL(sfSignature);
227 }
228 
229 Blob
231 {
232  STObject st(sfGeneric);
234  st.set(sit);
235  return st.getFieldVL(sfMasterSignature);
236 }
237 
240 {
241  try
242  {
243  std::string tokenStr;
244 
245  tokenStr.reserve(std::accumulate(
246  blob.cbegin(),
247  blob.cend(),
248  std::size_t(0),
249  [](std::size_t init, std::string const& s) {
250  return init + s.size();
251  }));
252 
253  for (auto const& line : blob)
254  tokenStr += boost::algorithm::trim_copy(line);
255 
256  tokenStr = base64_decode(tokenStr);
257 
258  Json::Reader r;
259  Json::Value token;
260 
261  if (r.parse(tokenStr, token))
262  {
263  auto const m = token.get("manifest", Json::Value{});
264  auto const k = token.get("validation_secret_key", Json::Value{});
265 
266  if (m.isString() && k.isString())
267  {
268  auto const key = strUnHex(k.asString());
269 
270  if (key && key->size() == 32)
271  return ValidatorToken{m.asString(), makeSlice(*key)};
272  }
273  }
274 
275  return std::nullopt;
276  }
277  catch (std::exception const&)
278  {
279  return std::nullopt;
280  }
281 }
282 
283 PublicKey
285 {
287  auto const iter = map_.find(pk);
288 
289  if (iter != map_.end() && !iter->second.revoked())
290  return iter->second.signingKey;
291 
292  return pk;
293 }
294 
295 PublicKey
297 {
299 
300  if (auto const iter = signingToMasterKeys_.find(pk);
301  iter != signingToMasterKeys_.end())
302  return iter->second;
303 
304  return pk;
305 }
306 
309 {
311  auto const iter = map_.find(pk);
312 
313  if (iter != map_.end() && !iter->second.revoked())
314  return iter->second.sequence;
315 
316  return std::nullopt;
317 }
318 
321 {
323  auto const iter = map_.find(pk);
324 
325  if (iter != map_.end() && !iter->second.revoked())
326  return iter->second.domain;
327 
328  return std::nullopt;
329 }
330 
333 {
335  auto const iter = map_.find(pk);
336 
337  if (iter != map_.end() && !iter->second.revoked())
338  return iter->second.serialized;
339 
340  return std::nullopt;
341 }
342 
343 bool
345 {
347  auto const iter = map_.find(pk);
348 
349  if (iter != map_.end())
350  return iter->second.revoked();
351 
352  return false;
353 }
354 
357 {
358  std::lock_guard applyLock{apply_mutex_};
359 
360  // Before we spend time checking the signature, make sure the
361  // sequence number is newer than any we have.
362  auto const iter = map_.find(m.masterKey);
363 
364  if (iter != map_.end() && m.sequence <= iter->second.sequence)
365  {
366  // We received a manifest whose sequence number is not strictly greater
367  // than the one we already know about. This can happen in several cases
368  // including when we receive manifests from a peer who doesn't have the
369  // latest data.
370  if (auto stream = j_.debug())
371  logMftAct(
372  stream,
373  "Stale",
374  m.masterKey,
375  m.sequence,
376  iter->second.sequence);
378  }
379 
380  // Now check the signature
381  if (!m.verify())
382  {
383  if (auto stream = j_.warn())
384  logMftAct(stream, "Invalid", m.masterKey, m.sequence);
386  }
387 
388  // If the master key associated with a manifest is or might be compromised
389  // and is, therefore, no longer trustworthy.
390  //
391  // A manifest revocation essentially marks a manifest as compromised. By
392  // setting the sequence number to the highest value possible, the manifest
393  // is effectively neutered and cannot be superseded by a forged one.
394  bool const revoked = m.revoked();
395 
396  if (auto stream = j_.warn(); stream && revoked)
397  logMftAct(stream, "Revoked", m.masterKey, m.sequence);
398 
399  std::lock_guard readLock{read_mutex_};
400 
401  // Sanity check: the master key of this manifest should not be used as
402  // the ephemeral key of another manifest:
403  if (auto const x = signingToMasterKeys_.find(m.masterKey);
404  x != signingToMasterKeys_.end())
405  {
406  JLOG(j_.warn()) << to_string(m)
407  << ": Master key already used as ephemeral key for "
408  << toBase58(TokenType::NodePublic, x->second);
409 
411  }
412 
413  if (!revoked)
414  {
415  // Sanity check: the ephemeral key of this manifest should not be used
416  // as the master or ephemeral key of another manifest:
417  if (auto const x = signingToMasterKeys_.find(m.signingKey);
418  x != signingToMasterKeys_.end())
419  {
420  JLOG(j_.warn())
421  << to_string(m)
422  << ": Ephemeral key already used as ephemeral key for "
423  << toBase58(TokenType::NodePublic, x->second);
424 
426  }
427 
428  if (auto const x = map_.find(m.signingKey); x != map_.end())
429  {
430  JLOG(j_.warn())
431  << to_string(m) << ": Ephemeral key used as master key for "
432  << to_string(x->second);
433 
435  }
436  }
437 
438  // This is the first manifest we are seeing for a master key. This should
439  // only ever happen once per validator run.
440  if (iter == map_.end())
441  {
442  if (auto stream = j_.info())
443  logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence);
444 
445  if (!revoked)
447 
448  auto masterKey = m.masterKey;
449  map_.emplace(std::move(masterKey), std::move(m));
451  }
452 
453  // An ephemeral key was revoked and superseded by a new key. This is
454  // expected, but should happen infrequently.
455  if (auto stream = j_.info())
456  logMftAct(
457  stream,
458  "AcceptedUpdate",
459  m.masterKey,
460  m.sequence,
461  iter->second.sequence);
462 
463  signingToMasterKeys_.erase(iter->second.signingKey);
464 
465  if (!revoked)
467 
468  iter->second = std::move(m);
469 
470  // Something has changed. Keep track of it.
471  seq_++;
472 
474 }
475 
476 void
478 {
479  auto db = dbCon.checkoutDb();
480  ripple::getManifests(*db, dbTable, *this, j_);
481 }
482 
483 bool
485  DatabaseCon& dbCon,
486  std::string const& dbTable,
487  std::string const& configManifest,
488  std::vector<std::string> const& configRevocation)
489 {
490  load(dbCon, dbTable);
491 
492  if (!configManifest.empty())
493  {
494  auto mo = deserializeManifest(base64_decode(configManifest));
495  if (!mo)
496  {
497  JLOG(j_.error()) << "Malformed validator_token in config";
498  return false;
499  }
500 
501  if (mo->revoked())
502  {
503  JLOG(j_.warn()) << "Configured manifest revokes public key";
504  }
505 
506  if (applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
507  {
508  JLOG(j_.error()) << "Manifest in config was rejected";
509  return false;
510  }
511  }
512 
513  if (!configRevocation.empty())
514  {
515  std::string revocationStr;
516  revocationStr.reserve(std::accumulate(
517  configRevocation.cbegin(),
518  configRevocation.cend(),
519  std::size_t(0),
520  [](std::size_t init, std::string const& s) {
521  return init + s.size();
522  }));
523 
524  for (auto const& line : configRevocation)
525  revocationStr += boost::algorithm::trim_copy(line);
526 
527  auto mo = deserializeManifest(base64_decode(revocationStr));
528 
529  if (!mo || !mo->revoked() ||
530  applyManifest(std::move(*mo)) == ManifestDisposition::invalid)
531  {
532  JLOG(j_.error()) << "Invalid validator key revocation in config";
533  return false;
534  }
535  }
536 
537  return true;
538 }
539 
540 void
542  DatabaseCon& dbCon,
543  std::string const& dbTable,
544  std::function<bool(PublicKey const&)> const& isTrusted)
545 {
547  auto db = dbCon.checkoutDb();
548 
549  saveManifests(*db, dbTable, isTrusted, map_, j_);
550 }
551 } // namespace ripple
ripple::Slice::size
std::size_t size() const noexcept
Returns the number of bytes in the storage.
Definition: Slice.h:79
ripple::Manifest::domain
std::string domain
The domain, if one was specified in the manifest; empty otherwise.
Definition: Manifest.h:93
ripple::makeSlice
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:240
std::string
STL class.
ripple::ManifestDisposition
ManifestDisposition
Definition: Manifest.h:183
ripple::ManifestCache::getMasterKey
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition: app/misc/impl/Manifest.cpp:296
std::exception
STL class.
ripple::sfGeneric
const SField sfGeneric(access, 0)
Definition: SField.h:332
ripple::Manifest
Definition: Manifest.h:78
ripple::Slice
An immutable linear range of bytes.
Definition: Slice.h:44
std::string::reserve
T reserve(T... args)
ripple::sfSequence
const SF_UINT32 sfSequence
ripple::HashPrefix::manifest
@ manifest
Manifest.
Json::Value::get
Value get(UInt index, const Value &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Definition: json_value.cpp:834
ripple::verify
bool verify(PublicKey const &publicKey, Slice const &m, Slice const &sig, bool mustBeFullyCanonical) noexcept
Verify a signature on a message.
Definition: PublicKey.cpp:268
ripple::Manifest::signingKey
PublicKey signingKey
The ephemeral key associated with this manifest.
Definition: Manifest.h:87
std::vector< unsigned char >
ripple::Manifest::serialized
std::string serialized
The manifest in serialized form.
Definition: Manifest.h:81
std::string::size
T size(T... args)
ripple::Manifest::masterKey
PublicKey masterKey
The master key associated with this manifest.
Definition: Manifest.h:84
ripple::Manifest::verify
bool verify() const
Returns true if manifest signature is valid.
Definition: app/misc/impl/Manifest.cpp:184
ripple::sfSigningPubKey
const SF_VL sfSigningPubKey
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::Slice::data
std::uint8_t const * data() const noexcept
Return a pointer to beginning of the storage.
Definition: Slice.h:96
ripple::ValidatorToken
Definition: Manifest.h:174
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::lock_guard
STL class.
ripple::ManifestCache::map_
hash_map< PublicKey, Manifest > map_
Active manifests stored by master public key.
Definition: Manifest.h:231
ripple::soeREQUIRED
@ soeREQUIRED
Definition: SOTemplate.h:35
ripple::ManifestDisposition::badEphemeralKey
@ badEphemeralKey
The ephemeral key is not acceptable to us.
std::function
ripple::STObject::getFieldVL
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:568
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::Slice::empty
bool empty() const noexcept
Return true if the byte range is empty.
Definition: Slice.h:68
ripple::ManifestCache::apply_mutex_
std::mutex apply_mutex_
Definition: Manifest.h:227
ripple::Manifest::revoked
bool revoked() const
Returns true if manifest revokes master key.
Definition: app/misc/impl/Manifest.cpp:209
ripple::ManifestCache::save
void save(DatabaseCon &dbCon, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted)
Save cached manifests to database.
Definition: app/misc/impl/Manifest.cpp:541
ripple::ManifestCache::read_mutex_
std::mutex read_mutex_
Definition: Manifest.h:228
ripple::sfVersion
const SF_UINT16 sfVersion
ripple::publicKeyType
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Definition: PublicKey.cpp:203
stdexcept
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:75
ripple::SOTemplate
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:82
ripple::DatabaseCon::checkoutDb
LockedSociSession checkoutDb()
Definition: DatabaseCon.h:178
ripple::ManifestCache::signingToMasterKeys_
hash_map< PublicKey, PublicKey > signingToMasterKeys_
Master public keys stored by current ephemeral public key.
Definition: Manifest.h:234
ripple::ManifestDisposition::badMasterKey
@ badMasterKey
The master key is not acceptable to us.
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::sfMasterSignature
const SF_VL sfMasterSignature
ripple::getManifests
void getManifests(soci::session &session, std::string const &dbTable, ManifestCache &mCache, beast::Journal j)
getManifests Loads manifest from wallet DB and stores it in the cache.
Definition: RelationalDBInterface_global.cpp:56
ripple::soeOPTIONAL
@ soeOPTIONAL
Definition: SOTemplate.h:36
ripple::Manifest::getSignature
std::optional< Blob > getSignature() const
Returns manifest signature.
Definition: app/misc/impl/Manifest.cpp:219
std::to_string
T to_string(T... args)
ripple::ManifestCache::revoked
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition: app/misc/impl/Manifest.cpp:344
ripple::base64_decode
std::string base64_decode(std::string const &data)
Definition: base64.cpp:245
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::ManifestCache::j_
beast::Journal j_
Definition: Manifest.h:226
ripple::loadValidatorToken
std::optional< ValidatorToken > loadValidatorToken(std::vector< std::string > const &blob)
Definition: app/misc/impl/Manifest.cpp:239
ripple::ManifestCache::seq_
std::atomic< std::uint32_t > seq_
Definition: Manifest.h:236
std::accumulate
T accumulate(T... args)
ripple::SerialIter
Definition: Serializer.h:310
ripple::ManifestCache::getSigningKey
PublicKey getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition: app/misc/impl/Manifest.cpp:284
std::uint32_t
ripple::Manifest::getMasterSignature
Blob getMasterSignature() const
Returns manifest master key signature.
Definition: app/misc/impl/Manifest.cpp:230
ripple::ManifestDisposition::invalid
@ invalid
Timely, but invalid signature.
ripple::ManifestCache::getSequence
std::optional< std::uint32_t > getSequence(PublicKey const &pk) const
Returns master key's current manifest sequence.
Definition: app/misc/impl/Manifest.cpp:308
ripple::ManifestDisposition::accepted
@ accepted
Manifest is valid.
ripple::Manifest::sequence
std::uint32_t sequence
The sequence number of this manifest.
Definition: Manifest.h:90
ripple::STObject
Definition: STObject.h:51
ripple::ManifestCache::getManifest
std::optional< std::string > getManifest(PublicKey const &pk) const
Returns mainfest corresponding to a given public key.
Definition: app/misc/impl/Manifest.cpp:332
ripple::ManifestCache::getDomain
std::optional< std::string > getDomain(PublicKey const &pk) const
Returns domain claimed by a given public key.
Definition: app/misc/impl/Manifest.cpp:320
ripple::ManifestDisposition::stale
@ stale
Sequence is too old.
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
std::vector::cbegin
T cbegin(T... args)
ripple::logMftAct
Stream & logMftAct(Stream &s, std::string const &action, PublicKey const &pk, std::uint32_t seq)
Definition: app/misc/impl/Manifest.cpp:156
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::DatabaseCon
Definition: DatabaseCon.h:81
ripple::sfSignature
const SF_VL sfSignature
ripple::Manifest::hash
uint256 hash() const
Returns hash of serialized manifest data.
Definition: app/misc/impl/Manifest.cpp:200
std::string::empty
T empty(T... args)
std::string::assign
T assign(T... args)
ripple::TokenType::NodePublic
@ NodePublic
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:38
ripple::isProperlyFormedTomlDomain
bool isProperlyFormedTomlDomain(std::string const &domain)
Determines if the given string looks like a TOML-file hosting domain.
std::vector::cend
T cend(T... args)
numeric
std::numeric_limits::max
T max(T... args)
ripple::ManifestCache::load
bool load(DatabaseCon &dbCon, std::string const &dbTable, std::string const &configManifest, std::vector< std::string > const &configRevocation)
Populate manifest cache with manifests in database and config.
Definition: app/misc/impl/Manifest.cpp:484
ripple::sfDomain
const SF_VL sfDomain
ripple::ManifestCache::applyManifest
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition: app/misc/impl/Manifest.cpp:356
ripple::strUnHex
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
Definition: StringUtilities.h:49
ripple::deserializeManifest
std::optional< Manifest > deserializeManifest(Slice s)
Constructs Manifest from serialized string.
Definition: app/misc/impl/Manifest.cpp:50
ripple::STObject::set
void set(const SOTemplate &)
Definition: STObject.cpp:73
ripple::saveManifests
void saveManifests(soci::session &session, std::string const &dbTable, std::function< bool(PublicKey const &)> const &isTrusted, hash_map< PublicKey, Manifest > const &map, beast::Journal j)
saveManifests Saves all given manifests to database.
Definition: RelationalDBInterface_global.cpp:104
std::string::data
T data(T... args)
ripple::sfPublicKey
const SF_VL sfPublicKey
ripple::soeDEFAULT
@ soeDEFAULT
Definition: SOTemplate.h:37
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:118
ripple::STObject::getHash
uint256 getHash(HashPrefix prefix) const
Definition: STObject.cpp:312