-
Notifications
You must be signed in to change notification settings - Fork 23
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
BIP-0062 #34
base: master
Are you sure you want to change the base?
BIP-0062 #34
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just those comments
} | ||
|
||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing eol
src/crypto/elliptic_common.cpp
Outdated
|
||
for( size_t i = 0; i < 31; ++i ) | ||
{ | ||
if( c.data[33 + i ] != n_2[i] ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really strongly recommend that we use a bignum library rather than perform bespoke bitwise arithmetic. Does steemd not have access to a bignum library (libgmp, etc.) that could do this heavy lifting? Alternatively, the secp256k1_fe
type may be suitable. The library definitely has a constant-time comparison (secp256k1_fe_equal
); but I don't know whether it implements >
.
Also, i'm not sure if it matters, but this is not a constant-time comparison. to do that you would want something like the following:
uint8_t greater = 0;
uint8_t less = 0;
uint8_t a, b;
for (size_t i = 1; i < 31; i++) {
a = c.data[33+i];
b = n_2[i];
greater |= (b - a) & ~less;
less |= (a - b) & ~greater;
}
return ((greater | ~less) >> 7) != 0 && c.data[64] <= n_2[31];
If you want to completely exclude timing leaks you'd need to rearrange the final compound conditional into something bitwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, i'm not sure if it matters, but this is not a constant-time comparison.
While you can make the argument that my algorithm is O(n) it is bounded about by n=31, same as yours. Mine can simply short circuit as soon as we see the first byte is < 0x7F
.
Any bignum library we use will require converting the signature to native endian for comparison. This algorithm skips all of that by doing a byte-wise comparison.
The naive, but correct solution would be this:
const static boost::uint256_t n_2 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0_cppui256;
boost::uint256_t sig = 0;
for( size_t = 0; i < 4; i++ )
{
sig << 32;
sig += boost::endian::little_to_native( *( uint64_t* )( c.data + 33 + ( i * 8 ) ) );
}
return sig <= n_2;
My preference would be readability. Even with thousands of extra signatures, the unit tests using the old canonical code only takes 2 more seconds to execute. As a percentage of our execution time, signature verification is not that long. I was previously told by Dan that it was significant overhead, but reindexing the blockchain with full signature verification maybe only added a minute of reindex time. At a 1 hour reindex time, the difference is almost not perceptible. We don't normally do signature verification during reindex. The big performance hit is while live and during a sync. Even then. undo states are the killer there and cause the order of magnitude slowdown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, as far as regards the comparison: the goal for me is not performance, it's security. Any comparison is always going to be O(n) in the length of the longer string. I was saying that your proposed implementation leaks timing information by short-circuiting. Not sure if that's even relevant here; just wanted to point it out.
Any bignum library we use will require converting the signature to native endian for comparison
Yes. For a minimal performance penalty you would gain a lot in readability and safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@goldibex Updates have been pushed to use boost uint256_t
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@theoreticalbts should take a look, but lgtm
Are these test vectors from BIP62 itself? I would like an external review/sanity check on this before it's merged. |
According to here it says BIP-0062 is "withdrawn," is that concerning? |
I am not familiar with this syntax:
Where is this syntax documented? Is it compatible with gcc? I can't find any information about it in Google. I suggest avoiding this syntax and initializing this value from an array of some built-in integer type. |
OK, now I have issue with the initialization of
Where is the of All we have is:
|
I did some more reading of the code. In the openssl version of the code here it does:
This seems fishy, first of all because Anyway, if we combine with the DER encoding reference conveniently located in BIP 0062, we can get this reference for the offsets:
So this explains 64 out of 65 bytes are 32-byte It is initialized here to
Questions (b)-(c) don't seem to be answerable unless we can get more information on the exact specification of
Turning now to question (a), we can see that This code raises a lot of questions. First of all, several ignored return values and the comment on this line do not exactly inspire confidence. Looking at the openssl docs the function From a wider perspective, there are plenty of questions as well. Why might the key recovery sometimes fail? Why does it try four values 0...3? Why not one value, or five values, or a thousand values? Why values 0..3? Would four other integer values like 137, 200, 355, 999 work just as well? It is not obvious from the sequence of abstract algebra in |
I am going to try to answer as many questions as I can. They aren't going in a single comment, so bear with me. |
It is a language extension of C++11. This specific use is documented by Boost here. It requires C++11, and gcc 4.7+ or clang 3.3+. All of the standards and versions are sufficiently old enough that I do not have a problem incorporating this syntax into the project. |
The documentation of
And
Our array is this stack exchange thread is the best I can find on the meaning of 27 + 4 + recovery id. Because we only use compact signatures, we offset the value by 31. |
I was unable to find specific BIP62 test vectors. I am assuming it is because BIP62 itself was withdrawn. The bounds on S were enforced as a softfork in a later PR. (Mentioned above)
100% agree |
@theoreticalbts Please note that our signatures are _not_ DER-encoded. They
use secp256k1's compact signature format (1-byte header, 32-byte r, 32-byte
s). Note that r and s are 32-digit big-endian, twos-complement, base-256
numbers. They are _not_ DER bitstrings. This was Michael's powerful
insight: the code of the current check is cargo-culted from BIP-0066. There
the code assumes r and s are DER-encoded bitstrings. That's the source of
the present pain.
And 1000% agree with sneak that we need an external review here.
…On Fri, Dec 29, 2017 at 8:16 AM Michael Vandeberg ***@***.***> wrote:
Are these test vectors from BIP62 itself?
I was unable to find specific BIP62 test vectors. I am assuming it is
because BIP62 itself was withdrawn. The bounds on S were enforced as a
softfork in a later PR. (Mentioned above)
I would like an external review/sanity check on this before it's merged.
100% agree
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#34 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAK7e63_TH8r8aOJE59ZzpKScvLevzQJks5tFRBhgaJpZM4RN7wk>
.
|
@@ -94,7 +94,7 @@ namespace fc { namespace ecc { | |||
do | |||
{ | |||
FC_ASSERT( secp256k1_ecdsa_sign_compact( detail::_get_context(), (unsigned char*) digest.data(), (unsigned char*) result.begin() + 1, (unsigned char*) my->_key.data(), extended_nonce_function, &counter, &recid )); | |||
} while( require_canonical && !public_key::is_canonical( result ) ); | |||
} while( !public_key::is_canonical( result, canon_type ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@goldibex What about loop it self, is it okay to generate signature in the loop? There is a function secp256k1_ecdsa_signature_normalize which should do it.
you can find more here: https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L461
Additionally, due to the sensitive and critical nature of this code, I believe we should refactor it with this change to be maximally readable/comprehendable whilst maximally avoiding footguns. I can weigh in further with specific recommendations if need be. |
FC Implementation for BIP-0062 signature canonization.
Discussion in steemit/steem#1944