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

Questions about ECDSA #201

Closed
xtools-at opened this issue Jan 13, 2023 · 2 comments
Closed

Questions about ECDSA #201

xtools-at opened this issue Jan 13, 2023 · 2 comments

Comments

@xtools-at
Copy link

xtools-at commented Jan 13, 2023

I'd like to sign a message and create a recoverable signature which can be used with EIP-191 and Ethereum's erecover / OpenZeppelin's ECDSA smart contracts to verify the signature's signer.

Is it somehow possible to use uECC_sign() to achieve this, or would this require a complete re-implementation? can the 65th bit be extracted along the process and inserted into the created signature, or are recoverable signatures following a different process when signing altogether?

as a reference, i've tried using the secpk256k1 lib too since it supports recoverable signatures ootb, but the result is still invalid when testing with OZ's ECDSA contract. In contrast, when signing the hash directly in my wallet (e.g. MEW), i get a valid signature that resolves to the signer's address.

#include "secp256k1.h"
#include "secp256k1_recovery.h"

// private key: 0xbdb51a16eb6461ec16f84a7b6f19e20d0b9aa558fa0e9ae4bb493ff779f14255
static const uint8_t privateKey[] = {
    0xbd, 0xb5, 0x1a, 0x16, 0xeb, 0x64, 0x61, 0xec,
    0x16, 0xf8, 0x4a, 0x7b, 0x6f, 0x19, 0xe2, 0x0d,
    0x0b, 0x9a, 0xa5, 0x58, 0xfa, 0x0e, 0x9a, 0xe4,
    0xbb, 0x49, 0x3f, 0xf7, 0x79, 0xf1, 0x42, 0x55
};
// keccak256 of "1234": 0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7
uint8_t hash[32] = {
  0x38, 0x7a, 0x82, 0x33, 0xc9, 0x6e, 0x1f, 0xc0,
  0xad, 0x5e, 0x28, 0x43, 0x53, 0x27, 0x61, 0x77,
  0xaf, 0x21, 0x86, 0xe7, 0xaf, 0xa8, 0x52, 0x96,
  0xf1, 0x06, 0x33, 0x6e, 0x37, 0x66, 0x69, 0xf7
};

secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
secp256k1_ecdsa_recoverable_signature sig;
uint8_t signature[65];
int recId;

secp256k1_ecdsa_sign_recoverable(ctx, &sig, hash, privateKey, NULL, NULL);
secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, signature, &recId, &sig);
// fix recovery bit for Solidity
signature[64] = recId == 0x00 ? 0x1b : 0x1c;

// expected signature: 65f333960946f37b57336199fd98826f4b334f70137f5f3a7ef2b2622007c6d77905672bd78dce53e37eb3ddef0c2219296db1795b466b5a89ffecda9d2106fc1c
// actual signature  : 2981840499f452d6dcef73808a4bee846a0d25559b6218f61024be3ae8f7e49908835dbd349c0971ef9ed2167f663ead4ab238fe65d3db10728cd7ddb417f69c1c

does anyone have any experience with this? any help or clues appreciated greatly!

@kmackay
Copy link
Owner

kmackay commented Jan 13, 2023

See the discussion here: #65 (comment)

@xtools-at
Copy link
Author

xtools-at commented Jan 13, 2023

ok so if understand correctly, the additional bit in recoverable signatures is just the recid appended to the whole thing - but then something else is wrong on my side too, since it just doesn't add up eventually.

when i use uECC with the same hash and privateKey as above, the output signature differs too from the expected one, and I can't recover the correct address using the ECDSA smart contract (I'm appending "1c" or "1b" as recIds for Ethereum instead of 00-03).

#include <uECC.h>

// private key: 0xbdb51a16eb6461ec16f84a7b6f19e20d0b9aa558fa0e9ae4bb493ff779f14255
static const uint8_t privateKey[] = {
    0xbd, 0xb5, 0x1a, 0x16, 0xeb, 0x64, 0x61, 0xec,
    0x16, 0xf8, 0x4a, 0x7b, 0x6f, 0x19, 0xe2, 0x0d,
    0x0b, 0x9a, 0xa5, 0x58, 0xfa, 0x0e, 0x9a, 0xe4,
    0xbb, 0x49, 0x3f, 0xf7, 0x79, 0xf1, 0x42, 0x55
};
// keccak256 of "1234": 0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7
uint8_t hash[32] = {
  0x38, 0x7a, 0x82, 0x33, 0xc9, 0x6e, 0x1f, 0xc0,
  0xad, 0x5e, 0x28, 0x43, 0x53, 0x27, 0x61, 0x77,
  0xaf, 0x21, 0x86, 0xe7, 0xaf, 0xa8, 0x52, 0x96,
  0xf1, 0x06, 0x33, 0x6e, 0x37, 0x66, 0x69, 0xf7
};

bool eth_sign_hashed_message(const uint8_t *privateKey, const uint8_t *digest, uint8_t *result) {
    int success = uECC_sign(
        (const uint8_t*)(privateKey),
        (const uint8_t*)(digest),
        32,
        (uint8_t*)result,
        uECC_secp256k1()
    );
    return (success == 1);
}

uint8_t signatureRegular[64];
eth_sign_hashed_message(privateKey, hash, signatureRegular);


// expected sig:
// 65f333960946f37b57336199fd98826f4b334f70137f5f3a7ef2b2622007c6d77905672bd78dce53e37eb3ddef0c2219296db1795b466b5a89ffecda9d2106fc (+1c recId)

// uECC sig
// 45d6243b60fc6f589e7c40d597ee4664c0fec8a6121f72dcc67ce10015d9dc760b545ac2c0fc49561e9b5643f19a89670d1c9c572e469578fa79dfb08fb2d110 (+1b|1c recId -> both not valid)

the contract looks like this and validates the expected signature perfectly (returns wallet address 0x77baedd3f1aab1e8415bb6445cdaf418aa1924d5), but returns random addresses for the signature created using uECC:

pragma solidity ^0.8.13;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract TestECDSA {
    using ECDSA for bytes32;

    function recover(bytes memory signature, bytes32 hashedPayload) public pure returns (address) {
        bytes32 signedHash = hashedPayload.toEthSignedMessageHash();
        return (signedHash.recover(signature));
    }
}

any idea of what i might be doing wrong or how to get me on the right track? I've tried 4 different libraries by now and I'm close to giving up :(

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

No branches or pull requests

2 participants