-
Notifications
You must be signed in to change notification settings - Fork 19
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
ECDSA with Public Key Recovery #43
Conversation
From https://crypto.stackexchange.com/questions/18105/how-does-recovering-the-public-key-from-an-ecdsa-signature-work and https://www.secg.org/sec1-v2.pdf#subsubsection.4.1.6 it seems like for curves with I have replaced the |
Oh boy, it looks like I bit off more than I can chew. Apparently, parity is not so simple for curves over binary fields. In this context, point negation is defined as: pointNegate CurveF2m{} (Point x y) = Point x (x `addF2m` y) Which means that if We will probably need something like this: isPointOdd :: Curve -> Point -> Maybe Bool
isPointOdd _ PointO = Nothing
isPointOdd (CurveFP _) (Point _ y) = Just $ odd y
isPointOdd (CurveF2m (CurveBinary fx _)) (Point x y) = odd <$> divF2m fx y x Together with some reasoning that this will never go wrong in valid signature generation scenarios. |
Alright, I added:
I really only need (1) for my application, but (2) and (3) serve as verification and allow for automated round-trip tests. Indeed, I found several gaps in my understanding when trying to implement (2) and (3). That being said, if they are deemed too much or too risky, I would be okay only having (1) accepted in this PR. Considerations:
|
Revisiting the open questions from the original post:
As far as I am concerned, this PR could be merged, given appropriate review, since I am by no means an expert in cryptography and I do not want to introduce any vulnerabilities. |
@@ -209,3 +210,16 @@ divF2m | |||
-- ^ Quotient | |||
divF2m fx n1 n2 = mulF2m fx n1 <$> invF2m fx n2 | |||
{-# INLINE divF2m #-} | |||
|
|||
traceF2m :: BinaryPolynomial -> Integer -> Integer | |||
traceF2m fx = foldr addF2m 0 . take (log2 fx) . iterate (squareF2m fx) |
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 believe that a pair of take
and iterate
should be replaced with replicate
.
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.
Here we are defining a list of iterated squares:
[fx, squareF2m fx, squareF2m (squareF2m fx), squareF2m (squareF2m (squareF2m fx)), ...]
And then we take the sum (foldr addF2m 0
) of a prefix of that list. I do not know how to do this with replicate
, since that would just give us the last item of that prefix.
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.
My bad. I took it as repeat
.
Crypto/Number/F2m.hs
Outdated
{-# INLINE traceF2m #-} | ||
|
||
halfTraceF2m :: BinaryPolynomial -> Integer -> Integer | ||
halfTraceF2m fx = foldr addF2m 0 . take (1 + log2 fx `div` 2) . iterate (squareF2m fx . squareF2m fx) |
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 suggest to use `unsafeShiftR` 1
instead of div
.
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 can do that. I am assuming (!>>.)
would also be okay?
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.
It is not provided by old versions, sigh.
So, I recommend unsafeShiftR
.
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.
Ah, I see, that is unfortunate. I have changed it to use unsafeShiftR
.
Crypto/Number/F2m.hs
Outdated
|
||
-- | Solve a quadratic equation of the form @x^2 + x = a@ in F₂m. | ||
quadraticF2m :: BinaryPolynomial -> Integer -> Maybe Integer | ||
quadraticF2m fx a = if traceF2m fx a == 0 then Just $ halfTraceF2m fx a else Nothing |
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.
The following style is more readable to me:
quadraticF2m fx a
| traceF2m fx a == 0 = Just $ halfTraceF2m fx a
| otherwise = Nothing
I have confirmed that this PR does not give effect to my projects. 😀 |
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.
Now LGTM!
I'm sure that the reason why CI failed is not relating to this PR. |
Thank you for merging! As for the CI failure, I had the same errors when I tried to run the tests locally. I had to add a
I do not understand what |
Thanks. |
This is a minimal set of changes that would fix #41 for my use case.
The primary use case for public key recovery seems to be cryptocurrencies. I have tried my best to make this as general as possible without bringing in any specifics of that use case. For example, many cryptocurrencies use
secp256k1
, where it is nearly impossible for theoffset
flag to be set, so it is often not even calculated.I decided to go with an
ExtendedSignature
type rather than just a tuple. I also decided to represent the recovery information as twoBool
fields rather than one numeric field consisting of two packed bits. The advantage of having twoBool
fields are stronger types (guaranteeing exactly two bits of information), and easier manipulation of the individual bits (no bit operations likexor
are required). This will also be useful in a potential future PR for signature normalization.This is a draft PR for now since there are still several open questions:
signDigestWith
,signWith
,signDigest
,sign
in decreasing order of generality). The recovery information adds one more variant. If we want to have all options, this would amount to 2³ = 8 functions in total. I can easily do this, but I do not know if it is a good idea. In an ideal world, maybe it would be better to have a small helper function that takes care of the generation ofk
, as well as requiring users to do the transformation from message into digest. However, these would be breaking changes so it seems like we are stuck with many different versions of the same function. I do not know if we should have all 8 versions or only a subset thereof.Crypto.PubKey.ECDSA
module aswell?