androlo / standard-contracts

Storage repo for Solidity contracts, tests, and docs.
MIT License
97 stars 26 forks source link

cannot reproduce secp256k1 signature integration tests #7

Open mutsys opened 6 years ago

mutsys commented 6 years ago

Hi there,

I am having some difficulty with the Secp256k1.validateSignature function. In order to debug this, I created a simple test contact:

contract Test {

    function validateSignature(bytes32 message, uint[2] signature, uint[2] publicKey) public pure returns (bool) {
        return Secp256k1.validateSignature(message, signature, publicKey);
    }

}

which I run from within a mocha test via truffle

const chai = require("chai");
const expect = chai.expect;

const Test = artifacts.require("Test");

contract("Test", accounts => {
    it("should validate a signature", done => {
        Test.deployed()
            .then(instance => {
                const message = ...;
                const signature = ...;
                const publicKey = ...;
                return instance.validateSignature.call(message, signature, publicKey);
            })
            .then(result => {
                expect(result).to.be.true;
                done();
            })
            .catch(error => {
                console.error(error);
                done(error);
            });
    });
});

The test succeeds as expected using any set of messages/signatures/publicKeys in your integration test data, however it fails 100% of the time when I try to use my own values for the arguments. The equivalent works just fine for me using the elliptic npm package.

const crypto = require("crypto");
const EC = require("elliptic").ec;
const ec = new EC("secp256k1");

const createKeyPair = () => {
    const keyPair = ec.genKeyPair();
    return {
        privateKey: keyPair.getPrivate("hex"),
        publicKey: keyPair.getPublic("hex")
    };
};

const message = "this is a message to sign";
const messageHash = crypto.createHash("sha256").update(message).digest("hex");
const keyPair = createKeyPair();
const signature = ec.sign(messageHash, keyPair.privateKey);
const verified = ec.verify(messageHash, signature, keyPair.publicKey, "hex");

But when I use this in the context of the test calling Secp256k1.validateSignature, it fails 100% of the time.


const chai = require("chai");
const expect = chai.expect;
const crypto = require("crypto");
const EC = require("elliptic").ec;
const Signature = require("elliptic/lib/elliptic/ec/signature");
const ec = new EC("secp256k1");

const Test = artifacts.require("Test");

const createKeyPair = () => {
    const keyPair = ec.genKeyPair();
    return {
        privateKey: keyPair.getPrivate("hex"),
        publicKey: keyPair.getPublic("hex")
    };
};

const signMessage = (message, privateKey) => {
    return ec.sign(message, privateKey).toDER("hex");
};

const parsePublicKey = (publicKeyBytesHex) => {
    const publicKey = ec.keyFromPublic(publicKeyBytesHex, "hex").getPublic();
    return {
        x: `0x${publicKey.x.toString(16)}`,
        y: `0x${publicKey.y.toString(16)}`
    };
}

const parseSignature = (signatureBytesHex) => {
    const signature = new Signature(signatureBytesHex, "hex");
    return {
        r: `0x${signature.r.toString(16)}`,
        s: `0x${signature.s.toString(16)}`
    };
};

const publicKeyToArray = (publicKey) => {
    const xy = parsePublicKey(publicKey);
    return [xy.x, xy.y];
};

const signatureToArray = (signature) => {
    const rs = parseSignature(signature);
    return [rs.r, rs.s];
};

const message = "this is a message to sign";
const messageHash = crypto.createHash("sha256").update(message).digest("hex");
const keyPair = createKeyPair();
const signature = signatureToArray(signMessage(messageHash, keyPair.privateKey));
const publicKey = publicKeyToArray(keyPair.publicKey);

contract("Test", accounts => {
    it("should validate a signature", done => {
        Test.deployed()
            .then(instance => {
                return instance.validateSignature.call("0x" + messageHash, signature, publicKey);
            })
            .then(result => {
                expect(result).to.be.true;
                done();
            })
            .catch(error => {
                console.error(error);
                done(error);
            });
    });
});

I'm kind of puzzled. This approach has worked for me consistently across a varierty of other languages and crypto implementations, just not here. I am guessing that I am not correctly creating the value that I am passing as the first argument of `Secp256k1.validateSignature`. Are you able to recognize any errors in my test?
danaki commented 6 years ago

Can confirm, can't verify signed messages, hence tests are working.