0

I am using google cloud key management service to generate and manage keys. I have generated the HSM key for Asymmetric signing using Elliptic Curve secp256k1 - SHA256 Digest. The public key is something as below -

{
  pem: '-----BEGIN PUBLIC KEY-----\n' +
    'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' +
    'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' +
    '-----END PUBLIC KEY-----\n',
  algorithm: 'EC_SIGN_SECP256K1_SHA256',
  pemCrc32c: { value: '12345678' },
  name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
  protectionLevel: 'HSM'
}

I am looking to derive Ethereum address from this so that I can fund the wallet and perform signing. For the same I have written a function as below -

const deriveEthAddress = async () => {
    const publicKey = await getPublicKey(); // this returns same key as show above snippet
    const address = keccak256(publicKey.pem);
    const hexAddress = address.toString('hex');
    return '0x' + hexAddress.toString('hex').substring(hexAddress.length - 40, hexAddress.length)
}

This function gives me ethereum checksum verified address, but not sure is it the correct way to do this. Is this solution correct or needs improvement?

Example public key:

publicKey {
  pem: '-----BEGIN PUBLIC KEY-----\n' +
    'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeYRv0S7Zf5CNh5/APxiT6xoY+z521DHT\n' +
    'FgLdUPUP2e/3jkYDuZTbCHP8zEHm7nhG6AUOpJCbTF2J2vWkC1i3Yg==\n' +
    '-----END PUBLIC KEY-----\n',
  algorithm: 'EC_SIGN_SECP256K1_SHA256',
  pemCrc32c: { value: '41325621' },
  name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
  protectionLevel: 'HSM'
}

And, ethereum address I am deriving is - 0x8aCd56527DfE9205edf7D6F1EB39A5c9aa8aaE3F

DIGVJSS
  • 463
  • 1
  • 10
  • 21
  • `publicKey.pem` is the PEM encoded public key (in X.509/SPKI format). The hash is created from the *raw* public key (concatenated x and y, 64 bytes). Also you should post test data (public key and address). – Topaco Sep 02 '22 at 12:35
  • @Topaco added the example public key and the address I am deriving. – DIGVJSS Sep 05 '22 at 04:42

1 Answers1

3

You must not use the PEM encoded public key when determining the Keccak hash, but must apply the raw public key, i.e. the concatenation of the hex encoded x and y value.
This can be derived most easily from the PEM encoded key by converting it to a DER encoded key. The last 64 bytes of the DER encoded key correspond to the raw public key (at least for secp256k1, the elliptic curve used by Ethereum):

var publicKey = {
  pem: '-----BEGIN PUBLIC KEY-----\n' +
  ...
}

// Export raw public key (without 0x04 prefix)
var x509pem = publicKey.pem;
var x509der = crypto.createPublicKey(x509pem).export({format: 'der', type: 'spki'});
var rawXY = x509der.subarray(-64);
console.log('Raw key: 0x' + rawXY.toString('hex')); // 79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762

For your pem encoded public key the raw public key is (hex encoded):

79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762

The Ethereum address derived from this is (hex encoded):

fd55ad0678e9b90d5f5175d7ce5fd1ebd440309d

or with checksum:

Fd55aD0678E9b90D5f5175d7cE5fD1eBd440309D

which can be verified on https://www.rfctools.com/ethereum-address-test-tool.


Full code:

const crypto = require('crypto');
const keccak256 = require('keccak256');

var publicKey = {
  pem: '-----BEGIN PUBLIC KEY-----\n' +
    'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEeYRv0S7Zf5CNh5/APxiT6xoY+z521DHT\n' +
    'FgLdUPUP2e/3jkYDuZTbCHP8zEHm7nhG6AUOpJCbTF2J2vWkC1i3Yg==\n' +
    '-----END PUBLIC KEY-----\n',
  algorithm: 'EC_SIGN_SECP256K1_SHA256',
  pemCrc32c: { value: '41325621' },
  name: 'PATH-TO-KEY-ON-KMS/cryptoKeyVersions/1',
  protectionLevel: 'HSM'
}

// Export raw public key (without 0x04 prefix)
var x509pem = publicKey.pem;
var x509der = crypto.createPublicKey(x509pem).export({format: 'der', type: 'spki'});
var rawXY = x509der.subarray(-64);
console.log('Raw key: 0x' + rawXY.toString('hex')); // 79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762

// Derive address from raw public key
var hashXY = keccak256(rawXY);
var address = hashXY.subarray(-20).toString('hex').toLowerCase();

// Calculate checksum (expressed as upper/lower case in the address)
var addressHash = keccak256(address).toString('hex');
var addressChecksum = '';
for (var i = 0; i < address.length; i++){
    if (parseInt(addressHash[i], 16) > 7) {
        addressChecksum += address[i].toUpperCase();
    } else {
        addressChecksum += address[i];
    }
}

console.log('Derived: 0x' + addressChecksum);                       // 0xFd55aD0678E9b90D5f5175d7cE5fD1eBd440309D
console.log('Test:    0xFd55aD0678E9b90D5f5175d7cE5fD1eBd440309D'); // from https://www.rfctools.com/ethereum-address-test-tool using the raw key 
Topaco
  • 40,594
  • 4
  • 35
  • 62