Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add secure minting #110

Draft
wants to merge 5 commits into
base: trunk
Choose a base branch
from
Draft

Add secure minting #110

wants to merge 5 commits into from

Conversation

davebryson
Copy link
Contributor

This PR addresses issue #98 adding support for secure minting. Mint transactions now run through the sentinel and validation. Minters are identified via pre-approved key pairs used during sentinel validation to determine who is allowed to execute a mint transaction. Public keys associated with approved minters are added to the cfg file.

For testing and demonstrations a pre-calculated minter key pair is available. Existing cfg files have been updated with the information.

A mint transaction is identified as having no inputs and 1 or more outputs. Validation has been adjusted to support this to ensure only valid minting transactions are forwarded from the sentinel. This required a change in the Atomizer as it will no longer reject a transaction that has no inputs (as it did previously).

PR #96 should be applied before this PR

@HalosGhost
Copy link
Collaborator

@davebryson looks like there's a small merge conflict (and some linting issues); but a general rebase is probably a good idea now that the macOS script updates have been merged in.

@davebryson
Copy link
Contributor Author

@HalosGhost Rebased. But still need to address cognitive complexity issue (clang-tidy) in client-cli.cpp

@davebryson
Copy link
Contributor Author

@HalosGhost clang-tidy is failing due to the number of if/else clauses in client-cli.cpp main() exceeding the default threshold of 25 (there are currently 26) for the readability-function-cognitive-complexity.Threshold setting.

Any objections to increasing the threshold setting in .clang-tidy to provide a little room for growth in the command line client?

@HalosGhost
Copy link
Collaborator

I think a longer-term solution would be to factor out a basic parsing mechanism so that we can have less of an if/else soup (ideally allowing use of a function-pointer map, or switch/case); but that's definitely a larger refactoring effort. I don't have any immediate problem raising the threshold. @metalicjames is there a middle-ground that you think we should pursue instead?

@metalicjames
Copy link
Member

I would prefer that we move the command dispatch code to its own function than increase the cognitive complexity limit.

Secure minting and small refactor of command line client

Signed-off-by: Dave Bryson <[email protected]>
Fix clang-tidy issue with return after if/else

Signed-off-by: Dave Bryson <[email protected]>
@davebryson
Copy link
Contributor Author

@HalosGhost Not sure what the issue is with the CI failing on unit/integration tests. Note commit 9fa789e above passed all tests. Last commit (that failed tests) made no changes that would effect tests. Additionally I've tested locally several times and all unit/integration tests are passing.

@HalosGhost
Copy link
Collaborator

Seems it's the atomizer end-to-end tests:

/home/runner/work/opencbdc-tx/opencbdc-tx/tests/integration/atomizer_end_to_end_test.cpp:58: Failure
Value of: m_ctl_watchtower->get_block_height() > 0
  Actual: false
Expected: true
scripts/test.sh: line 9:  8480 Aborted                 (core dumped) $PWD/$1

I will try to pull the changes and test locally today to see what's going wrong.

@HalosGhost
Copy link
Collaborator

@davebryson your suspicions were correct; timing issue during the run; rerunning it passed all checks in a breeze. I'll dig in and begin review in the next few days!

@HalosGhost HalosGhost self-requested a review June 3, 2022 21:30
Copy link
Collaborator

@HalosGhost HalosGhost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it has taken me a little while to review this. I think there's a lot of great work here (and a solid step towards having some rough means of secure minting).

I do have a few questions to make sure I'm understanding the flow right, and a few requests for changes that I've made inline.

Please feel free to ask for clarification!

src/uhs/atomizer/sentinel/controller.cpp Outdated Show resolved Hide resolved
src/uhs/atomizer/sentinel/controller.cpp Show resolved Hide resolved
src/uhs/atomizer/shard/shard.cpp Outdated Show resolved Hide resolved
src/uhs/client/client-cli.cpp Outdated Show resolved Hide resolved
src/uhs/transaction/validation.cpp Outdated Show resolved Hide resolved
src/uhs/transaction/wallet.cpp Outdated Show resolved Hide resolved
src/uhs/transaction/wallet.cpp Outdated Show resolved Hide resolved
tests/unit/validation_test.cpp Outdated Show resolved Hide resolved
tests/unit/validation_test.cpp Outdated Show resolved Hide resolved
@davebryson
Copy link
Contributor Author

