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

Protocol Amendment: Always Require Fully-Canonical Signatures #3256

Closed
wants to merge 8 commits into from
Closed

Protocol Amendment: Always Require Fully-Canonical Signatures #3256

wants to merge 8 commits into from

Conversation

movitto
Copy link
Contributor

@movitto movitto commented Feb 11, 2020

Addresses #3042 by adding an amendment which if enabled would require full-canonical signatures regardless of whether or not the tfFullyCanonicalSig flag is set. Existing functionality will be preserved if amendment is not enabled.

@movitto
Copy link
Contributor Author

movitto commented Feb 11, 2020

Incidentally, first #XRPCommunity amendment?

@codecov-io
Copy link

codecov-io commented Feb 11, 2020

Codecov Report

Merging #3256 into develop will decrease coverage by 0.2%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #3256      +/-   ##
===========================================
- Coverage    70.33%   70.12%   -0.21%     
===========================================
  Files          675      674       -1     
  Lines        53178    53612     +434     
===========================================
+ Hits         37402    37598     +196     
- Misses       15776    16014     +238
Impacted Files Coverage Δ
src/ripple/protocol/Feature.h 100% <ø> (ø) ⬆️
src/ripple/protocol/STTx.h 64.7% <ø> (ø) ⬆️
src/ripple/protocol/impl/Feature.cpp 91.66% <ø> (ø) ⬆️
src/ripple/app/tx/impl/apply.cpp 81.66% <100%> (+0.63%) ⬆️
src/ripple/protocol/impl/STTx.cpp 83.91% <100%> (+0.21%) ⬆️
src/ripple/app/misc/impl/ValidatorList.cpp 78.05% <0%> (-11.14%) ⬇️
src/ripple/basics/impl/FileUtilities.cpp 69.23% <0%> (-7.24%) ⬇️
src/ripple/basics/impl/StringUtilities.cpp 89.18% <0%> (-3.92%) ⬇️
src/ripple/peerfinder/impl/PeerfinderManager.cpp 53.93% <0%> (-1.63%) ⬇️
src/ripple/peerfinder/impl/Counts.h 52.74% <0%> (-1.19%) ⬇️
... and 42 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cc4cefa...aedf91a. Read the comment docs.

@nbougalis nbougalis requested review from scottschurr, HowardHinnant and a user February 12, 2020 03:25
Copy link
Contributor

@nbougalis nbougalis left a comment

Choose a reason for hiding this comment

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

Lean and mean. I like this. But it needs more thorough unit tests.

@@ -186,7 +187,7 @@ std::pair<bool, std::string> STTx::checkSign() const
// at the SigningPubKey. It it's empty we must be
// multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
ret = signingPubKey.empty () ? checkMultiSign () : checkSingleSign ();
ret = signingPubKey.empty () ? checkMultiSign (rules) : checkSingleSign (rules);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that for these two private member functions, it's a better idea to pass an actual bool instead of the actual Rules object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@MarkusTeufelberger
Copy link
Collaborator

Incidentally, first #XRPCommunity amendment?

Only if you consider Ripple not as a part of the community. ;-)

@scottschurr
Copy link
Collaborator

The non-unity CI builds are failing. It looks like, at a minimum, STTx.cpp is missing an include of Feature.h. Here the the output from the one of the non-unity builds:

../../src/ripple/protocol/impl/STTx.cpp:265:89: error: 'featureRequireFullyCanonicalSig' was not declared in this scope
         bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || rules.enabled(featureRequireFullyCanonicalSig);
                                                                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../src/ripple/protocol/impl/STTx.cpp:265:89: note: suggested alternative: 'tfFullyCanonicalSig'
         bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || rules.enabled(featureRequireFullyCanonicalSig);
                                                                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                                                         tfFullyCanonicalSig
../../src/ripple/protocol/impl/STTx.cpp: In member function 'std::pair<bool, std::__cxx11::basic_string<char> > ripple::STTx::checkMultiSign(const ripple::Rules&) const':
../../src/ripple/protocol/impl/STTx.cpp:318:85: error: 'featureRequireFullyCanonicalSig' was not declared in this scope
     bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || rules.enabled(featureRequireFullyCanonicalSig);
                                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../src/ripple/protocol/impl/STTx.cpp:318:85: note: suggested alternative: 'tfFullyCanonicalSig'
     bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) || rules.enabled(featureRequireFullyCanonicalSig);
                                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                                                     tfFullyCanonicalSig

