我正在使用谷歌云密钥管理服务来生成和管理密钥。我已使用 Elliptic Curve secp256k1 - SHA256 Digest 生成了用于 非对称签名的 HSM 密钥。公钥如下 -
{
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'
}
我希望从中获取以太坊地址,以便我可以为钱包提供资金并执行签名。同样,我编写了一个函数,如下 -
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)
}
此函数为我提供了以太坊校验和验证的地址,但不确定这是否是正确的方法。这个解决方案正确还是需要改进?
公钥示例:
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'
}
而且,我导出的以太坊地址是 -
0x8aCd56527DfE9205edf7D6F1EB39A5c9aa8aaE3F
在确定 Keccak 哈希时,不得使用 PEM 编码的公钥,而必须应用原始公钥,即十六进制编码的 x 和 y 值的串联。
通过将 PEM 编码密钥转换为 DER 编码密钥,可以最轻松地从该密钥导出。 DER 编码密钥的最后 64 个字节对应于原始公钥(至少对于 secp256k1,以太坊使用的椭圆曲线):
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
对于您的 pem 编码公钥,原始公钥是(十六进制编码):
79846fd12ed97f908d879fc03f1893eb1a18fb3e76d431d31602dd50f50fd9eff78e4603b994db0873fccc41e6ee7846e8050ea4909b4c5d89daf5a40b58b762
由此得出的以太坊地址是(十六进制编码):
fd55ad0678e9b90d5f5175d7ce5fd1ebd440309d
或带有校验和:
Fd55aD0678E9b90D5f5175d7cE5fD1eBd440309D
可以在 https://www.rfctools.com/ethereum-address-test-tool 上进行验证。
完整代码:
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
Google 提供以 DER 格式编码的公钥。
要获取以太坊地址,必须首先获得以太坊公钥。
asn1js 库是解码和解释 DER 编码的公钥数据的有效机制。
需要注意的是,根据 RFC 5480 第 2.2 节,初始字节 0x04 表示未压缩的密钥。通过删除这个初始字节,人们可以获得标准的以太坊公钥。
以下是解密 DER 编码的公钥的方法:
import * as asn1js from "asn1js";
decryptPublickeyDerEncoding(input: Buffer) : Buffer {
/**
* Before calculating the Ethereum address, we need to get the raw value of the public key.
* the input returns a DER-encoded X.509 public key
* asSubjectPublickeyInfo (SPKI), as defined in RFC 5280.
* Use an ASN1 library that allows us to define this as a schema as `OBJECT IDENTIFIER `
* https://www.rfc-editor.org/rfc/rfc5480#section-2
*/
const schema = new asn1js.Sequence({ value: [
new asn1js.Sequence({ value: [new asn1js.ObjectIdentifier()] }),
new asn1js.BitString({ name: "objectIdentifier" }),
]});
const parsed = asn1js.verifySchema(input, schema);
if (!parsed.verified) {
throw new Error(`Publickey: failed to parse. ${parsed.result.error}`);
}
const objectIdentifier: ArrayBuffer = parsed.result.objectIdentifier.valueBlock.valueHex;
/**
* According to section 2.2 of RFC 5480, the first byte, 0x04 indicates that this is an uncompressed key.
* We need to remove this byte for the public key to be correct. Once we delete the first byte, we get the
* raw public key that can be used to calculate our Ethereum address.
*/
const publickey = objectIdentifier.slice(1); // remove 0x04
/**
* Returns the wallet's public key buffer
*/
return Buffer.from(publickey);
}
有了以太坊公钥,您现在可以计算以太坊地址:
publickeyToAddress(input: Buffer) {
const hash = Buffer.from(keccak256(input), 'hex');
const address = "0x" + hash.slice(-20).toString('hex');
return address;
}
这就是你的以太坊地址!