ringct: encode 8 byte amount, saving 24 bytes per output
Found by knaccc
This commit is contained in:
parent
77f8f45459
commit
6ba3a11637
|
@ -45,6 +45,8 @@
|
||||||
#include "ringct/rctTypes.h"
|
#include "ringct/rctTypes.h"
|
||||||
#include "ringct/rctOps.h"
|
#include "ringct/rctOps.h"
|
||||||
|
|
||||||
|
BOOST_CLASS_VERSION(rct::ecdhTuple, 1)
|
||||||
|
|
||||||
//namespace cryptonote {
|
//namespace cryptonote {
|
||||||
namespace boost
|
namespace boost
|
||||||
{
|
{
|
||||||
|
@ -248,7 +250,15 @@ namespace boost
|
||||||
inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver)
|
inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver)
|
||||||
{
|
{
|
||||||
a & x.mask;
|
a & x.mask;
|
||||||
a & x.amount;
|
if (ver < 1)
|
||||||
|
{
|
||||||
|
a & x.amount;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
crypto::hash8 &amount = (crypto::hash8&)x.amount;
|
||||||
|
if (!Archive::is_saving::value)
|
||||||
|
memset(&x.amount, 0, sizeof(x.amount));
|
||||||
|
a & amount;
|
||||||
// a & x.senderPk; // not serialized, as we do not use it in monero currently
|
// a & x.senderPk; // not serialized, as we do not use it in monero currently
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,8 +188,8 @@ namespace hw {
|
||||||
return encrypt_payment_id(payment_id, public_key, secret_key);
|
return encrypt_payment_id(payment_id, public_key, secret_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec) = 0;
|
virtual bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) = 0;
|
||||||
virtual bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec) = 0;
|
virtual bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) = 0;
|
||||||
|
|
||||||
virtual bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
virtual bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
||||||
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) = 0;
|
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) = 0;
|
||||||
|
|
|
@ -302,13 +302,13 @@ namespace hw {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool device_default::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec) {
|
bool device_default::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) {
|
||||||
rct::ecdhEncode(unmasked, sharedSec);
|
rct::ecdhEncode(unmasked, sharedSec, short_amount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool device_default::ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec) {
|
bool device_default::ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) {
|
||||||
rct::ecdhDecode(masked, sharedSec);
|
rct::ecdhDecode(masked, sharedSec, short_amount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,8 @@ namespace hw {
|
||||||
|
|
||||||
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
|
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
|
||||||
|
|
||||||
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec) override;
|
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) override;
|
||||||
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec) override;
|
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) override;
|
||||||
|
|
||||||
bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
||||||
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) override;
|
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) override;
|
||||||
|
|
|
@ -1142,13 +1142,13 @@ namespace hw {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool device_ledger::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & AKout) {
|
bool device_ledger::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & AKout, bool short_amount) {
|
||||||
AUTO_LOCK_CMD();
|
AUTO_LOCK_CMD();
|
||||||
|
|
||||||
#ifdef DEBUG_HWDEVICE
|
#ifdef DEBUG_HWDEVICE
|
||||||
const rct::key AKout_x = hw::ledger::decrypt(AKout);
|
const rct::key AKout_x = hw::ledger::decrypt(AKout);
|
||||||
rct::ecdhTuple unmasked_x = unmasked;
|
rct::ecdhTuple unmasked_x = unmasked;
|
||||||
this->controle_device->ecdhEncode(unmasked_x, AKout_x);
|
this->controle_device->ecdhEncode(unmasked_x, AKout_x, short_amount);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int offset = set_command_header_noopt(INS_BLIND);
|
int offset = set_command_header_noopt(INS_BLIND);
|
||||||
|
@ -1179,13 +1179,13 @@ namespace hw {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool device_ledger::ecdhDecode(rct::ecdhTuple & masked, const rct::key & AKout) {
|
bool device_ledger::ecdhDecode(rct::ecdhTuple & masked, const rct::key & AKout, bool short_amount) {
|
||||||
AUTO_LOCK_CMD();
|
AUTO_LOCK_CMD();
|
||||||
|
|
||||||
#ifdef DEBUG_HWDEVICE
|
#ifdef DEBUG_HWDEVICE
|
||||||
const rct::key AKout_x = hw::ledger::decrypt(AKout);
|
const rct::key AKout_x = hw::ledger::decrypt(AKout);
|
||||||
rct::ecdhTuple masked_x = masked;
|
rct::ecdhTuple masked_x = masked;
|
||||||
this->controle_device->ecdhDecode(masked_x, AKout_x);
|
this->controle_device->ecdhDecode(masked_x, AKout_x, short_amount);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int offset = set_command_header_noopt(INS_UNBLIND);
|
int offset = set_command_header_noopt(INS_UNBLIND);
|
||||||
|
|
|
@ -190,8 +190,8 @@ namespace hw {
|
||||||
|
|
||||||
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
|
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
|
||||||
|
|
||||||
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec) override;
|
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_format) override;
|
||||||
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec) override;
|
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_format) override;
|
||||||
|
|
||||||
bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index,
|
||||||
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) override;
|
const rct::key &amount_key, const crypto::public_key &out_eph_public_key) override;
|
||||||
|
|
|
@ -487,18 +487,38 @@ namespace rct {
|
||||||
|
|
||||||
//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
|
//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
|
||||||
// where C= aG + bH
|
// where C= aG + bH
|
||||||
void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec) {
|
static key ecdhHash(const key &k)
|
||||||
|
{
|
||||||
|
char data[38];
|
||||||
|
rct::key hash;
|
||||||
|
memcpy(data, "amount", 6);
|
||||||
|
memcpy(data + 6, &k, sizeof(k));
|
||||||
|
cn_fast_hash(hash, data, sizeof(data));
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
static void xor8(key &v, const key &k)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
v.bytes[i] ^= k.bytes[i];
|
||||||
|
}
|
||||||
|
void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec, bool short_amount) {
|
||||||
key sharedSec1 = hash_to_scalar(sharedSec);
|
key sharedSec1 = hash_to_scalar(sharedSec);
|
||||||
key sharedSec2 = hash_to_scalar(sharedSec1);
|
key sharedSec2 = hash_to_scalar(sharedSec1);
|
||||||
//encode
|
//encode
|
||||||
sc_add(unmasked.mask.bytes, unmasked.mask.bytes, sharedSec1.bytes);
|
sc_add(unmasked.mask.bytes, unmasked.mask.bytes, sharedSec1.bytes);
|
||||||
sc_add(unmasked.amount.bytes, unmasked.amount.bytes, sharedSec2.bytes);
|
if (short_amount)
|
||||||
|
xor8(unmasked.amount, ecdhHash(sharedSec));
|
||||||
|
else
|
||||||
|
sc_add(unmasked.amount.bytes, unmasked.amount.bytes, sharedSec2.bytes);
|
||||||
}
|
}
|
||||||
void ecdhDecode(ecdhTuple & masked, const key & sharedSec) {
|
void ecdhDecode(ecdhTuple & masked, const key & sharedSec, bool short_amount) {
|
||||||
key sharedSec1 = hash_to_scalar(sharedSec);
|
key sharedSec1 = hash_to_scalar(sharedSec);
|
||||||
key sharedSec2 = hash_to_scalar(sharedSec1);
|
key sharedSec2 = hash_to_scalar(sharedSec1);
|
||||||
//decode
|
//decode
|
||||||
sc_sub(masked.mask.bytes, masked.mask.bytes, sharedSec1.bytes);
|
sc_sub(masked.mask.bytes, masked.mask.bytes, sharedSec1.bytes);
|
||||||
sc_sub(masked.amount.bytes, masked.amount.bytes, sharedSec2.bytes);
|
if (short_amount)
|
||||||
|
xor8(masked.amount, ecdhHash(sharedSec));
|
||||||
|
else
|
||||||
|
sc_sub(masked.amount.bytes, masked.amount.bytes, sharedSec2.bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ namespace rct {
|
||||||
|
|
||||||
//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
|
//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
|
||||||
// where C= aG + bH
|
// where C= aG + bH
|
||||||
void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec);
|
void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec, bool short_amount);
|
||||||
void ecdhDecode(ecdhTuple & masked, const key & sharedSec);
|
void ecdhDecode(ecdhTuple & masked, const key & sharedSec, bool short_amount);
|
||||||
}
|
}
|
||||||
#endif /* RCTOPS_H */
|
#endif /* RCTOPS_H */
|
||||||
|
|
|
@ -682,7 +682,7 @@ namespace rct {
|
||||||
//mask amount and mask
|
//mask amount and mask
|
||||||
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
||||||
rv.ecdhInfo[i].amount = d2h(amounts[i]);
|
rv.ecdhInfo[i].amount = d2h(amounts[i]);
|
||||||
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i]);
|
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2);
|
||||||
}
|
}
|
||||||
|
|
||||||
//set txn fee
|
//set txn fee
|
||||||
|
@ -803,7 +803,7 @@ namespace rct {
|
||||||
//mask amount and mask
|
//mask amount and mask
|
||||||
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
rv.ecdhInfo[i].mask = copy(outSk[i].mask);
|
||||||
rv.ecdhInfo[i].amount = d2h(outamounts[i]);
|
rv.ecdhInfo[i].amount = d2h(outamounts[i]);
|
||||||
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i]);
|
hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2);
|
||||||
}
|
}
|
||||||
|
|
||||||
//set txn fee
|
//set txn fee
|
||||||
|
@ -1102,7 +1102,7 @@ namespace rct {
|
||||||
|
|
||||||
//mask amount and mask
|
//mask amount and mask
|
||||||
ecdhTuple ecdh_info = rv.ecdhInfo[i];
|
ecdhTuple ecdh_info = rv.ecdhInfo[i];
|
||||||
hwdev.ecdhDecode(ecdh_info, sk);
|
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2);
|
||||||
mask = ecdh_info.mask;
|
mask = ecdh_info.mask;
|
||||||
key amount = ecdh_info.amount;
|
key amount = ecdh_info.amount;
|
||||||
key C = rv.outPk[i].mask;
|
key C = rv.outPk[i].mask;
|
||||||
|
@ -1132,7 +1132,7 @@ namespace rct {
|
||||||
|
|
||||||
//mask amount and mask
|
//mask amount and mask
|
||||||
ecdhTuple ecdh_info = rv.ecdhInfo[i];
|
ecdhTuple ecdh_info = rv.ecdhInfo[i];
|
||||||
hwdev.ecdhDecode(ecdh_info, sk);
|
hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2);
|
||||||
mask = ecdh_info.mask;
|
mask = ecdh_info.mask;
|
||||||
key amount = ecdh_info.amount;
|
key amount = ecdh_info.amount;
|
||||||
key C = rv.outPk[i].mask;
|
key C = rv.outPk[i].mask;
|
||||||
|
|
|
@ -282,7 +282,20 @@ namespace rct {
|
||||||
return false;
|
return false;
|
||||||
for (size_t i = 0; i < outputs; ++i)
|
for (size_t i = 0; i < outputs; ++i)
|
||||||
{
|
{
|
||||||
FIELDS(ecdhInfo[i])
|
if (type == RCTTypeBulletproof2)
|
||||||
|
{
|
||||||
|
ar.begin_object();
|
||||||
|
FIELD_N("mask", ecdhInfo[i].mask);
|
||||||
|
if (!typename Archive<W>::is_saving())
|
||||||
|
memset(ecdhInfo[i].amount.bytes, 0, sizeof(ecdhInfo[i].amount.bytes));
|
||||||
|
crypto::hash8 &amount = (crypto::hash8&)ecdhInfo[i].amount;
|
||||||
|
FIELD(amount);
|
||||||
|
ar.end_object();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FIELDS(ecdhInfo[i])
|
||||||
|
}
|
||||||
if (outputs - i > 1)
|
if (outputs - i > 1)
|
||||||
ar.delimit_array();
|
ar.delimit_array();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9637,7 +9637,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
|
||||||
crypto::secret_key scalar1;
|
crypto::secret_key scalar1;
|
||||||
hwdev.derivation_to_scalar(found_derivation, n, scalar1);
|
hwdev.derivation_to_scalar(found_derivation, n, scalar1);
|
||||||
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
|
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
|
||||||
hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1));
|
hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
|
||||||
const rct::key C = tx.rct_signatures.outPk[n].mask;
|
const rct::key C = tx.rct_signatures.outPk[n].mask;
|
||||||
rct::key Ctmp;
|
rct::key Ctmp;
|
||||||
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
|
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
|
||||||
|
@ -10142,7 +10142,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
|
||||||
crypto::secret_key shared_secret;
|
crypto::secret_key shared_secret;
|
||||||
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
|
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
|
||||||
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
|
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
|
||||||
rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret));
|
rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
|
||||||
amount = rct::h2d(ecdh_info.amount);
|
amount = rct::h2d(ecdh_info.amount);
|
||||||
}
|
}
|
||||||
total += amount;
|
total += amount;
|
||||||
|
|
|
@ -455,7 +455,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
|
||||||
crypto::secret_key scalar1;
|
crypto::secret_key scalar1;
|
||||||
crypto::derivation_to_scalar(derivation, n, scalar1);
|
crypto::derivation_to_scalar(derivation, n, scalar1);
|
||||||
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
|
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
|
||||||
rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1));
|
rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
|
||||||
rct::key C = tx.rct_signatures.outPk[n].mask;
|
rct::key C = tx.rct_signatures.outPk[n].mask;
|
||||||
rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
|
rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
|
||||||
CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
|
CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
|
||||||
|
|
|
@ -114,7 +114,7 @@ TEST(device, ops)
|
||||||
ASSERT_EQ(ki0, ki1);
|
ASSERT_EQ(ki0, ki1);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(device, ecdh)
|
TEST(device, ecdh32)
|
||||||
{
|
{
|
||||||
hw::core::device_default dev;
|
hw::core::device_default dev;
|
||||||
rct::ecdhTuple tuple, tuple2;
|
rct::ecdhTuple tuple, tuple2;
|
||||||
|
@ -123,8 +123,24 @@ TEST(device, ecdh)
|
||||||
tuple.amount = rct::skGen();
|
tuple.amount = rct::skGen();
|
||||||
tuple.senderPk = rct::pkGen();
|
tuple.senderPk = rct::pkGen();
|
||||||
tuple2 = tuple;
|
tuple2 = tuple;
|
||||||
dev.ecdhEncode(tuple, key);
|
dev.ecdhEncode(tuple, key, false);
|
||||||
dev.ecdhDecode(tuple, key);
|
dev.ecdhDecode(tuple, key, false);
|
||||||
|
ASSERT_EQ(tuple2.mask, tuple.mask);
|
||||||
|
ASSERT_EQ(tuple2.amount, tuple.amount);
|
||||||
|
ASSERT_EQ(tuple2.senderPk, tuple.senderPk);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(device, ecdh8)
|
||||||
|
{
|
||||||
|
hw::core::device_default dev;
|
||||||
|
rct::ecdhTuple tuple, tuple2;
|
||||||
|
rct::key key = rct::skGen();
|
||||||
|
tuple.mask = rct::skGen();
|
||||||
|
tuple.amount = rct::skGen();
|
||||||
|
tuple.senderPk = rct::pkGen();
|
||||||
|
tuple2 = tuple;
|
||||||
|
dev.ecdhEncode(tuple, key, true);
|
||||||
|
dev.ecdhDecode(tuple, key, true);
|
||||||
ASSERT_EQ(tuple2.mask, tuple.mask);
|
ASSERT_EQ(tuple2.mask, tuple.mask);
|
||||||
ASSERT_EQ(tuple2.amount, tuple.amount);
|
ASSERT_EQ(tuple2.amount, tuple.amount);
|
||||||
ASSERT_EQ(tuple2.senderPk, tuple.senderPk);
|
ASSERT_EQ(tuple2.senderPk, tuple.senderPk);
|
||||||
|
|
|
@ -843,8 +843,8 @@ TEST(ringct, ecdh_roundtrip)
|
||||||
t0.amount = d2h(amount);
|
t0.amount = d2h(amount);
|
||||||
|
|
||||||
t1 = t0;
|
t1 = t0;
|
||||||
ecdhEncode(t1, k);
|
ecdhEncode(t1, k, true);
|
||||||
ecdhDecode(t1, k);
|
ecdhDecode(t1, k, true);
|
||||||
ASSERT_TRUE(t0.mask == t1.mask);
|
ASSERT_TRUE(t0.mask == t1.mask);
|
||||||
ASSERT_TRUE(equalKeys(t0.mask, t1.mask));
|
ASSERT_TRUE(equalKeys(t0.mask, t1.mask));
|
||||||
ASSERT_TRUE(t0.amount == t1.amount);
|
ASSERT_TRUE(t0.amount == t1.amount);
|
||||||
|
|
Loading…
Reference in New Issue