-
Notifications
You must be signed in to change notification settings - Fork 239
Simplify transaction's constructor and EIP155 handling #153
Conversation
I just updated this PR with an implementation of this logic. I think it's better to validate To implement this I had to change the In doing the modification to |
Update: I fixed a bug when signing already signed txs. If it was previously signed without EIP155, |
Thanks for the super solid work and the great summary, I would generally go along with all changes proposed/implemented, just as some first estimation. 😄 |
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.
One change request on a comment, another question on v
setting, otherwise to be continued...
Woke up thinking that this change isn't right. Signing signed and unsigned transactions should have the same logic to decide whether to use EIP155 or not, but I see two ways of fixing this:
I'll make a commit with the second alternative, as the first one needs further discussion, and can be addressed as part of #151. UPDATE: Done. |
While testing this I realized that I didn't reimplement its behavior, as |
Would agree here, taking 2. should be a valid fix for now.
Ok. |
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.
@alcuadrado Ok, I think I can give this a go now, thanks for the great work! 😄
Please nevertheless answer the one outstanding question.
st.equal( | ||
pt.serialize().toString('hex'), | ||
'ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080', | ||
'ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080808080', |
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 likely don't have the complete picture yet, but I am not getting atm why the serialization now differs from the example from the EIP page linked above in the comment.
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, is this the RLP empty buffer case?
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.
Yes, it's because of that.
Note that what the EIP calls "signing data" isn't the output of serialize()
, it's the preimage of the hash returned by hash(false)
. We don't have a getter for such value in this class.
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 added an explanation about this in the test
@@ -137,7 +136,7 @@ tape('[FakeTransaction]: Basic functions', function(t) { | |||
}) | |||
|
|||
t.test('should sign', st => { | |||
const tx = new FakeTransaction(txData) | |||
const tx = new FakeTransaction(txData, { hardfork: 'tangerineWhistle' }) | |||
tx.sign(Buffer.from('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex')) | |||
st.plan(3) | |||
st.equal( |
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.
New test cases sufficiently cover the changed EIP-155 and v value setting behavior, good.
this._senderPubKey = ecrecover( | ||
msgHash, | ||
v, | ||
this.r, | ||
this.s, | ||
useChainIdWhileRecoveringPubKey ? this._chainId : undefined, | ||
useChainIdWhileRecoveringPubKey ? this.getChainId() : undefined, |
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.
All verifySignature()
changes unproblematic, ok.
const unsigned = !this.r.length && !this.s.length | ||
const seeksReplayProtection = this._chainId > 0 | ||
|
||
if ((unsigned && seeksReplayProtection) || (!unsigned && meetsAllEIP155Conditions)) { |
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 is the heaviest code logic change in the PR and I get that this upper part has been moved into this._implementsEIP155()
but I am not completely sure how to read this conditional simplification.
Could you give a short reasoning for the two cases in the if
clause? That has likely already been done implicitly in your other explanations on the overall changes, but just want to make sure we are not missing anything here.
Won't make this a blocker request though.
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.
Actually, they aren't completely equivalent. Not for unsigned transactions.
The decision to use EIP155 or not when signing was previously based on the _chainId
field, and its complex initialization logic in the constructor. It's simpler now: if the hardfork is spurious dragon or later, we use EIP155.
If a transaction is already signed and we are validating it, the logic is the same. It should be in spurious dragon or later and have an EIP155-encoded chain id as v
to enable EIP155 validation.
} | ||
|
||
Object.assign(this, sig) | ||
} |
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.
Changes in sign()
are ok.
@s1na @alcuadrado After this has been merged (@alcuadrado feel free to merge) I think we are in a good working state on the library. I would very much tend to do now do a major release on this, I think the changes already done are heavy enough and further refactoring/simplification work started and discussed by you guys is still a bit far off respectively shouldn't be overrushed and we should take our time here and do a subsequent likely major release on this once ready. Are you going along with this? |
Completely agree. I created #154 as a place to explore different ideas, not to be merged and released now. |
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.
Looks good, thanks for the explanations.
This PR implements the changes discussed in the TS migration PR and in fixes #149.
A summary of the changes:
data
object.opts.common
, oropts.chain
andopts.hardfork
can be used.'petersburg'
.common
object now. It isn't computed in the constructor.v
value is present, indicates that EIP155 was enabled, and the chain id it indicates doesn't match the one of the internalcommon
object.v
is set. If a transaction isn't signed, it would be an empty buffer.v
is changed after construction,and it doesn't match the one of the internalits value is validated in its setter.common
object, we throw a clear message intx.verifySignature()
instead of just letting it return false.I think (7) and (8) require some extra comments:
(7) This is different from the previous implementation, where by default EIP155 wasn't used. We'd need an extra param to implement that. That would also make the logic to decide when to use EIP155 would be more complex, and it's already fairly complex. IMO it's better to use EIP155 by default, giving the users replay-protection by default. This EIP has been active for a long time now.
(8)
Maybe this should be done in theUpdate: See next commentv
setter. What do you think? It would require a call toObject.defineProperty
in the constructor, as anything in the prototype is shadowed bydefineProperties
.