@movitto
Copy link
Contributor Author

movitto commented Feb 13, 2020

Lean and mean. I like this. But it needs more thorough unit tests.

@nbougalis could you advise as to how to generate non-canonical signatures for testing purposes? Can ripple-lib be modified to do so?

@movitto
Copy link
Contributor Author

movitto commented Feb 13, 2020

Incidentally, first #XRPCommunity amendment?

Only if you consider Ripple not as a part of the community. ;-)

D'oh! Should've said first non-ripple #XRPCommunity ammendment! 🤦‍♂️

@movitto
Copy link
Contributor Author

movitto commented Feb 13, 2020

The non-unity CI builds are failing. It looks like, at a minimum, STTx.cpp is missing an include of Feature.h. Here the the output from the one of the non-unity builds:

Pushed a followon patch fixing the unity build

@intelliot
Copy link
Collaborator

I don't think ripple-lib can generate non-canonical signatures. It may be possible to modify it to do so, but I'm not knowledgeable about how to do that. Searches turned up the following resources, in case they are helpful. I imagine @nbougalis knows more.

HowardHinnant
HowardHinnant previously approved these changes Feb 19, 2020
ghost
ghost previously approved these changes Feb 20, 2020
@scottschurr
Copy link
Collaborator

First and foremost, thanks for doing this! I think you've made a great change. This is yet another case where you're doing something that we should have taken care of years ago. I appreciate it.

That said, there are a couple of changes that I think need to be made. They are:

  1. Introducing Rules into STTx breaks levelization. I wouldn't have noticed, but fortunately it caused the validator-keys CI build to break. I think a good way to fix this would be to check the Rules in apply.cpp before the call into STTx. Then pass a flag into STTx, rather than passing all of the Rules. You can find an example of how I'd approach that problem here: scottschurr@ff6aa77. You're welcome to cherry-pick that, but you can also implement it yourself without much trouble.

  2. The harder problem is that we need a unit test to verify the functionality that you're putting in. This is tricky, since you need a non-fully-canonical transaction signature and none of our tools currently produce that (as far as I know).

    I don't think all is lost, however. I talked with @nbougalis and possibly, with some multi precision math, you can turn a fully-canonical signature into a valid non-fully-canonical signature. If you're interested in making a run at that, here are a couple of resources to checkout:

I haven't actually tried turning a fully-canonical signature into a non-fully-canonical signature using this technique. So I can't guarantee that it will work. Also, if you feel like figuring this out is above and beyond the call of duty then let me know and I'll try to put together a couple of unit tests.

I think you have a good change here. It's very much worth my time to help you get this change into the code base.

Let me know if you want any help. And thanks again.

@movitto movitto dismissed stale reviews from ghost and HowardHinnant via 61633f4 February 21, 2020 20:01
@movitto
Copy link
Contributor Author

movitto commented Feb 21, 2020

@nbougalis @scottschurr OK I was able to generate a TX w/ a non-fully-canonical signature (also w/out the tfFullyCanonicalSig flag). I pushed a follow on patch adding a test case w/ that, verifying the transaction fails to be verified when the amendment is enabled, and succeeds when that amendment is disabled.

Also cherry picked @scottschurr's patch introducing the new "RequireFullyCanonicalSig" enum and added a followon patch updating the test case to use that.

Let me know if there is anything else!

Copy link
Collaborator

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

Hi @movitto. I really appreciate you adding the uint test. And it's so close. But it doesn't actually test the change that you're adding. What you're adding is an amendment that, before it's passed, will allow a non-fully-canonically-signed transaction to slip through the system. Once the amendment is passed the non-fully-canonically-signed transaction should no longer be accepted.

Your unit test doesn't actually exercise the amendment itself. That's what I think we really want. Does that distinction make sense? If not let me know and I'll see if I can explain it better.

Thanks again for your work on this change. Once the unit test is in order I think this pull request will be good to go.

