aboutsummaryrefslogtreecommitdiff
path: root/tests/unit_tests/asset_confidential.cpp
blob: b400a2041f1c1f621e2cb7ddb9af58231f1d7d7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include "gtest/gtest.h"

#include <cstring>

#include "cryptonote_basic/asset_confidential.h"
#include "ringct/bulletproofs_plus.h"
#include "ringct/rctOps.h"
#include "ringct/rctSigs.h"
#include "device/device.hpp"

namespace
{
  crypto::hash asset_id(unsigned char value)
  {
    crypto::hash id{};
    id.data[0] = value;
    return id;
  }

  cryptonote::assets::confidential_asset_balance make_balance(
    const crypto::hash& id, uint64_t input_amount,
    const std::vector<uint64_t>& outputs, const std::vector<uint64_t>& burns)
  {
    rct::keyV masks = rct::skvGen(outputs.size() + burns.size());
    rct::key input_mask = rct::zero();
    for (const rct::key& mask : masks)
      sc_add(input_mask.bytes, input_mask.bytes, mask.bytes);
    cryptonote::assets::confidential_asset_balance balance;
    balance.asset_id = id;
    balance.pseudo_inputs.push_back({id, rct::commit(input_amount, input_mask)});
    size_t index = 0;
    for (const uint64_t amount : outputs)
      balance.outputs.push_back(rct::commit(amount, masks[index++]));
    for (const uint64_t amount : burns)
      balance.burns.push_back(rct::commit(amount, masks[index++]));
    std::vector<uint64_t> amounts = outputs;
    amounts.insert(amounts.end(), burns.begin(), burns.end());
    balance.range_proofs.push_back(rct::bulletproof_plus_PROVE(amounts, masks));
    return balance;
  }

  cryptonote::assets::asset_ownership_proof make_ownership_proof(
    const crypto::hash& id, const crypto::hash& carrier, rct::key* generated_pseudo_mask = nullptr)
  {
    constexpr size_t real = 5;
    cryptonote::assets::asset_ownership_proof proof;
    proof.asset_id = id;
    rct::ctkeyV public_ring;
    rct::key spend_secret{}, input_mask{};
    const rct::key amount = rct::d2h(10);
    for (size_t index = 0; index < cryptonote::assets::CONFIDENTIAL_ASSET_RING_SIZE; ++index)
    {
      cryptonote::assets::asset_ring_member member;
      member.asset_id = id;
      member.output_id.data[0] = static_cast<unsigned char>(index + 1);
      rct::key ignored;
      rct::skpkGen(ignored, member.public_output.dest);
      rct::skpkGen(ignored, member.public_output.mask);
      proof.ring.push_back(member);
    }
    rct::skpkGen(spend_secret, proof.ring[real].public_output.dest);
    input_mask = rct::skGen();
    rct::addKeys2(proof.ring[real].public_output.mask, input_mask, amount, rct::H);
    for (const auto& member : proof.ring)
      public_ring.push_back(member.public_output);
    const rct::key pseudo_mask = rct::skGen();
    if (generated_pseudo_mask)
      *generated_pseudo_mask = pseudo_mask;
    rct::addKeys2(proof.pseudo_input, pseudo_mask, amount, rct::H);
    rct::key message;
    std::string error;
    if (!cryptonote::assets::derive_asset_ownership_message(proof, cryptonote::TESTNET, carrier, message, &error))
      throw std::runtime_error(error);
    rct::ctkey input_secret;
    input_secret.dest = spend_secret;
    input_secret.mask = input_mask;
    proof.signature = rct::proveRctCLSAGSimple(
      message, public_ring, input_secret, pseudo_mask, proof.pseudo_input,
      real, hw::get_device("default"));
    std::memcpy(&proof.key_image, &proof.signature.I, sizeof(proof.key_image));
    return proof;
  }
}

TEST(asset_confidential, verifies_private_transfer_and_explicit_burn)
{
  const crypto::hash id = asset_id(1);
  const auto balance = make_balance(id, 10, {7}, {3});
  std::string error;
  ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_balance(balance, &error)) << error;
  ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_transaction({balance}, {id}, boost::none, &error)) << error;
}

TEST(asset_confidential, rejects_inflation_and_commitment_substitution)
{
  const crypto::hash id = asset_id(2);
  std::string error;
  auto inflated = make_balance(id, 10, {11}, {});
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_balance(inflated, &error));
  auto substituted = make_balance(id, 10, {10}, {});
  substituted.outputs.front() = rct::commit(10, rct::skGen());
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_balance(substituted, &error));
  auto malformed = make_balance(id, 10, {10}, {});
  malformed.range_proofs.front().A.bytes[0] ^= 1;
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_balance(malformed, &error));
}

TEST(asset_confidential, rejects_cross_asset_and_duplicate_balance_domains)
{
  const crypto::hash first = asset_id(3);
  const crypto::hash second = asset_id(4);
  std::string error;
  auto crossed = make_balance(first, 9, {9}, {});
  crossed.pseudo_inputs.front().source_asset_id = second;
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_balance(crossed, &error));
  const auto valid = make_balance(first, 9, {9}, {});
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction({valid, valid}, {first}, boost::none, &error));
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction({valid}, {second}, boost::none, &error));
}

