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

Deterministic signatures according to RFC6979 #360

Merged
merged 1 commit into from
Apr 2, 2015

Conversation

oleganza
Copy link
Contributor

See #358 for discussion.

The test vectors are the same as in Trezor and CoreBitcoin implementations and cover both the raw nonce (k) and the complete DER-encoded signature (which must have canonical S).

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Thanks for the pull request. I'm going to do a quick review now for general things, but I plan to dig into the RFC a little later and do a very careful analysis since this is obviously extremely important to get right given signature issues can easily lead to stolen funds.

@@ -56,11 +56,7 @@ func (p *PrivateKey) ToECDSA() *ecdsa.PrivateKey {
// Sign wraps ecdsa.Sign to sign the provided hash (which should be the result
Copy link
Member

Choose a reason for hiding this comment

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

Please update the comment to reflect reality here.

@oleganza
Copy link
Contributor Author

@davecgh thanks, updated the code according to your remarks.

// - https://github.com/trezor/trezor-crypto/blob/9fea8f8ab377dc514e40c6fd1f7c89a74c1d8dc6/tests.c#L432-L453
// - https://github.com/oleganza/CoreBitcoin/blob/e93dd71207861b5bf044415db5fa72405e7d8fbc/CoreBitcoin/BTCKey%2BTests.m#L23-L49
verifyRFC6979Nonce(
"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
Copy link
Member

Choose a reason for hiding this comment

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

Because these are hard coded strings in the test source code, I'd prefer all of these use a function like the following decodeHex which panics if any errors occur parsing them rather than ignoring them.

// decodeHex converts the passed hex string into a big integer pointer and will
// panic is there is an error.  This is only provided for the hard-coded
// constants so errors in the source code can bet detected. It will only (and
// must only) be called for initialization purposes.
func decodeHex(hexStr string) []byte {
    hexBytes, err := hex.DecodeString(hexStr)
    if err != nil {
        panic("invalid hex in source file: " + hexStr)
    }
    return hexBytes
}

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Alright that's it for now. I'll do an in-depth review of the algorithm against the RFC later tonight.

@@ -396,3 +408,130 @@ func RecoverCompact(curve *KoblitzCurve, signature,

return key, ((signature[0] - 27) & 4) == 4, nil
}

// SignRFC6979 generates a deterministic ECDSA signature according to RFC 6979 and BIP 62.
func SignRFC6979(privateKey *PrivateKey, hash []byte) (*Signature, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Probably don't need to export this since it's scoped to the private key.

@oleganza
Copy link
Contributor Author

@davecgh do you know what's wrong with ./goclean.sh exiting with status 1 that makes Travis fail?

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Looks like it needs a gofmt on signature_test.go.

@oleganza
Copy link
Contributor Author

Thanks. The error was not very helpful. Lets see if it works.

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Yeah, I noticed that as well. I just made #361 to address that.

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Thanks for your work on this! It's looking good.

I imagine it's right given the tests pass, but as I said previously, I still plan to give the algorithm a through review later tonight after I finish up with some other things I'm working on.

@oleganza
Copy link
Contributor Author

Thanks for the comments, I appreciate your feedback.

@davecgh
Copy link
Member

davecgh commented Mar 31, 2015

Oh, now that we have deterministic signatures, I think it would be worth updating the SignMessage example in example_test.go. IIRC it has the print of the serialized signature commented out because it used to be random. With a deterministic signature, it can be printed and checked with the // Output: line.

EDIT: If you're unaware, go test actually runs the examples and compares the output to ensure the example code compiles and produces the output as documented.

s.Mod(s, N)

if s.Cmp(halforder) == 1 {
s = new(big.Int).Sub(order, s)
Copy link
Member

Choose a reason for hiding this comment

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

The extra allocation can be avoided here.

s.Sub(N, s)

That is s = N - s.

Also, the rest of the code here is using N for the order, probably should use N here too.

@davecgh
Copy link
Member

davecgh commented Apr 1, 2015

Alright, I've finished carefully comparing this against the RFC and everything looks accurate. I would like to point out a few things that can be improved in the future:

  • There are various allocations happening through the use of append that could be avoided by creating appropriately size byte slices and copying the relevant data into them.
  • The Sign function takes an already hashed message while the signing code assumes the hash function is sha256. This is a currently a valid assumption, however care will need to be taken should another hash function come into use.
  • This code assumes that the curve is secp256k1 while this package is intended to be able to work with any curve. There are a few other areas where this is the case however, so I don't think it would be fair to hold up this PR due to this.
  • There are several optimization opportunities where the conversions between big.Ints and []byte could be avoided.

All of that said, I see no reason any of those points should hold up this PR since it works properly and provides highly useful functionality.

@oleganza However, I would like to see the the other comments addressed.

@davecgh
Copy link
Member

davecgh commented Apr 1, 2015

@jimmysong Can you review and verify the algorithms conform to the RFC as well?

@jimmysong
Copy link
Contributor

@davecgh Looking at the RFC spec now. Will review the code soon.

@oleganza
Copy link
Contributor Author

oleganza commented Apr 1, 2015

@davecgh thanks! It's my first time I write in Go. Sorry for some obvious things like unnecessary allocations. Thanks for correcting those.

k := nonceRFC6979(privkey.D.Bytes(), hash)
inv := new(big.Int).ModInverse(k, N)
r, _ := privkey.Curve.ScalarBaseMult(k.Bytes())
r.Mod(r, N)
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny nit here, this is probably just a bit faster:

if r.cmp(N) == 1 {
    r.Sub(r, N)
}

@jimmysong
Copy link
Contributor

@oleganza @davecgh I've checked everything and made my comments. A couple of nits and the only major thing is step H2. But even there, it'll go through fine if qlen is a multiple of 8, which it is in secp256k1.

@davecgh
Copy link
Member

davecgh commented Apr 1, 2015

@jimmysong Thanks for taking the time to look it over!

@oleganza
Copy link
Contributor Author

oleganza commented Apr 1, 2015

Addressed all the remarks. Please double-check.

@oleganza
Copy link
Contributor Author

oleganza commented Apr 1, 2015

@davecgh @jimmysong Should I squash and rebase to make the merge neat?

@jimmysong
Copy link
Contributor

@oleganza I would check for s being zero, but that's about it.

@davecgh
Copy link
Member

davecgh commented Apr 1, 2015

@oleganza Yes, I'd like a single commit after the final OKs. Please hold off until then though as it's much easier to review the incremental changes during the review process that address items as separate commits.

Also, in the final commit, please add a closes #358 at the end so the associated issue is automatically closed.

if s.Cmp(halforder) == 1 {
s.Sub(N, s)
}
if r.Sign() == 0 {
Copy link
Member

Choose a reason for hiding this comment

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

s.Sign

@davecgh
Copy link
Member

davecgh commented Apr 1, 2015

All updates look good except the typo in commit 489d98e, which I commented on (s.Sign).

@@ -37,13 +37,14 @@ func Example_signMessage() {
//
Copy link
Member

Choose a reason for hiding this comment

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

This NOTE can be removed now since it's no longer true with you change.

@oleganza
Copy link
Contributor Author

oleganza commented Apr 2, 2015

Oops, thanks. Waiting for the final OK.

@davecgh
Copy link
Member

davecgh commented Apr 2, 2015

Looks good. OK.

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.

4 participants