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
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