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

Add missing tests to ECSDA #1248

Merged
merged 16 commits into from
Sep 26, 2018

Conversation

come-maiz
Copy link
Contributor

Requires #1243.
This is one step towards #1147

🚀 Description

Complete the test coverage for ECRecover:
cases when version is 27, 28 and invalid.

  • 📘 I've reviewed the OpenZeppelin Contributor Guidelines
  • ✅ I've added tests where applicable to test my new functionality.
  • 📖 I've made sure that my contracts are well-documented.
  • 🎨 I've run the JS/Solidity linters and fixed any issues (npm run lint:all:fix).

@come-maiz come-maiz changed the title Test/missing signature tests Add missing tests to ECRecovery Aug 29, 2018
@come-maiz come-maiz added kind:improvement tests Test suite and helpers. labels Aug 29, 2018
@nventuro
Copy link
Contributor

Can you rebase from master, now that #1243 has been merged? The diff is hard to read otherwise.

@come-maiz
Copy link
Contributor Author

@nventuro updated.

// The last two hex digits are the signature version.
// The only valid values are 0, 1, 27 and 28.
// eslint-disable-next-line max-len
const signature = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e002';
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this signature different from the others? Shouldn't just the version differ? If so, I'd abstract out the signature prefix, and add the suffix in each test.

But more importantly, why is the signature hardcoded and not generated in the test? :/

@@ -30,6 +30,33 @@ contract('ECRecovery', function ([_, anyone]) {
(await this.mock.recover(TEST_MESSAGE, signature)).should.equal(signer);
});

it('recover v27', async function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, these may read more clearly with a structure like the following one:

context with valid signature
  context with v27 signature
    it works
  context with v28 signature
    it works
  context with invalid version signature
    it reverts
context with invalid signature
  it reverts

(the test itself should probably be abstracted into a function to avoid repetition)

@frangio frangio added this to the v2.0 milestone Sep 24, 2018
@come-maiz
Copy link
Contributor Author

come-maiz commented Sep 26, 2018

@nventuro please take another look.
I will leave removing the hardcoded signature as an exercise for a future reader, because I couldn't find how to generate the v1 signature.

@frangio frangio changed the title Add missing tests to ECRecovery Add missing tests to ECSDA Sep 26, 2018
@@ -14,48 +14,101 @@ contract('ECDSA', function ([_, anyone]) {
this.mock = await ECDSAMock.new();
Copy link
Contributor

Choose a reason for hiding this comment

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

While we're at it, can you rename this to this.ecdsa or something more descriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

const signer = '0x2cc1166f6212628a0deef2b33befb2187d35b86c';
// eslint-disable-next-line max-len
const signatureWithoutVersion = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892';
context('with 00 as version value', function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add an empty line before this context

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

(await this.mock.recover(TEST_MESSAGE, signature)).should.equal(signer);
});
context('recover with valid signature', function () {
context('with v0 signature', function () {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we have a case for v0 (and v1) signatures, where the version is incorrect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. We check the version first, and if it's not 0/1/27/28, we return 0. It doesn't matter if the rest of the signature makes sense or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's an internal detail though 😛 I wanted to include that it inside this context, to make it clear that all signatures fail if the version is wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

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

alright. I kind-of thing that's a myth that you can (and should) keep your tests totally independent from the implementation. But I'll follow your advice here. One less magic number anyway.

// Signature generated outside ganache with method web3.eth.sign(signer, message)
const version = '00';
const signature = signatureWithoutVersion + version;
(await this.mock.recover(TEST_MESSAGE, signature)).should.equal(signer);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd create a function as follows:

async function shouldRecoverSigner() {
   (await this.mock.recover(TEST_MESSAGE, this.signatureWithoutVersion + this.version)).should.equal(this.signer);
}

then have each context assign version and friends in a beforeEach, and then simply do

it('works', async function () {
  await shouldRecoverSigner();
});

WDYT?

Copy link
Contributor Author

@come-maiz come-maiz Sep 26, 2018

Choose a reason for hiding this comment

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

I'm slightly against this. I think the duplication here makes every test easier to read independently. shouldRecoverSigner hides how recover works, and for tests I tend to value more the expressiveness of each one than the amount of code. I find this.ecdsa.recover(TEST_MESSAGE, signature)).should.equal(signer); nice and short.

But there are good arguments for simplifying with the function, I can make the change. Your call @nventuro.

Copy link
Contributor

@nventuro nventuro Sep 26, 2018

Choose a reason for hiding this comment

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

shouldRecoverSigner will be extremely close by for in-depth analysis, so I don't agree with it hiding the internals. IMO, the tests not sharing code seems to suggest they do different things, and I have to manually compare each line to realize they are actually all the same. @frangio wtb exec decision

Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

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

I've been trying to understand more about the v value and I've learnt that it's not a version number. It's a "recovery identifier" according to the yellow paper; something related to the parameters of the ECDSA curve. So 1) we might want to change everywhere that says "version" in these tests, but 2) I have no idea how to generate the different v values so the hardcoded signatures will have to do for now.

});
context('with 00 as version value', function () {
it('works', async function () {
// Signature generated outside ganache with method web3.eth.sign(signer, message)
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment seems like it should be moved above next to the declaration of signatureWithoutVersion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed, thanks for catching it.

@come-maiz
Copy link
Contributor Author

@frangio I think that research about v and refactoring everything to that new name once we understand what it is and how to get web3 to generate it should be a separate task.

@@ -9,7 +9,7 @@ require('chai')
const TEST_MESSAGE = web3.sha3('OpenZeppelin');
const WRONG_MESSAGE = web3.sha3('Nope');

contract('ECDSA', function ([_, anyone]) {
contract.only('ECDSA', function ([_, anyone]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Oops!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I hate this tool :)

@nventuro nventuro merged commit 75c0a59 into OpenZeppelin:master Sep 26, 2018
frangio pushed a commit that referenced this pull request Sep 28, 2018
* fix: refactor sign.js and related tests

* fix: remove unused dep

* fix: update package.json correctly

* Add missing tests to ECRecovery

* fix lint

* Reorganize the tests

* Reuse signature

* fix static errors

* Apply suggestions by @frangion and @nventuro

* Remove only

* More suggestions

* Remove unnecessary max-len

* remove only

(cherry picked from commit 75c0a59)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tests Test suite and helpers.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants