phpprivate-keyelliptic-curveecdsader

How to sign ECDSA signature and encode to DER format in hex in php?


How to sign the Tx data including ETH Keccak256 hashed addresses signature with secp256k1 (ECDSA) keys and encode to DER format in hex and verify in php?

I want to use only php libraries come with, openssl and others, to make secp256k1 private and public keys, sign, and convert to DER format in hex for making a sample of my cryptcoin.

Ethereum addresses are made by secp256k1 EDSA keys, hash the 256 bits/64 character pubic key made from the private key with Keccak256 (sha-3-256) hash algorithm to calculate hash, then take last 40 characters from the hash string in hex, add checksum, and add x0 prefix to the hashed string.

The example is shown in github.com/simplito/elliptic-php repo as toDER() function in elliptic-php/lib/EC/Signature.php. The library is using BN-php, but I want to use only php library, openssl and others.

// https://github.com/simplito/elliptic-php ECDSA sample
<?php
use Elliptic\EC;
// Create and initialize EC context
// (better do it once and reuse it)
$ec = new EC('secp256k1');

// Generate keys
$key = $ec->genKeyPair();

// Sign message (can be hex sequence or array)
$msg = 'ab4c3451';
$signature = $key->sign($msg);

// Export DER encoded signature to hex string
$derSign = $signature->toDER('hex');

openssl_pkey_new() and openssl_sign() functions can make new secp256k1 private and public keys and sign, but can not convert the public key to DER format in hex to make address by hashing keccak256 algorithm .

Signing Tx hash requires from/to ETH addresses in JSON string Tx data {address:{to:address1,from:address2},amount:0,timestamp:timestamp tx, hash:sha256hash, previousHash:hash, difficulty:2...} and openssl_pkey_new() openssl_pkey_export() made private and public keys are needed to make ETH address.

Since, openssl_pkey_new() openssl_pkey_export() for OPENSSL_KEYTYPE_EC return DER bin keys, I need to convert them to hex.

I have tried openssl_pkey_new() and openssl_sign() functions but I can not convert to DER format in hex with simple function.

openssl_pkey_new() function to make new secp256k1 key pairs, just shown in Generating the 256bit ECDSA private key question.

$config = [
    "config" => getenv('OPENSSL_CONF'),
    'private_key_type' => OPENSSL_KEYTYPE_EC,
    'curve_name' => 'secp256k1'
];
$res = openssl_pkey_new($config);
if (!$res) {
    echo 'ERROR: Fail to generate private key. -> ' . openssl_error_string();
    exit;
}
// Generate Private Key
openssl_pkey_export($res, $priv_key, NULL, $config);
// Get The Public Key
$key_detail = openssl_pkey_get_details($res);
$pub_key = $key_detail["key"];

echo "priv_key:<br>".$priv_key;
echo "<br><br>pub_key:<br>".$pub_key;

Link

And sign with the key.

openssl_sign($hashdata, $signature, $private_key);

Is there any way, with php libraries, to convert the keys to DER in hex?


Solution

  • Unfortunately I think you're out of luck. First to clarify, for Ethereum you want the signature in DER and the key format can vary depending on your software, but for an Ethereum account address you do NOT want the key in DER. Specifically an address is computed by:

    1. represent the publickey point as raw X,Y coordinates, with no metadata like DER or even a single byte like X9.62 and no encoding like base64 or hex

    2. take a Keccak256 hash and take the last 20 bytes, which can be represented (e.g. displayed) in hex with 0x prefixed for a total of 42 characters

    For step 1, the keys used by OpenSSL, and thus by php's builtin module, are PEM format, as you see in your example, which consists of an ASN.1 (DER or sometimes BER) body encoded in base64 with linebreaks and header/trailer lines. Specifically the publickey is in the format defined by RFC7468 section 13 whose content is the SubjectPublicKeyInfo structure defined by X.509 and PKIX i.e. RFC5280 4.1.2.7 whose contents for a particular algorithm is defined in other standards; the case here, X9-style EC, is defined in RFC3279 2.3.5 and simplified by RFC5480 2.2 which reference X9.62 and SEC1. (Whew!) While these standards allow several options, OpenSSL in PHP always uses X9.62-uncompressed point format and DER encoding, so for this curve the data you need for Ethereum is simply the last 64 bytes of the DER decoded from the PEM:

    $der=base64_decode(str_replace(array("-----BEGIN PUBLIC KEY-----\n","-----END PUBLIC KEY-----\n"),"",$pub_key));
    $raw=substr($der,-64);
    

    But step 2 is a problem. Although SHA3 and SHAKE as standardized in FIPS202 are instances of Keccak, the hash Ethereum uses is a different and nonstandard instance of Keccak, which OpenSSL does not implement, therefore neither does php by itself (without adding a library).