比特币隔离见证钱包地址计算

Posted

技术标签:

【中文标题】比特币隔离见证钱包地址计算【英文标题】:Bitcoin SegWit Wallet Address Calculation 【发布时间】:2020-09-18 06:41:57 【问题描述】:

对不起,如果这有点误导,但我实际上是用莱特币而不是比特币来做这个,但算法是完全相同的,我很确定答案也会是。事实上,我几乎可以肯定,比特币也会有同样的问题。我很难为给定的公钥生成正确的 SegWit 地址,如下所示:

莱特币地址生成(与比特币相同)

    我采用压缩公钥: 03861b83752e0c47cac36fc5980ae8956f41f6d9792a51f68a6bd5f66cc7364b48 公钥上的 SHA256。 RipeMD160 来自 2 的结果。 添加一个前缀字节,在我的例子中是 0x3a,即 LTC_TESTNET 双 SHA256 结果为 4,并将该结果的前四个字节添加到 RipeMD160 的末尾。 最后,Base58 编码。

一切看起来都很花哨,对吧?弹出 SegWit 地址:QhQxSZvVDWr3JvoKsYVC6BBW3DqkGhesrF

但是,我很确定这个地址不正确。当我将私钥作为 (p2wpkh-p2sh:) 导入 Electrum-LTC 时,它为此私钥生成的地址是:QYyWqgyWSm1AJWph32GnyY7eamG1wUDruk

现在我相信 Electrum-LTC 是正确的,在生成 SegWit 地址时我缺少一些东西,而且地址生成不仅仅是更改网络前缀。 我的公钥哈希是:

e444ac77800cdf904b928fc4642ab6fb6d4d696c

而 Electrum-LTC 的公钥哈希是:

87b3e5bf5b2a1381e6549020d245e45b9ac76c82

由于值非常不同,这表明最初的 SHA256 不只是散列公钥,而且我遗漏了一些东西。我几乎没有想法,希望有人能给出答案,而我在源代码中唯一能找到的东西,在 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;

【问题讨论】:

请在您的问题中添加MRE。如果没有代码,我们将无法提供太多帮助。 【参考方案1】:

终于想通了……计算p2wpkh-p2sh地址时,不只是地址的前缀不同。实际上,我在这里找到了上述问题的答案:https://bitcoin.stackexchange.com/questions/72775/is-it-possible-to-convert-an-address-from-p2pkh-to-p2sh(虽然它的解释方式相当神秘)。

我的问题中的上述代码对于比特币/莱特币等的 p2pkh 地址生成非常有效,但是当生成 Q(用于莱特币)、2(用于比特币)时,它不会起作用,因为它不仅仅是公众散列的密钥。它实际上是一个脚本,由 0x00 (DUP_0) 和一段公钥哈希 (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

【讨论】:

以上是关于比特币隔离见证钱包地址计算的主要内容,如果未能解决你的问题,请参考以下文章

(SegWit)隔离见证人在24小时内激活:比特币会如何变化?

比特币内存池再次爆满,每秒仅能处理4笔交易,隔离见证支持率逐渐上升

求教,比特币钱包怎么与php建立连接

求教,比特币钱包怎么与php建立连接

确定比特币钱包地址是不是“有效”

重磅BIP91锁定完成,比特币网络隔离见证部署在即