TEST(asset_confidential, validates_fixed_supply_issuance_commitment)
{
  crypto::public_key issuer{};
  crypto::secret_key secret{};
  crypto::generate_keys(issuer, secret);
  cryptonote::assets::issuance_descriptor descriptor;
  descriptor.network = cryptonote::TESTNET;
  descriptor.issuer_key = issuer;
  descriptor.atomic_supply = 10;
  descriptor.issuance_nonce.data[0] = 9;
  crypto::hash id{};
  ASSERT_TRUE(cryptonote::assets::derive_asset_id(descriptor, id));
  cryptonote::assets::confidential_asset_balance balance;
  balance.asset_id = id;
  balance.pseudo_inputs.push_back({id, rct::commit(10, rct::zero())});
  balance.outputs.push_back(rct::commit(10, rct::zero()));
  balance.range_proofs.push_back(rct::bulletproof_plus_PROVE(10, rct::zero()));
  std::string error;
  ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_transaction({balance}, {}, descriptor, &error)) << error;
  balance.pseudo_inputs.front().commitment = rct::commit(11, rct::zero());
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction({balance}, {}, descriptor, &error));
}

TEST(asset_confidential, verifies_domain_separated_clsag_ownership)
{
  const crypto::hash id = asset_id(6);
  crypto::hash carrier{};
  carrier.data[0] = 0x77;
  const auto proof = make_ownership_proof(id, carrier);
  std::string error;
  ASSERT_TRUE(cryptonote::assets::verify_asset_ownership_proof(
    proof, cryptonote::TESTNET, carrier, &error)) << error;

  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    proof, cryptonote::MAINNET, carrier, &error));
  crypto::hash other_carrier = carrier;
  other_carrier.data[1] = 1;
  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    proof, cryptonote::TESTNET, other_carrier, &error));
}

TEST(asset_confidential, rejects_clsag_ring_key_image_and_asset_tampering)
{
  const crypto::hash id = asset_id(7);
  crypto::hash carrier{};
  carrier.data[0] = 0x78;
  const auto original = make_ownership_proof(id, carrier);
  std::string error;

  auto changed_reference = original;
  changed_reference.ring[0].output_id.data[1] = 1;
  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    changed_reference, cryptonote::TESTNET, carrier, &error));
  auto wrong_asset = original;
  wrong_asset.ring[0].asset_id = asset_id(8);
  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    wrong_asset, cryptonote::TESTNET, carrier, &error));
  auto wrong_image = original;
  wrong_image.key_image.data[0] ^= 1;
  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    wrong_image, cryptonote::TESTNET, carrier, &error));
  auto duplicate = original;
  duplicate.ring[1].output_id = duplicate.ring[0].output_id;
  EXPECT_FALSE(cryptonote::assets::verify_asset_ownership_proof(
    duplicate, cryptonote::TESTNET, carrier, &error));
}

TEST(asset_confidential, binds_every_pseudo_input_to_one_unique_ownership_proof)
{
  const crypto::hash id = asset_id(9);
  crypto::hash carrier{};
  carrier.data[0] = 0x79;
  rct::key pseudo_mask;
  const auto ownership = make_ownership_proof(id, carrier, &pseudo_mask);
  cryptonote::assets::confidential_asset_balance balance;
  balance.asset_id = id;
  balance.pseudo_inputs.push_back({id, ownership.pseudo_input});
  balance.outputs.push_back(rct::commit(10, pseudo_mask));
  balance.range_proofs.push_back(rct::bulletproof_plus_PROVE(10, pseudo_mask));
  std::string error;
  ASSERT_TRUE(cryptonote::assets::verify_confidential_asset_transaction_with_ownership(
    {balance}, {ownership}, {id}, boost::none, cryptonote::TESTNET, carrier, &error)) << error;
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction_with_ownership(
    {balance}, {}, {id}, boost::none, cryptonote::TESTNET, carrier, &error));
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction_with_ownership(
    {balance}, {ownership, ownership}, {id}, boost::none, cryptonote::TESTNET, carrier, &error));

  auto duplicate_input = balance;
  duplicate_input.pseudo_inputs.push_back(duplicate_input.pseudo_inputs.front());
  duplicate_input.outputs.push_back(balance.outputs.front());
  duplicate_input.range_proofs.clear();
  duplicate_input.range_proofs.push_back(rct::bulletproof_plus_PROVE(
    std::vector<uint64_t>{10, 10}, rct::keyV{pseudo_mask, pseudo_mask}));
  EXPECT_FALSE(cryptonote::assets::verify_confidential_asset_transaction_with_ownership(
    {duplicate_input}, {ownership, ownership}, {id}, boost::none,
    cryptonote::TESTNET, carrier, &error));
}