如何从椭圆曲线 secp256k1 - SHA256 Digest 导出以太坊地址

问题描述 投票:0回答:2

我正在使用谷歌云密钥管理服务来生成和管理密钥。我已使用 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

cryptography ethereum public-key google-cloud-kms
2个回答
4
投票

在确定 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 

0
投票

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

这就是你的以太坊地址!

© www.soinside.com 2019 - 2024. All rights reserved.