OpenCBDC Transaction Processor
Loading...
Searching...
No Matches
validation.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 MIT Digital Currency Initiative,
2// Federal Reserve Bank of Boston
3// Distributed under the MIT software license, see the accompanying
4// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6#include "validation.hpp"
7
8#include "transaction.hpp"
9
10#include <cassert>
11#include <memory>
12#include <secp256k1.h>
13#include <secp256k1_schnorrsig.h>
14#include <set>
15
17 static const auto secp_context
18 = std::unique_ptr<secp256k1_context,
19 decltype(&secp256k1_context_destroy)>(
20 secp256k1_context_create(SECP256K1_CONTEXT_VERIFY),
21 &secp256k1_context_destroy);
22
23 auto input_error::operator==(const input_error& rhs) const -> bool {
24 return std::tie(m_code, m_data_err, m_idx)
25 == std::tie(rhs.m_code, rhs.m_data_err, rhs.m_idx);
26 }
27
28 auto output_error::operator==(const output_error& rhs) const -> bool {
29 return std::tie(m_code, m_idx) == std::tie(rhs.m_code, rhs.m_idx);
30 }
31
32 auto witness_error::operator==(const witness_error& rhs) const -> bool {
33 return std::tie(m_code, m_idx) == std::tie(rhs.m_code, rhs.m_idx);
34 }
35
37 -> std::optional<tx_error> {
38 const auto structure_err = check_tx_structure(tx);
39 if(structure_err) {
40 return structure_err;
41 }
42
43 for(size_t idx = 0; idx < tx.m_inputs.size(); idx++) {
44 const auto& inp = tx.m_inputs[idx];
45 const auto input_err = check_input_structure(inp);
46 if(input_err) {
47 auto&& [code, data] = input_err.value();
48 return tx_error{input_error{code, data, idx}};
49 }
50 }
51
52 for(size_t idx = 0; idx < tx.m_outputs.size(); idx++) {
53 const auto& out = tx.m_outputs[idx];
54 const auto output_err = check_output_value(out);
55 if(output_err) {
56 return tx_error{output_error{output_err.value(), idx}};
57 }
58 }
59
60 const auto in_out_set_error = check_in_out_set(tx);
61 if(in_out_set_error) {
62 return in_out_set_error;
63 }
64
65 for(size_t idx = 0; idx < tx.m_witness.size(); idx++) {
66 const auto witness_err = check_witness(tx, idx);
67 if(witness_err) {
68 return tx_error{witness_error{witness_err.value(), idx}};
69 }
70 }
71
72 return std::nullopt;
73 }
74
76 -> std::optional<tx_error> {
77 const auto input_count_err = check_input_count(tx);
78 if(input_count_err) {
79 return input_count_err;
80 }
81
82 const auto output_count_err = check_output_count(tx);
83 if(output_count_err) {
84 return output_count_err;
85 }
86
87 const auto witness_count_err = check_witness_count(tx);
88 if(witness_count_err) {
89 return witness_count_err;
90 }
91
92 const auto input_set_err = check_input_set(tx);
93 if(input_set_err) {
94 return input_set_err;
95 }
96
97 return std::nullopt;
98 }
99
101 -> std::optional<
102 std::pair<input_error_code, std::optional<output_error_code>>> {
103 const auto data_err = check_output_value(inp.m_prevout_data);
104 if(data_err) {
105 return {{input_error_code::data_error, data_err}};
106 }
107
108 return std::nullopt;
109 }
110
112 -> std::optional<tx_error> {
113 uint64_t input_total{0};
114 for(const auto& inp : tx.m_inputs) {
115 if(input_total + inp.m_prevout_data.m_value <= input_total) {
117 }
118 input_total += inp.m_prevout_data.m_value;
119 }
120
121 uint64_t output_total{0};
122 for(const auto& out : tx.m_outputs) {
123 if(output_total + out.m_value <= output_total) {
125 }
126 output_total += out.m_value;
127 }
128
129 if(input_total != output_total) {
131 }
132
133 return std::nullopt;
134 }
135
136 // TODO: check input assumptions with flags for whether preconditions have
137 // already been checked.
138 auto check_witness(const cbdc::transaction::full_tx& tx, size_t idx)
139 -> std::optional<witness_error_code> {
140 const auto& witness_program = tx.m_witness[idx];
141 if(witness_program.empty()) {
143 }
144
145 // Note this is safe because we aready checked the format in the prior
146 // step
147 const auto witness_program_type
149 witness_program[0]);
150 switch(witness_program_type) {
152 return check_p2pk_witness(tx, idx);
153 default:
155 }
156 }
157
159 -> std::optional<witness_error_code> {
160 const auto witness_len_err = check_p2pk_witness_len(tx, idx);
161 if(witness_len_err) {
162 return witness_len_err;
163 }
164
165 const auto witness_commitment_err
167 if(witness_commitment_err) {
168 return witness_commitment_err;
169 }
170
171 const auto witness_sig_err = check_p2pk_witness_signature(tx, idx);
172 if(witness_sig_err) {
173 return witness_sig_err;
174 }
175
176 return std::nullopt;
177 }
178
180 size_t idx)
181 -> std::optional<witness_error_code> {
182 const auto& wit = tx.m_witness[idx];
183 if(wit.size() != p2pk_witness_len) {
185 }
186
187 return std::nullopt;
188 }
189
191 size_t idx)
192 -> std::optional<witness_error_code> {
193 const auto& wit = tx.m_witness[idx];
194 const auto witness_program_hash
195 = hash_data(wit.data(), p2pk_witness_prog_len);
196
197 const auto& witness_program_commitment
198 = tx.m_inputs[idx].m_prevout_data.m_witness_program_commitment;
199
200 if(witness_program_hash != witness_program_commitment) {
202 }
203
204 return std::nullopt;
205 }
206
208 size_t idx)
209 -> std::optional<witness_error_code> {
210 const auto& wit = tx.m_witness[idx];
211 secp256k1_xonly_pubkey pubkey{};
212
213 // TODO: use C++20 std::span to avoid pointer arithmetic in validation
214 // code
215 pubkey_t pubkey_arr{};
216 std::memcpy(pubkey_arr.data(),
217 &wit[sizeof(witness_program_type)],
218 sizeof(pubkey_arr));
219 if(secp256k1_xonly_pubkey_parse(secp_context.get(),
220 &pubkey,
221 pubkey_arr.data())
222 != 1) {
224 }
225
226 const auto sighash = cbdc::transaction::tx_id(tx);
227
228 std::array<unsigned char, sig_len> sig_arr{};
229 std::memcpy(sig_arr.data(),
230 &wit[p2pk_witness_prog_len],
231 sizeof(sig_arr));
232 if(secp256k1_schnorrsig_verify(secp_context.get(),
233 sig_arr.data(),
234 sighash.data(),
235 &pubkey)
236 != 1) {
238 }
239
240 return std::nullopt;
241 }
242
244 -> std::optional<tx_error> {
245 if(tx.m_inputs.empty()) {
247 }
248
249 return std::nullopt;
250 }
251
253 -> std::optional<tx_error> {
254 if(tx.m_outputs.empty()) {
256 }
257
258 return std::nullopt;
259 }
260
262 -> std::optional<tx_error> {
263 if(tx.m_inputs.size() != tx.m_witness.size()) {
265 }
266
267 return std::nullopt;
268 }
269
271 -> std::optional<tx_error> {
272 std::set<cbdc::transaction::out_point> inps;
273
274 for(size_t idx = 0; idx < tx.m_inputs.size(); idx++) {
275 const auto& inp = tx.m_inputs[idx];
276 const auto it = inps.find(inp.m_prevout);
277 if(it != inps.end()) {
279 std::nullopt,
280 idx}};
281 }
282 inps.insert(inp.m_prevout);
283 }
284
285 return std::nullopt;
286 }
287
289 -> std::optional<output_error_code> {
290 if(out.m_value < 1) {
292 }
293
294 return std::nullopt;
295 }
296
298 auto witness_program = to_vector(payee);
299 witness_program.insert(witness_program.begin(),
300 std::byte(witness_program_type::p2pk));
301
302 const auto wit_commit
303 = hash_data(witness_program.data(), witness_program.size());
304
305 return wit_commit;
306 }
307
309 -> std::string {
310 switch(err) {
312 return "No inputs";
314 return "No outputs";
316 return "More inputs than witnesses";
319 return "Input values do not equal output values";
321 return "Total value of inputs or outputs overflows a 64-bit "
322 "integer";
323 default:
324 return "Unknown error";
325 }
326 }
327
329 -> std::string {
330 switch(err) {
332 return "Prevout data error";
334 return "Duplicate outpoint";
335 default:
336 return "Unknown error";
337 }
338 }
339
341 -> std::string {
342 switch(err) {
344 return "Output has zero value";
345 default:
346 return "Unknown error";
347 }
348 }
349
351 -> std::string {
352 auto ret = "Input error (idx: " + std::to_string(err.m_idx)
353 + "): " + to_string(err.m_code);
354 if(err.m_data_err.has_value()) {
355 ret += ", Data error: " + to_string(err.m_data_err.value());
356 }
357 return ret;
358 }
359
361 -> std::string {
362 switch(err) {
364 return "Incorrect witness data length";
367 return "Witness missing script type";
370 return "Witness commitment does not match witness program";
373 return "Witness signature is invalid";
376 return "Witness public key is invalid";
379 return "Witness contains an unknown script type";
380 default:
381 return "Unknown error";
382 }
383 }
384
385 auto to_string(const witness_error& err) -> std::string {
386 return "Witness error (idx: " + std::to_string(err.m_idx)
387 + "): " + to_string(err.m_code);
388 }
389
390 auto to_string(const output_error& err) -> std::string {
391 return "Output error (idx: " + std::to_string(err.m_idx)
392 + "): " + to_string(err.m_code);
393 }
394
395 auto to_string(const tx_error& err) -> std::string {
396 auto str
397 = std::visit(overloaded{[](const tx_error_code& e) {
398 return "TX error: " + to_string(e);
399 },
400 [](const auto& e) {
401 return to_string(e);
402 }},
403 err);
404 return str;
405 }
406
408 const transaction::compact_tx& tx,
409 const std::unordered_set<pubkey_t, hashing::null>& pubkeys,
410 size_t threshold) -> bool {
411 if(tx.m_attestations.size() < threshold) {
412 return false;
413 }
414
415 return std::all_of(tx.m_attestations.begin(),
416 tx.m_attestations.end(),
417 [&](const auto& att) {
418 return pubkeys.find(att.first) != pubkeys.end()
419 && tx.verify(secp_context.get(), att);
420 });
421 }
422}
struct secp256k1_context_struct secp256k1_context
Definition keys.hpp:14
auto check_input_structure(const cbdc::transaction::input &inp) -> std::optional< std::pair< input_error_code, std::optional< output_error_code > > >
auto check_p2pk_witness_commitment(const cbdc::transaction::full_tx &tx, size_t idx) -> std::optional< witness_error_code >
auto check_input_set(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
auto get_p2pk_witness_commitment(const pubkey_t &payee) -> hash_t
std:: variant< input_error, output_error, witness_error, tx_error_code > tx_error
An error that may occur when sentinels or clients statically validate a transaction.
auto to_string(cbdc::transaction::validation::tx_error_code err) -> std::string
auto check_tx(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
Runs static validation checks on the given transaction.
auto check_attestations(const transaction::compact_tx &tx, const std::unordered_set< pubkey_t, hashing::null > &pubkeys, size_t threshold) -> bool
Validates the sentinel attestations attached to a compact transaction.
auto check_output_count(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
auto check_witness_count(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
tx_error_code
Types of errors that may occur when a sentinel statically validates a transaction.
@ missing_witness
The number of witnesses and inputs do not match.
@ value_overflow
The total value of inputs/outputs overflows a 64-bit integer.
@ asymmetric_values
The total values of inputs and outputs do not match.
auto check_p2pk_witness(const cbdc::transaction::full_tx &tx, size_t idx) -> std::optional< witness_error_code >
auto check_output_value(const cbdc::transaction::output &out) -> std::optional< output_error_code >
auto check_input_count(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
witness_error_code
Types of errors that may occur when sentinels validate witness commitments.
@ invalid_signature
The witness's signature is invalid.
@ malformed
The witness's format appears invalid.
@ unknown_witness_program_type
The validation system does not recognize the provided witness_program_type.
@ invalid_public_key
The witness's public key is invalid.
@ program_mismatch
The witness's specified program doesn't match its commitment.
@ missing_witness_program_type
The witness did not provide a witness_program_type.
witness_program_type
Specifies how validators should interpret the witness program.
output_error_code
A transaction input validation error.
auto check_tx_structure(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
auto check_p2pk_witness_signature(const cbdc::transaction::full_tx &tx, size_t idx) -> std::optional< witness_error_code >
auto check_p2pk_witness_len(const cbdc::transaction::full_tx &tx, size_t idx) -> std::optional< witness_error_code >
auto check_witness(const cbdc::transaction::full_tx &tx, size_t idx) -> std::optional< witness_error_code >
input_error_code
Types of input validation errors.
@ duplicate
More than one transaction input contains the same output.
@ data_error
A transaction input includes invalid output data.
auto check_in_out_set(const cbdc::transaction::full_tx &tx) -> std::optional< tx_error >
auto tx_id(const full_tx &tx) noexcept -> hash_t
Calculates the unique hash of a full transaction.
std::array< unsigned char, cbdc::hash_size > hash_t
SHA256 hash container.
auto to_vector(const std::array< unsigned char, S > &arr) -> std::vector< std::byte >
Converts an std::array into an std::vector of the same size via copy.
Definition keys.hpp:42
auto hash_data(const std::byte *data, size_t len) -> hash_t
Calculates the SHA256 hash of the specified data.
std::array< unsigned char, pubkey_len > pubkey_t
A public key of a public/private keypair.
Definition keys.hpp:25
Variant handler template.
A condensed, hash-only transaction representation.
A complete transaction.
An input for a new transaction.
An output of a transaction.
An error that may occur when sentinels validate inputs.
auto operator==(const input_error &rhs) const -> bool
An error that may occur when sentinels validate transaction outputs.
auto operator==(const output_error &rhs) const -> bool
An error that may occur when sentinels validate witness commitments.
auto operator==(const witness_error &rhs) const -> bool