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
Conversation
@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. |
@@ -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. | |||
// |
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.
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. |
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.
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.
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.
Maybe also add the remark about R, S, V
format here. There isn't really a canonical format for secp256k1 signatures.
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.
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. |
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.
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 |
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.
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 |
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.
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.
1191c79
to
2b120bd
Compare
@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 |
2b120bd
to
ed16403
Compare
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, The signature format we use everywhere is just 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 |
ed16403
to
6eca171
Compare
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. |
482377c
to
e1db48e
Compare
…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.
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 ofV
) being0
or1
canonically. Both ourcrypto
library as well as ourAccountManager
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 theV
value. This cause bothcrypto
andAccountManager
packages gaining additional methodsSingEthereum
andSignEthereumWithPassphrase
. 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
andaccounts
package is reduced to only be able to produce plain canonicalsecp256k1
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.