@HalosGhost Latest commit addresses requested changes

Copy link
Collaborator

@HalosGhost HalosGhost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davebryson these changes look solid (sorry there was such a delay in-review). I have a small comment, and I'd like @metalicjames to take a quick look at it before merge, but I'm comfortable with merging.

@@ -237,6 +251,8 @@ namespace cbdc {
/// \brief Sends the given minting transaction to a service that will
/// accept and process it.
///
/// TODO: Remove. No longer needed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the point of leaving this just to minimize the change in this PR? (it's otherwise leaving specifically-dead code in the repo.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HalosGhost I primarily left it in the event someone objected to the changes and/or wanted the functionality for other reasons (testing?).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing occurs to me on why it would still be needed with the adopting of this (pretty simple even) minting mechanism, but perhaps @metalicjames will think otherwise (I'm open to hearing alternative opinions). :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're not using it, let's remove it.

src/uhs/transaction/wallet.cpp Outdated Show resolved Hide resolved
Copy link
Member

@metalicjames metalicjames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a general question. Can I make multiple mint transactions with this PR?

It seems that all mint transactions will be identical, resulting in the same outpoints for every mint TX, meaning I can actually only make one valid mint TX without overwriting (or re-adding!! previously used outputs). One can see how this could lead to replay attacks where a previously submitted transaction spending a mint output becomes valid again when its input(s) are recreated.

The signatures on the outputs are missing any kind of sequence number or nonce. Does that matter? Is there a replay attack vector on mint transactions without one?

if(!m_shard_network.send_to_one(ctx_pkt)) {
m_logger->error("Failed to send mint tx to shard");
}
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style point: prefer return; over else here to avoid the unnecessary condition nesting (which causes a cognitive complexity penalty).

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f56-avoid-unnecessary-condition-nesting

return cbdc::watchtower::tx_error{
tx.m_id,
cbdc::watchtower::tx_error_inputs_dne{{}}};
msg.m_tx = std::move(tx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need the sentinel attestations here?

m_attestations is not set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metalicjames This is related to an issue that I asked about early in the process of working on this. The Atomizer, unlike 2PC, actually requires inputs in the compact transaction. A mint transaction doesn't have any inputs. So making mint transactions work with the Atomized feels like a hack and deviates from the normal transaction flow. Any thoughts or insights you have on this would be appreciated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think the atomizer should be able to understand mint transactions. Otherwise, how would the mint transaction be included in a block and applied by the shards? If the transaction is included in a block, the atomizer should check the sentinel attestations. A valid mint transaction should still be validated by multiple sentinels and attested to, the same way as any other transaction. Otherwise, how can we be confident the mint transaction wasn't validated by a malicious sentinel, ignoring the authorized minter keys?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metalicjames A mint transaction flows through the sentinel and accumulates attestations the same as any other transaction. The controller checks the attestations before it forwards it to the shard: https://github.com/mit-dci/opencbdc-tx/blob/trunk/src/uhs/atomizer/shard/controller.cpp#L152. All the shard appears to do is collect the input index into the msg attestations set https://github.com/mit-dci/opencbdc-tx/blob/trunk/src/uhs/atomizer/shard/shard.cpp#L151. What concerns me more is the fact that atomizer allow this (at least passes all tests) even though the msg.m_attestations is empty for a mint transaction.

<< std::endl;

if(resp.has_value()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code seems duplicated from print_tx_result, can we refactor it?

@@ -237,6 +251,8 @@ namespace cbdc {
/// \brief Sends the given minting transaction to a service that will
/// accept and process it.
///
/// TODO: Remove. No longer needed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're not using it, let's remove it.

@@ -33,8 +33,13 @@ namespace cbdc::transaction::validation {
return std::tie(m_code, m_idx) == std::tie(rhs.m_code, rhs.m_idx);
}

auto check_tx(const cbdc::transaction::full_tx& tx)
auto check_tx(const cbdc::transaction::full_tx& tx,
const std::unordered_set<pubkey_t, hashing::null>& minters)
Copy link
Member

@metalicjames metalicjames Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use a more general container here (like a vector, array etc)? Requiring a std::unordered_set with a custom hasher seems too specific to me.

auto check_tx(const transaction::full_tx& tx,
const std::unordered_set<pubkey_t, hashing::null>& minters)
-> std::optional<tx_error>;
auto
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add doc comments for the new functions in this file?

const privkey_t seckey = m_keys.at(pubkey);

// Sign each output
for(size_t i = 0; i < n_outputs; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signing code seems duplicated with the code in wallet::sign, can we refactor it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the code is duplicate. As I was touching a lot of the code base, I didn't want to be too intrusive

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to refactor the duplicate code in this PR. That will make the code more maintainable in the long run. You can split the refactors into separate commits to reduce the review burden.

@@ -25,22 +25,55 @@ namespace cbdc {
auto transaction::wallet::mint_new_coins(const size_t n_outputs,
const uint32_t output_val)
-> transaction::full_tx {
transaction::full_tx ret;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to rename this variable?

return generate_key();
}
// first key is always the minter key
return m_pubkeys[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to hold the lock to access m_pubkeys here.

return m_pubkeys[0];
}

auto transaction::wallet::generate_test_minter_key() -> pubkey_t {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about this. Is the test minter key the one the validation code will check signed the TX? How is that different from the key generated when I can generate_minter_key()?

What if I call generate_minter_key() first, and then generate_test_minter_key() after? Now m_pubkeys[1] contains the minter key. Same thing if I call generate_test_minter_key() multiple times.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generate_test_minter_key generates a pre-determined key that's preset in all existing .cfg files for testing and/or demos. Again, I was looking for a way to minimize changes across the codebase. See

/// Generate a fixed public key for a minter. This is
for a descriptoin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not read the key from the config file? Why do we need code to re-generate it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metalicjames Mainly because tests call wallet code to mint (especially the integration tests) and you need a deterministic key for that. Putting the minter's private key in the configuration file for testing would mean you should put all minter private keys in the configuration file (testing and non-testing) to keep the design consistent. But IMO, putting minter (or any) private keys in the configuration file seems like a really bad idea from a security perspective.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that this is not a production system, so we don't have to be quite as worried about such things at the moment.

For example, for sentinel attestation checks, the sentinel private keys are stored in the configuration files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HalosGhost Agree and I understand what you're saying. But on the other hand, adding security late to the process usually doesn't turn out well. If security is not a high-priority in the current context of the project, then is secure minting even necessary right now? Just thinking out loud on how to best bound the problem...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take your point. I suppose my view is that we should try to scope our questions as small as possible.

I think this PR is answering the question “How could secure minting be supported?” That doesn't require all other best-practices to be addressed at the same time. To that end, having the keys in-configuration or not is an orthogonal concern (in my opinion).

@davebryson
Copy link
Contributor Author

The signatures on the outputs are missing any kind of sequence number or nonce. Does that matter? Is there a replay attack vector on mint transactions without one?

@metalicjames Great question and I believe you are correct. Adding a nonce/sequence number would be an easy solution. Of course a sequence number would require some form of state known by the system for validation. But either way you have minters using the same key over and over. Policy could dictate a minter must rotate a key after each use, but this complicates configuration.

Signed-off-by: Dave Bryson <[email protected]>

Added minting private keys to the configuration file.  Removed older minting approach for mint keys.  Moving to draft pull request as there are still other issues to resolve
@davebryson davebryson marked this pull request as draft September 7, 2022 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants