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

accounts, core, crypto, internal: use canonical sigs internally #3455

Merged
merged 3 commits into from Jan 5, 2017

Conversation

karalabe
Copy link
Member

Ethereum uses the secp256k1 curve cryptography, which has it's own "canonical" signature representation. For the scope of this PR, we only care about the final byte (value of V) being 0 or 1 canonically. Both our crypto library as well as our AccountManager provide methods for generating these signatures.

However, Ethereum transactions themselves are not signed using the canonical representation, rather the yellow paper specifies adding 27 to the V value. This cause both crypto and AccountManager packages gaining additional methods SingEthereum and SignEthereumWithPassphrase. The problem is that anyone using our code as a library now needs to be aware of the crypto differences to use the methods properly.

This issue is further complicated by EIP158 which introduced chain IDs, which are yet another different schemes about how to transform the V value to do chain replay protections. Opposed to the plain and "Ethereum" style signatures, these are hidden from the user even at a library level. However now there's yet again a mental note people need to be aware of.


To address this increasing signature complexity, this PR discards all notion of "different" signature types at the library level. Both the crypto and accounts package is reduced to only be able to produce plain canonical secp256k1 signatures. This makes the crpyto APIs much cleaner, simpler and harder to abuse.

To handle the Ethereum yellow paper or Ethereum EIP158 style signature transforms, all the details have been moved out to the API edges and only executed when a particular signature format is needed. E.g. the Frontier and Homestead signers take a canonical signature and transform them into the canonical format, whereas the EIP158 signer takes again the canonical signature and transforms it according to our chain ID. This allows users to call a single Sign method and have it behave correctly irrelevant where the signature is used.

Similarly the RPC API needed the plain -> yellow papers transforms implemented there too.

@mention-bot
Copy link

@karalabe, thanks for your PR! By analyzing the history of the files in this pull request, we identified @bas-vk, @obscuren and @Gustav-Simonsson to be potential reviewers.

@karalabe karalabe added this to the 1.5.6 milestone Dec 16, 2016
@@ -1113,12 +1117,19 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
// Sign calculates an ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
// Note, the produced signature conforms to the Ethereum yellow paper spec where
// the V parameter must be 27 or 28 irrelevant of any ChainID features.
//
Copy link
Contributor

@fjl fjl Dec 16, 2016

Choose a reason for hiding this comment

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

Instead of using this sentence ("conforms to the Ethereum yellow paper spec...") please document that the format of the resulting signature is R, S, V where V is 0 or 1. There is no need to mention ChainID here.

// can be decrypted with the given passphrase. The produced signature is in the
// canonical secp256k1 format (V = 0 or 1), which can be transformed into the
// proper Ethereum signature accoring to the yellow paper within the transaction
// signer.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the part where it says how the signature could be transformed by the caller. Any caller can do any transformation on the result. "the transaction signer" is one of the callers, but I'm not sure that people reading the docs on godoc would know where/what that is.

Copy link
Contributor

@fjl fjl Dec 17, 2016

Choose a reason for hiding this comment

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

Maybe also add the remark about R, S, V format here. There isn't really a canonical format for secp256k1 signatures.

Copy link
Contributor

@fjl fjl Dec 17, 2016

Choose a reason for hiding this comment

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

Maybe we should add a section in the crypto package docs that explains the formats and how they relate to bitcoin signatures.

@@ -321,6 +324,9 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, message string, addr commo
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
// addr = ecrecover(hash, signature)
//
// Note, the signature must conform to the Ethereum yellow paper spec where the V
// parameter must be 27 or 28 irrelevant of any ChainID features.
Copy link
Contributor

@fjl fjl Dec 16, 2016

Choose a reason for hiding this comment

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

No need to mention ChainID. It's sufficient to say that the accepted format is R, S, V where V must be 27 or 28 for legacy reasons.

@@ -312,6 +314,7 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, message string, addr commo
if err != nil {
return "0x", err
}
signature[64] += 27 // SignWithPassphrase uses canonical secp256k1 signatures (v = 0 or 1), transform to yellow paper
Copy link
Contributor

Choose a reason for hiding this comment

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

comment could be shorter

@@ -181,7 +184,7 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool {
if s.Cmp(secp256k1.N) >= 0 {
return false
}
if r.Cmp(secp256k1.N) < 0 && (vint == 27 || vint == 28) {
if r.Cmp(secp256k1.N) < 0 && (vint == 0 || vint == 1) {
return true
} else {
return false
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be

return r.Cmp(secp256k1.N) < 0 && s.Cmp(secp256k1.N) < 0 && (v == 0 || v == 1)

Please move changes around ValidateSignatureValues into their own commit.

@karalabe karalabe force-pushed the only-use-canonical-signatures branch 2 times, most recently from 1191c79 to 2b120bd Compare December 19, 2016 09:18
@karalabe
Copy link
Member Author

@fjl I've fixed up all issues in the first commit and also moved anything unrelated to ValidateSignatureValues into there. The second commit remained only the validation updates. PTAL

@karalabe karalabe force-pushed the only-use-canonical-signatures branch from 2b120bd to ed16403 Compare December 19, 2016 10:03
@fjl
Copy link
Contributor

fjl commented Dec 19, 2016

It looks better now, but I still stand by my comment that there is no such thing as "canonical secp256k1 format".

The format of a signature is independent of the curve. An ECDSA signature is a tuple of two numbers, R and S. For secp25k1 signatures, the additional V value is required to recover the correct public key. It is not required for plain signature verification where the expected public key is known.

The signature format we use everywhere is just R, S, V concatenated. This format is not standard or canonical, it's just what we use. Signatures are usually encoded using ASN.1 DER, but we don't do that. Bitcoin has a 'compact' signature format, which is V, R, S with V + 27 for arbitrary reasons.

So again, please remove the phrase "canonical secp256k1 format" from comments. If you want to document the format, you can say "R, S, V format where V is 0 or 1". That's unambiguous and everyone
will know what you mean. You can also define the format somewhere (e.g. package crypto docs) and then refer to it.

@karalabe
Copy link
Member Author

PTAL. No, I'm not going to flatten that last commit into the first two because it would need to split up in between the two commits to avoid rebase conflicts.

@karalabe karalabe force-pushed the only-use-canonical-signatures branch from 482377c to e1db48e Compare January 4, 2017 12:30
@fjl fjl merged commit 08eea0f into ethereum:master Jan 5, 2017
@fjl fjl removed the review label Jan 5, 2017
farazdagi pushed a commit to status-im/go-ethereum that referenced this pull request Feb 22, 2017
…andling (ethereum#3455)

To address increasing complexity in code that handles signatures, this PR
discards all notion of "different" signature types at the library level. Both
the crypto and accounts package is reduced to only be able to produce plain
canonical secp256k1 signatures. This makes the crpyto APIs much cleaner,
simpler and harder to abuse.
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.

None yet

3 participants