{
// Construct a payments w/out a fully-canonical tx
const std::string non_fully_canonical_tx =
"12000022000000002400000001201B00497D9C6140000000000F695068400000000000000C732103767C7B2C13AD90050A4263745E4BAB2B975417FA22E87780E1506DDAF21139BE74483046022100E95670988A34C4DB0FA73A8BFD6383872AF438C147A62BC8387406298C3EADC1022100A7DC80508ED5A4750705C702A81CBF9D2C2DC3AFEDBED37BBCCD97BC8C40E08F8114E25A26437D923EEF4D6D815DF93368B62E6440848314BB85996936E4F595287774684DC2AC6266024BEF";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: This is a really long line. You can preserve the string and still limit the line(s) to 80 characters. The compiler concatenates consecutive string literals that are not separated by commas. So you can build the string literal like this:

        // Construct a payments w/out a fully-canonical tx
        const std::string non_fully_canonical_tx =
            "12000022000000002400000001201B00497D9C6140000000000F6950684000000"
            "00000000C732103767C7B2C13AD90050A4263745E4BAB2B975417FA22E87780E1"
            "506DDAF21139BE74483046022100E95670988A34C4DB0FA73A8BFD6383872AF43"
            "8C147A62BC8387406298C3EADC1022100A7DC80508ED5A4750705C702A81CBF9D"
            "2C2DC3AFEDBED37BBCCD97BC8C40E08F8114E25A26437D923EEF4D6D815DF9336"
            "8B62E6440848314BB85996936E4F595287774684DC2AC6266024BEF";

Note that the example I'm giving indents the string literal by four spaces, not two, since this is an indent-by-four-spaces code base.

{
bool valid = tx.checkSign(STTx::RequireFullyCanonicalSig::no).first;
if(!valid)
fail("Non-Fully canoncial signature was not permitted");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: this and the following fail should be indented by four, not two, spaces.

@movitto
Copy link
Contributor Author

movitto commented Feb 22, 2020

@scottschurr pushed a patch testing the acceptability of non-canonical txs via the enablement of the amendment vs the state of the boolean in the internal helpers. Also fixes the style issues you pointed out. I believe this addresses the items you pointed out, let me know if otherwise

scottschurr
scottschurr previously approved these changes Feb 24, 2020
Copy link
Collaborator

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

👍 Looks great. Thanks for your persistence with the unit test.

@nbougalis, are you good with the unit test? If so, then I think this is good to go.

HowardHinnant
HowardHinnant previously approved these changes Feb 24, 2020
nbougalis
nbougalis previously approved these changes Feb 26, 2020
Copy link
Contributor

@nbougalis nbougalis left a comment

Choose a reason for hiding this comment

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

Looks good, thanks. Left two micro-nits that I'd like to see addressed.

Comment on lines 50 to 55
STTx::RequireFullyCanonicalSig const requireCanonicalSig =
rules.enabled(featureRequireFullyCanonicalSig) ?
STTx::RequireFullyCanonicalSig::yes :
STTx::RequireFullyCanonicalSig::no;

auto const sigVerify = tx.checkSign(requireCanonicalSig);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer: auto const requireCanonicalSig = ... whatever ... instead of STTx::RequireFullyCanonicalSig const requireCanonicalSig = ... whatever ....

Rationale: the precise type of the variable here is irrelevant and doesn't add much. This is, ultimately, a bool with some type-safety and user-friendliness sprinked on it, and a programmer can deduce the actual type by examining the ternary.

bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig);
bool const fullyCanonical =
(getFlags() & tfFullyCanonicalSig) ||
(requireCanonicalSig != RequireFullyCanonicalSig::no);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer requireCanonicalSig == RequireFullyCanonicalSig::yes here (and below in checkMultiSign). It makes it easier to read and inverting the check just makes it harder (at least for me) to reason about this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I hear the argument. Personally, I picked != no because I know that it's possible for a bool to have values other than 0 and 1. So in case there's something unexpected in the bool it will set fullyCanonical == true. Using == yes doesn't give that guarantee.

But I don't feel strongly about it. The case is pretty unlikely.

@movitto
Copy link
Contributor Author

movitto commented Feb 27, 2020

@nbougalis pushed a followup patch incorporating your feedback

Copy link
Collaborator

@scottschurr scottschurr left a comment

Choose a reason for hiding this comment

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

👍

@ghost
Copy link

ghost commented Feb 29, 2020

Linux build OK after rebasing on 1.5.0-b5, unit tests passed.

@carlhua carlhua mentioned this pull request Mar 5, 2020
@carlhua carlhua closed this in #3283 Mar 6, 2020
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.

7 participants