比特币 SegWit 钱包地址计算
Bitcoin SegWit Wallet Address Calculation
抱歉,如果这有点误导,但我实际上是用莱特币而不是比特币来做这件事,但算法完全一样,我很确定答案也是如此。事实上,我几乎可以肯定,比特币也会有同样的问题。我很难为给定的 public 密钥生成正确的 SegWit 地址,如下所示:
莱特币地址生成(与比特币相同)
- 我使用压缩的 public 密钥:
03861b83752e0c47cac36fc5980ae8956f41f6d9792a51f68a6bd5f66cc7364b48
- public 密钥上的 SHA256。
- RipeMD160 来自 2.
的结果
- 添加一个前缀字节,在我的例子中,0x3a 即 LTC_TESTNET
- 4 的双 SHA256 结果,并将该结果的前四个字节添加到 RipeMD160 的末尾。
- 最后,Base58编码。
一切看起来很花花公子,对吧?弹出 SegWit 地址:QhQxSZvVDWr3JvoKsYVC6BBW3DqkGhesrF
但是,我很确定这个地址不对。当我将私钥作为 (p2wpkh-p2sh:) 导入 Electrum-LTC 时,它为此私钥生成的地址是:QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk
现在我相信 Electrum-LTC 是正确的,并且在生成 SegWit 地址时我遗漏了一些东西,而且地址生成不仅仅是更改网络前缀。
我的 Public 密钥哈希是:
e444ac77800cdf904b928fc4642ab6fb6d4d696c
和 Electrum-LTC 的 Public 密钥哈希是:
87b3e5bf5b2a1381e6549020d245e45b9ac76c82
由于值非常不同,这表明初始 SHA256 并没有单独散列 public 密钥,我遗漏了一些东西。我快没主意了,我希望有人知道答案,我在源代码中唯一能找到的东西,在 chainparams.cpp 中是这样的:
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
有人知道我可能做错了什么吗?
更新 - 有人要求提供代码 - 所以在这里
#include <openssl/sha.h>
#include <openssl/ripemd.h>
#include <cstdint>
#include <iostream>
#include <vector>
std::string base58Encode(const std::vector<uint8_t>& data)
{
const uint8_t mapping[] = {
'1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z' };
std::vector<uint8_t> digits((data.size() * 137) / 100);
size_t digitslen = 1;
for (size_t i = 0; i < data.size(); i++)
{
uint32_t carry = static_cast<uint32_t>(data[i]);
for (size_t j = 0; j < digitslen; j++)
{
carry = carry + static_cast<uint32_t>(digits[j] << 8);
digits[j] = static_cast<uint8_t>(carry % 58);
carry /= 58;
}
for (; carry; carry /= 58)
digits[digitslen++] = static_cast<uint8_t>(carry % 58);
}
std::string result;
for (size_t i = 0; i < data.size() && !data[i]; i++)
result.push_back(mapping[0]);
for (size_t i = 0; i < digitslen; i++)
result.push_back(mapping[digits[digitslen - 1 - i]]);
return result;
}
std::vector<uint8_t> SHA256_Hash(const std::vector<uint8_t>& data)
{
std::vector<uint8_t> SHA256_Digest(SHA256_DIGEST_LENGTH);
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, data.data(), data.size());
SHA256_Final(SHA256_Digest.data(), &ctx);
return SHA256_Digest;
}
std::vector<uint8_t> RipeMD160_Hash(const std::vector<uint8_t>& data)
{
std::vector<uint8_t> RipeMD160_Digest(RIPEMD160_DIGEST_LENGTH);
RIPEMD160_CTX ctx;
RIPEMD160_Init(&ctx);
RIPEMD160_Update(&ctx, data.data(), data.size());
RIPEMD160_Final(RipeMD160_Digest.data(), &ctx);
return RipeMD160_Digest;
}
std::string getWalletAddress(const std::vector<uint8_t>& public_key, uint8_t id)
{
std::vector<uint8_t> addr_hash = SHA256_Hash(key);
std::vector<uint8_t> addr_ripe = RipeMD160_Hash(addr_hash);
addr_ripe.insert(addr_ripe.begin(), id);
addr_hash = SHA256_Hash(SHA256_Hash(addr_ripe));
addr_ripe.insert(addr_ripe.end(), addr_hash.begin(), addr_hash.begin() + sizeof(uint32_t));
return base58Encode(addr_ripe);
}
int main(int argc, char** argv)
{
const uint8_t LTC_TESTNET = 0x3a;
std::vector<uint8_t> public_key{
0x03, 0x86, 0x1b, 0x83, 0x75, 0x2e, 0x0c, 0x47, 0xca, 0xc3, 0x6f, 0xc5, 0x98, 0x0a, 0xe8, 0x95,
0x6f, 0x41, 0xf6, 0xd9, 0x79, 0x2a, 0x51, 0xf6, 0x8a, 0x6b, 0xd5, 0xf6, 0x6c, 0xc7, 0x36, 0x4b, 0x48};
std::cout << "Wallet Address: " << getWalletAddress(public_key, LTC_TESTNET) << std::endl;
// The above ouputs: QhQxSZvVDWr3JvoKsYVC6BBW3DqkGhesrF
// But it should be: QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk
return 0;
}
终于弄明白了...在计算 p2wpkh-p2sh 地址时,不仅仅是地址的前缀不同。事实上,我在这里找到了上述问题的答案:https://bitcoin.stackexchange.com/questions/72775/is-it-possible-to-convert-an-address-from-p2pkh-to-p2sh(虽然解释起来很隐晦)。
我问题中的上述代码对于 Bitcoin/Litecoin 等的 p2pkh 地址生成非常有效,但是当生成 Q(对于莱特币)、2(对于比特币)时,它不会工作,因为它是不仅仅是散列的 public 键。它实际上是一个脚本,0x00 (DUP_0) 然后是 public 密钥散列 (0x14) 的长度。
所以,要修复上面的代码,如果地址生成代码改为:
std::string getWalletAddress(const std::vector<uint8_t>& public_key, uint8_t id, bool is_p2sh)
{
std::vector<uint8_t> addr_hash = SHA256_Hash(key);
std::vector<uint8_t> addr_ripe = RipeMD160_Hash(addr_hash);
if (is_p2sh)
{
addr_ripe.insert(addr_ripe.begin(), { 0x00, static_cast<uint8_t>(addr_ripe.size()) });
addr_hash = SHA256_Hash(addr_ripe);
addr_ripe = RipeMD160_Hash(addr_ripe);
}
addr_ripe.insert(addr_ripe.begin(), id);
addr_hash = SHA256_Hash(SHA256_Hash(addr_ripe));
addr_ripe.insert(addr_ripe.end(), addr_hash.begin(), addr_hash.begin() + sizeof(uint32_t));
return base58Encode(addr_ripe);
}
// This will now work for both p2pkh addresses and p2wpkh-p2sh addresses
getWalletAddress(public_key, LTC_TESTNET, true); // Produces: QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk
抱歉,如果这有点误导,但我实际上是用莱特币而不是比特币来做这件事,但算法完全一样,我很确定答案也是如此。事实上,我几乎可以肯定,比特币也会有同样的问题。我很难为给定的 public 密钥生成正确的 SegWit 地址,如下所示:
莱特币地址生成(与比特币相同)
- 我使用压缩的 public 密钥: 03861b83752e0c47cac36fc5980ae8956f41f6d9792a51f68a6bd5f66cc7364b48
- public 密钥上的 SHA256。
- RipeMD160 来自 2. 的结果
- 添加一个前缀字节,在我的例子中,0x3a 即 LTC_TESTNET
- 4 的双 SHA256 结果,并将该结果的前四个字节添加到 RipeMD160 的末尾。
- 最后,Base58编码。
一切看起来很花花公子,对吧?弹出 SegWit 地址:QhQxSZvVDWr3JvoKsYVC6BBW3DqkGhesrF
但是,我很确定这个地址不对。当我将私钥作为 (p2wpkh-p2sh:) 导入 Electrum-LTC 时,它为此私钥生成的地址是:QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk
现在我相信 Electrum-LTC 是正确的,并且在生成 SegWit 地址时我遗漏了一些东西,而且地址生成不仅仅是更改网络前缀。 我的 Public 密钥哈希是:
e444ac77800cdf904b928fc4642ab6fb6d4d696c
和 Electrum-LTC 的 Public 密钥哈希是:
87b3e5bf5b2a1381e6549020d245e45b9ac76c82
由于值非常不同,这表明初始 SHA256 并没有单独散列 public 密钥,我遗漏了一些东西。我快没主意了,我希望有人知道答案,我在源代码中唯一能找到的东西,在 chainparams.cpp 中是这样的:
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
有人知道我可能做错了什么吗?
更新 - 有人要求提供代码 - 所以在这里
#include <openssl/sha.h>
#include <openssl/ripemd.h>
#include <cstdint>
#include <iostream>
#include <vector>
std::string base58Encode(const std::vector<uint8_t>& data)
{
const uint8_t mapping[] = {
'1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z' };
std::vector<uint8_t> digits((data.size() * 137) / 100);
size_t digitslen = 1;
for (size_t i = 0; i < data.size(); i++)
{
uint32_t carry = static_cast<uint32_t>(data[i]);
for (size_t j = 0; j < digitslen; j++)
{
carry = carry + static_cast<uint32_t>(digits[j] << 8);
digits[j] = static_cast<uint8_t>(carry % 58);
carry /= 58;
}
for (; carry; carry /= 58)
digits[digitslen++] = static_cast<uint8_t>(carry % 58);
}
std::string result;
for (size_t i = 0; i < data.size() && !data[i]; i++)
result.push_back(mapping[0]);
for (size_t i = 0; i < digitslen; i++)
result.push_back(mapping[digits[digitslen - 1 - i]]);
return result;
}
std::vector<uint8_t> SHA256_Hash(const std::vector<uint8_t>& data)
{
std::vector<uint8_t> SHA256_Digest(SHA256_DIGEST_LENGTH);
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, data.data(), data.size());
SHA256_Final(SHA256_Digest.data(), &ctx);
return SHA256_Digest;
}
std::vector<uint8_t> RipeMD160_Hash(const std::vector<uint8_t>& data)
{
std::vector<uint8_t> RipeMD160_Digest(RIPEMD160_DIGEST_LENGTH);
RIPEMD160_CTX ctx;
RIPEMD160_Init(&ctx);
RIPEMD160_Update(&ctx, data.data(), data.size());
RIPEMD160_Final(RipeMD160_Digest.data(), &ctx);
return RipeMD160_Digest;
}
std::string getWalletAddress(const std::vector<uint8_t>& public_key, uint8_t id)
{
std::vector<uint8_t> addr_hash = SHA256_Hash(key);
std::vector<uint8_t> addr_ripe = RipeMD160_Hash(addr_hash);
addr_ripe.insert(addr_ripe.begin(), id);
addr_hash = SHA256_Hash(SHA256_Hash(addr_ripe));
addr_ripe.insert(addr_ripe.end(), addr_hash.begin(), addr_hash.begin() + sizeof(uint32_t));
return base58Encode(addr_ripe);
}
int main(int argc, char** argv)
{
const uint8_t LTC_TESTNET = 0x3a;
std::vector<uint8_t> public_key{
0x03, 0x86, 0x1b, 0x83, 0x75, 0x2e, 0x0c, 0x47, 0xca, 0xc3, 0x6f, 0xc5, 0x98, 0x0a, 0xe8, 0x95,
0x6f, 0x41, 0xf6, 0xd9, 0x79, 0x2a, 0x51, 0xf6, 0x8a, 0x6b, 0xd5, 0xf6, 0x6c, 0xc7, 0x36, 0x4b, 0x48};
std::cout << "Wallet Address: " << getWalletAddress(public_key, LTC_TESTNET) << std::endl;
// The above ouputs: QhQxSZvVDWr3JvoKsYVC6BBW3DqkGhesrF
// But it should be: QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk
return 0;
}
终于弄明白了...在计算 p2wpkh-p2sh 地址时,不仅仅是地址的前缀不同。事实上,我在这里找到了上述问题的答案:https://bitcoin.stackexchange.com/questions/72775/is-it-possible-to-convert-an-address-from-p2pkh-to-p2sh(虽然解释起来很隐晦)。
我问题中的上述代码对于 Bitcoin/Litecoin 等的 p2pkh 地址生成非常有效,但是当生成 Q(对于莱特币)、2(对于比特币)时,它不会工作,因为它是不仅仅是散列的 public 键。它实际上是一个脚本,0x00 (DUP_0) 然后是 public 密钥散列 (0x14) 的长度。
所以,要修复上面的代码,如果地址生成代码改为:
std::string getWalletAddress(const std::vector<uint8_t>& public_key, uint8_t id, bool is_p2sh)
{
std::vector<uint8_t> addr_hash = SHA256_Hash(key);
std::vector<uint8_t> addr_ripe = RipeMD160_Hash(addr_hash);
if (is_p2sh)
{
addr_ripe.insert(addr_ripe.begin(), { 0x00, static_cast<uint8_t>(addr_ripe.size()) });
addr_hash = SHA256_Hash(addr_ripe);
addr_ripe = RipeMD160_Hash(addr_ripe);
}
addr_ripe.insert(addr_ripe.begin(), id);
addr_hash = SHA256_Hash(SHA256_Hash(addr_ripe));
addr_ripe.insert(addr_ripe.end(), addr_hash.begin(), addr_hash.begin() + sizeof(uint32_t));
return base58Encode(addr_ripe);
}
// This will now work for both p2pkh addresses and p2wpkh-p2sh addresses
getWalletAddress(public_key, LTC_TESTNET, true); // Produces: QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk