主页 > imtoken钱包注册教程 > 比特币私钥、公钥及地址生成方法源码分析

比特币私钥、公钥及地址生成方法源码分析

imtoken钱包注册教程 2023-12-23 05:10:31

本文所有源码来自比特币核心0.11

1.比特币私钥

以下是《精通比特币》一书的私钥生成部分的说明:

生成密钥的第一步也是最重要的一步是找到足够安全的熵源,即随机源。生成比特币私钥本质上与“选择 1 到 2^256 之间的数字”相同。只要选择的结果不可预测或不可重复,选择数字的确切方法并不重要。比特币软件使用操作系统底层的随机数生成器来生成 256 位的熵(随机性)。通常,操作系统随机数生成器由人为的随机源初始化,可能是通过摇动鼠标几秒钟等。对于真正的偏执狂,使用掷骰子方法并用铅笔和纸记录下来。

更准确地说,私钥可以是 1 到 n-1 之间的任意数字,其中 n 是一个常数(n=1.158 * 10^77 ,略小于 2^256),并且由比特币使用的椭圆曲线的阶数定义(参见4.1.5 Elliptic Curve Cryptography Explained)。要生成这样的私钥,我们随机选择一个 256 位的数字,并检查如果小于n - 1。从编程的角度来看,一般通过从密码安全的随机源中取出一长串随机字节,并使用SHA256哈希算法对其进行操作,可以方便地生成一个256-比特数,如果运算结果小于n-1,我们就有了一个合适的私钥,否则,我们用另一个随机数重复。

根据描述,我们找到了比特币私钥生成部分的源码:

void CKey::MakeNewKey(bool fCompressedIn) {
    RandAddSeedPerfmon();
    do {
        GetRandBytes(vch, sizeof(vch));
    } while (!Check(vch));
    fValid = true;
    fCompressed = fCompressedIn;
}

该函数首先调用了RandAddSeedPerfmon()函数,我们来看看这个函数:

比特币私钥泄露会怎样

void RandAddSeed()
{
    // Seed with CPU performance counter
    int64_t nCounter = GetPerformanceCounter();
    RAND_add(&nCounter, sizeof(nCounter), 1.5);
    memory_cleanse((void*)&nCounter, sizeof(nCounter));
}

void RandAddSeedPerfmon()
{
    RandAddSeed();
#ifdef WIN32
    // Don't need this on Linux, OpenSSL automatically uses /dev/urandom
    // Seed with the entire set of perfmon data
    // This can take up to 2 seconds, so only do it every 10 minutes
    static int64_t nLastPerfmon;
    if (GetTime() < nLastPerfmon + 10 * 60)
        return;
    nLastPerfmon = GetTime();
    std::vector vData(250000, 0);
    long ret = 0;
    unsigned long nSize = 0;
    const size_t nMaxSize = 10000000; // Bail out at more than 10MB of performance data
    while (true) {
        nSize = vData.size();
        ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", NULL, NULL, begin_ptr(vData), &nSize);
        if (ret != ERROR_MORE_DATA || vData.size() >= nMaxSize)
            break;
        vData.resize(std::max((vData.size() * 3) / 2, nMaxSize)); // Grow size of buffer exponentially
    }
    RegCloseKey(HKEY_PERFORMANCE_DATA);
    if (ret == ERROR_SUCCESS) {
        RAND_add(begin_ptr(vData), nSize, nSize / 100.0);
        memory_cleanse(begin_ptr(vData), nSize);
        LogPrint("rand", "%s: %lu bytes\n", __func__, nSize);
    } else {
        static bool warned = false; // Warn only once
        if (!warned) {
            LogPrintf("%s: Warning: RegQueryValueExA(HKEY_PERFORMANCE_DATA) failed with code %i\n", __func__, ret);
            warned = true;
        }
    }
#endif
}

在非windows平台上直接调用RandAddSeed()函数,如上图。这里用到了GetPerformanceCounter()函数,其源码:

static inline int64_t GetPerformanceCounter()
{
    int64_t nCounter = 0;
#ifdef WIN32
    QueryPerformanceCounter((LARGE_INTEGER*)&nCounter);
#else
    timeval t;
    gettimeofday(&t, NULL);
    nCounter = (int64_t)(t.tv_sec * 1000000 + t.tv_usec);
#endif
    return nCounter;
}  

QueryPerformanceCounter() 查询性能计数器。该函数用于获取精确的性能计数器值。如果存在,则返回当前计数器值的地址。gettimeofday()是Linux下获取精确时间的函数。可以看出,这是为了从系统中获取足够安全的熵源,即随机性源,并将值赋给nCounter。然后使用 RAND_add 函数。RAND_add(&nCounter, sizeof(nCounter), 1.5); 关于这个函数,详细解释如下:

void RAND_add(const void *buf,int num,双熵)

增加随机数生成的不可预测性,将buf数组中的num个数据添加到PRNG中,entropy是buf中数据的随机性估计,如果entropy和num相等,则RAND_add函数与Rand_seed函数相同;

buf中的数据一般使用系统中的随机事件,比如一些交互数据比特币私钥泄露会怎样,用户点击的键盘值,鼠标滑动的位置等等。

比特币私钥泄露会怎样

由此,我们使用系统生成随机数种子并将其添加到 PRNG(伪随机数生成器)中。

然后循环调用 GetRandBytes()

void GetRandBytes(unsigned char* buf, int num)
{
    if (RAND_bytes(buf, num) != 1) {
        LogPrintf("%s: OpenSSL RAND_bytes() failed with error: %s\n", __func__, ERR_error_string(ERR_get_error(), NULL));
        assert(false);
    }
}

即计算 RAND_bytes()

int RAND_bytes(unsigned char *buf,int num);

根据加密算法生成的随机数实际上是一个伪随机数。但是,如果在调用此函数之前设置了随机种子,则无法预先计算生成的随机数。

buf:输出,存储生成的随机数数组;

num:输入,产生的随机数个数;

比特币私钥泄露会怎样

返回值:1表示成功,0表示失败;

停止,直到满足 eccrypto::Check(vch) 函数的要求,并且 fValid 设置为 true。此时,生成了一个私钥。

2.比特币公钥

公钥可以通过椭圆曲线算法从私钥计算出来,这是一个不可逆的过程:K = k * G。其中 k 是私钥,G 是称为生成点的常数点,K 是生成的公钥。反向操作,称为“求离散对数”——知道公钥 K 的情况下找到私钥 k——非常困难,就像尝试 k 的所有可能值一样,是一种蛮力搜索。

生成公钥的函数如下:

CPubKey CKey::GetPubKey() const {
    assert(fValid);
    CPubKey result;
    int clen = 65;
    int ret = secp256k1_ec_pubkey_create(secp256k1_context, (unsigned char*)result.begin(), &clen, begin(), fCompressed);
    assert((int)result.size() == clen);
    assert(ret);
    assert(result.IsValid());
    return result;
}

核心是利用secp256k1_ec_pubkey_create()函数根据之前生成的私钥生成对应的公钥。

3.比特币地址

比特币私钥泄露会怎样

在交易中,比特币地址通常作为接收方出现。如果将比特币交易比作支票,那么比特币地址就是收款人,也就是我们要在收款人栏中写的。支票的收款人可以是银行账户、公司、机构,甚至是现金支票。支票不需要指定特定账户,而是使用通用名称作为收款人,这使其成为一种相当灵活的支付工具。同样,比特币地址的使用使比特币交易变得灵活。比特币地址可以代表一对公钥和私钥的所有者,或者其他东西,例如脚本。现在,让我们看一个从公钥生成比特币地址的简单示例。

比特币地址可以通过单向加密哈希算法从公钥中获得。哈希算法是一种单向函数,它接受任意长度的输入以生成指纹摘要。加密哈希函数在比特币中被广泛使用:比特币地址、脚本地址和挖矿中的工作量证明算法。用于从公钥生成比特币地址的算法是安全散列算法 (SHA) 和 RACE Integrity Primitives Evaluation Message Digest (RIPEMD),特别是 SHA256 和 RIPEMD160。

以公钥 K 为输入,计算其 SHA256 哈希值,并利用此结果计算 RIPEMD160 哈希值比特币私钥泄露会怎样,得到一个长度为 160 位(20 字节)的数字:

A = RIPEMD160(SHA256(K))

不管隔离见证如何,比特币有两种主要的交易类型:

对应两个不同的地址:

3.1 对于P2PKH类型的交易,地址生成函数如下:

其中,Hash160分为两步:SHA256和RIPEMD160

比特币私钥泄露会怎样

/** Compute the 160-bit hash an object. */
template
inline uint160 Hash160(const T1 pbegin, const T1 pend)
{
    static unsigned char pblank[1] = {};
    uint160 result;
    CHash160().Write(pbegin == pend ? pblank : (const unsigned char*)&pbegin[0], (pend - pbegin) * sizeof(pbegin[0]))
              .Finalize((unsigned char*)&result);
    return result;
}

3.2 对于P2SH类型的交易,地址生成函数如下:

/** A reference to a CScript: the Hash160 of its serialization (see script.h) */
class CScriptID : public uint160
{
public:
    CScriptID() : uint160() {}
    CScriptID(const CScript& in);
    CScriptID(const uint160& in) : uint160(in) {}
};

此时,从公钥生成一个 160 位(20 字节)的地址。

4.总结

通过公钥和地址生成比特币的过程大致如下:

12473090-b46ae018d0a16843.jpg

来自“掌握比特币”