十二.Berkeley DB数据库 序列化和钱包管理(4)PubKeyToAddress

发布时间:2026/10/21 1:51:00
十二.Berkeley DB数据库 序列化和钱包管理(4)PubKeyToAddress 1.AddKey报错这个函数我们很熟悉它定义在main.cpp里:////////////////////////////////////////////////////////////////////////////// // // mapKeys // bool AddKey(const CKey key) { CRITICAL_BLOCK(cs_mapKeys) { mapKeys[key.GetPubKey()] key.GetPrivKey(); mapPubKeys[Hash160(key.GetPubKey())] key.GetPubKey(); } return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); }复制过去然后在main.h声明一下即可。但是这里的mapKeys和mapPubKeys还没定义之前有说过这两个是存储公私钥对的。这种全局变量定义一下即可在main.cpp中mapvectorunsigned char, CPrivKey mapKeys; mapuint160, vectorunsigned char mapPubKeys;然后在main.h声明一下:extern mapvectorunsigned char, CPrivKey mapKeys; extern mapuint160, vectorunsigned char mapPubKeys;然后这里的cs_makKeys也未定义,同样main.cpp中定义main.h里声明如下CCriticalSection cs_mapKeys;声明:extern CCriticalSection cs_mapKeys;最后AddKey函数就还剩一个问题了Hash160这个函数。这个函数的代码是在util.h里由于之前我们只添加了hash相关的函数所以没有Hash160函数相关的代码现在将这个函数代码添加进去支持160位的哈希计算不仅仅是256位如下inline uint160 Hash160(const vectorunsigned char vch) { uint256 hash1; SHA256(vch[0], vch.size(), (unsigned char*)hash1); uint160 hash2; RIPEMD160((unsigned char*)hash1, sizeof(hash1), (unsigned char*)hash2); return hash2; }然后这个函数需要RIPEMD160,这个是openssl里的哈希160函数事实上Hash160最终是调用openssl库里的函数来实现的。我们在headers.h中添加如下代码#include openssl/ripemd.h这样就能认到RIPEMD160函数了。至此AddKey函数代码已修复完毕。2.PubKeyToAddress接下来我们来看这句if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), Your Address))这个PubKeyToAddress函数翻译过来就是公钥转地址那么很明显作用就是将65字节的公钥转换成1开头的地址。关于公钥转地址我们在第8章中用文字描述过接下来我们来看一下这个函数的代码用代码来走一遍我们的流程。inline string PubKeyToAddress(const vectorunsigned char vchPubKey) { return Hash160ToAddress(Hash160(vchPubKey)); }在这里调用了Hash160函数代码如下inline uint160 Hash160(const vectorunsigned char vch) { uint256 hash1; SHA256(vch[0], vch.size(), (unsigned char*)hash1); uint160 hash2; RIPEMD160((unsigned char*)hash1, sizeof(hash1), (unsigned char*)hash2); return hash2; }可以看到1.先是对这个公钥进行SHA256哈希2.然后再进行RIPEMD160哈希。接着是Hash160ToAddressstatic const unsigned char ADDRESSVERSION 0; inline string Hash160ToAddress(uint160 hash160) { // add 1-byte version number to the front vectorunsigned char vch(1, ADDRESSVERSION); vch.insert(vch.end(), UBEGIN(hash160), UEND(hash160)); return EncodeBase58Check(vch); }先加版本号ADDRESSVERSION0,就是加0x00。然后得到vch。进行EncodeBase58Check:inline string EncodeBase58Check(const vectorunsigned char vchIn) { // add 4-byte hash check to the end vectorunsigned char vch(vchIn); uint256 hash Hash(vch.begin(), vch.end()); vch.insert(vch.end(), (unsigned char*)hash, (unsigned char*)hash 4); return EncodeBase58(vch); }然后调用hash进行两次哈希这里的hash内部是调用了两次sha256的。得到一个hash值。然后将这个hash值的前4字节加到vch末尾做校验和。接着调用EncodeBase58(vch)进行58编码。inline string EncodeBase58(const vectorunsigned char vch) { return EncodeBase58(vch[0], vch[0] vch.size()); } inline string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) { CAutoBN_CTX pctx; CBigNum bn58 58; CBigNum bn0 0; // Convert big endian data to little endian // Extra zero at the end make sure bignum will interpret as a positive number vectorunsigned char vchTmp(pend-pbegin1, 0); reverse_copy(pbegin, pend, vchTmp.begin()); // Convert little endian data to bignum CBigNum bn; bn.setvch(vchTmp); // Convert bignum to string string str; str.reserve((pend - pbegin) * 138 / 100 1); CBigNum dv; CBigNum rem; while (bn bn0) { if (!BN_div(dv, rem, bn, bn58, pctx)) throw bignum_error(EncodeBase58 : BN_div failed); bn dv; unsigned int c rem.getulong(); str pszBase58[c]; } // Leading zeroes encoded as base58 zeros for (const unsigned char* p pbegin; p pend *p 0; p) str pszBase58[0]; // Convert little endian string to big endian reverse(str.begin(), str.end()); return str; }最终得到地址OK流程都没问题。接下来是解决代码问题了把这个函数的代码都复制到对应的文件中以支持PubKeyToAddress函数实现。这些函数几乎全在base58.h头文件里所以我们新建一个头文件base58.h然后将下面代码复制进去:// Copyright (c) 2009 Satoshi Nakamoto // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. // // Why base-58 instead of standard base-64 encoding? // - Dont want 0OIl characters that look the same in some fonts and // could be used to create visually identical looking account numbers. // - A string with non-alphanumeric characters is not as easily accepted as an account number. // - E-mail usually wont line-break if theres no punctuation to break at. // - Doubleclicking selects the whole number as one word if its all alphanumeric. // static const char* pszBase58 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz; inline string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) { CAutoBN_CTX pctx; CBigNum bn58 58; CBigNum bn0 0; // Convert big endian data to little endian // Extra zero at the end make sure bignum will interpret as a positive number vectorunsigned char vchTmp(pend - pbegin 1, 0); reverse_copy(pbegin, pend, vchTmp.begin()); // Convert little endian data to bignum CBigNum bn; bn.setvch(vchTmp); // Convert bignum to string string str; str.reserve((pend - pbegin) * 138 / 100 1); CBigNum dv; CBigNum rem; while (bn bn0) { if (!BN_div(dv, rem, bn, bn58, pctx)) throw bignum_error(EncodeBase58 : BN_div failed); bn dv; unsigned int c rem.getulong(); str pszBase58[c]; } // Leading zeroes encoded as base58 zeros for (const unsigned char* p pbegin; p pend *p 0; p) str pszBase58[0]; // Convert little endian string to big endian reverse(str.begin(), str.end()); return str; } inline string EncodeBase58(const vectorunsigned char vch) { return EncodeBase58(vch[0], vch[0] vch.size()); } inline bool DecodeBase58(const char* psz, vectorunsigned char vchRet) { CAutoBN_CTX pctx; vchRet.clear(); CBigNum bn58 58; CBigNum bn 0; CBigNum bnChar; while (isspace(*psz)) psz; // Convert big endian string to bignum for (const char* p psz; *p; p) { const char* p1 strchr(pszBase58, *p); if (p1 NULL) { while (isspace(*p)) p; if (*p ! \0) return false; break; } bnChar.setulong(p1 - pszBase58); if (!BN_mul(bn, bn, bn58, pctx)) throw bignum_error(DecodeBase58 : BN_mul failed); bn bnChar; } // Get bignum as little endian data vectorunsigned char vchTmp bn.getvch(); // Trim off sign byte if present if (vchTmp.size() 2 vchTmp.end()[-1] 0 vchTmp.end()[-2] 0x80) vchTmp.erase(vchTmp.end() - 1); // Restore leading zeros int nLeadingZeros 0; for (const char* p psz; *p pszBase58[0]; p) nLeadingZeros; vchRet.assign(nLeadingZeros vchTmp.size(), 0); // Convert little endian data to big endian reverse_copy(vchTmp.begin(), vchTmp.end(), vchRet.end() - vchTmp.size()); return true; } inline bool DecodeBase58(const string str, vectorunsigned char vchRet) { return DecodeBase58(str.c_str(), vchRet); } inline string EncodeBase58Check(const vectorunsigned char vchIn) { // add 4-byte hash check to the end vectorunsigned char vch(vchIn); uint256 hash Hash(vch.begin(), vch.end()); vch.insert(vch.end(), (unsigned char*)hash, (unsigned char*)hash 4); return EncodeBase58(vch); } inline bool DecodeBase58Check(const char* psz, vectorunsigned char vchRet) { if (!DecodeBase58(psz, vchRet)) return false; if (vchRet.size() 4) { vchRet.clear(); return false; } uint256 hash Hash(vchRet.begin(), vchRet.end() - 4); if (memcmp(hash, vchRet.end()[-4], 4) ! 0) { vchRet.clear(); return false; } vchRet.resize(vchRet.size() - 4); return true; } inline bool DecodeBase58Check(const string str, vectorunsigned char vchRet) { return DecodeBase58Check(str.c_str(), vchRet); } static const unsigned char ADDRESSVERSION 0; inline string Hash160ToAddress(uint160 hash160) { // add 1-byte version number to the front vectorunsigned char vch(1, ADDRESSVERSION); vch.insert(vch.end(), UBEGIN(hash160), UEND(hash160)); return EncodeBase58Check(vch); } inline bool AddressToHash160(const char* psz, uint160 hash160Ret) { vectorunsigned char vch; if (!DecodeBase58Check(psz, vch)) return false; if (vch.empty()) return false; unsigned char nVersion vch[0]; if (vch.size() ! sizeof(hash160Ret) 1) return false; memcpy(hash160Ret, vch[1], sizeof(hash160Ret)); return (nVersion ADDRESSVERSION); } inline bool AddressToHash160(const string str, uint160 hash160Ret) { return AddressToHash160(str.c_str(), hash160Ret); } inline bool IsValidBitcoinAddress(const char* psz) { uint160 hash160; return AddressToHash160(psz, hash160); } inline bool IsValidBitcoinAddress(const string str) { return IsValidBitcoinAddress(str.c_str()); } inline string PubKeyToAddress(const vectorunsigned char vchPubKey) { return Hash160ToAddress(Hash160(vchPubKey)); }然后将base58.h放在headers.h里合适的位置(最下面亦可,此文件被依赖较少---上面文件)以支持base58.h里用到的的一些类型。比如uint160 hash之类的。几乎没报错就报未定义的标识符UBEGINUEND。我们从源码中转到它的定义发现定义在util.h里在BEGIN和END的下面把这两个添加进去即可#define UBEGIN(a) ((unsigned char*)(a)) #define UEND(a) ((unsigned char*)(((a))[1]))ok这次有点顺利不用改太多东西base58.h不报错了db.cpp里的PubKeyToAddress函数也不报错了至此该函数问题解决这里我就不测试了。因为还有一些问题没解决。等都解决了可以把这个函数单独拿来测试一下。3.cs_mapWallet报未定义bool CWalletDB::LoadWallet(vectorunsigned char vchDefaultKeyRet) { vchDefaultKeyRet.clear(); //// todo: shouldnt we catch exceptions and try to recover and continue? CRITICAL_BLOCK(cs_mapKeys) CRITICAL_BLOCK(cs_mapWallet) {这些都是线程锁的变量main.cpp里定义一下main.h声明一下即可两个代码如下//定义 CCriticalSection cs_mapWallet; //声明 extern CCriticalSection cs_mapWallet;3. mapWallet然后是 mapWallet这个是存储交易的数据就是标识为tx的数据。是一个map类型main.cpp定义main.h声明即可mapuint256, CWalletTx mapWallet; extern mapuint256, CWalletTx mapWallet;关于这里为什么不一次性的把main.cpp和main.h中的所有定义和声明复制过去其实也省不了多少事如果光复制定义如果是自定义的类型你还是得区分和一个一个解决相关类型的问题无非是从哪边解决罢了当然主要是有解决的过程中可以加深一些印象主要是学习为主,注意修改代码如果照着我文中的步骤改不会那么顺利因为还有些细节我没法写上来我只说我碰到的问题剩下的小细节靠你们自己去解决或者以后直接下载我改好的项目4.CBigNum这里编译一下发现base58.h还有问题我们来解决一下报CBigNum错误。我们把#include bignum.h 头文件在headers.h中放在base58.h的前面。注意bignum.h要放在uint256.h的后面否则会报一大堆错因为bignum.h里调用了uint256.h。然后去掉所有cpp,.h中相冲突的bignum.h头文件(历史遗留问题)关于为什么要用这种上下序的顺序我选择尊重原著原汁原味现在还不到修改的时候。所以我请大家现在尽量不要脱离源码在还没完全搞懂整个项目的时候。5.strType setting钱包的数据库里还有一种键值对数据标识为setting,代码如下else if (strType setting) /// or settings or option or options or config? { string strKey; ssKey strKey; if (strKey fGenerateBitcoins) ssValue fGenerateBitcoins; if (strKey nTransactionFee) ssValue nTransactionFee; if (strKey addrIncoming) ssValue addrIncoming; }这是是钱包的一些配置数据比如是否开启挖矿以及设置交易手续费和你钱包的对外的地址这些。这三个在main.cpp中定义类型如下// Settings int fGenerateBitcoins; int64 nTransactionFee 0; CAddress addrIncoming;main.h声明// Settings extern int fGenerateBitcoins; extern int64 nTransactionFee; extern CAddress addrIncoming;6.CAddress然后这里又出了一种新类型CAddress这个是在net.h里定义的网络部分。现在我们还未涉及到所以就不引入了否则又将会有一大堆问题要解决。临时将这个addrIncoming有关的一切注释掉就4个地方去掉影响不大也不麻烦。并标注// CAddress未引入临时注释。以防以后忘记。7.CWalletTx这个类型我们之前提到过最终是继承自CTransaction在标识为tx数据时会涉及到这个CWalletTx,需要用它来承载交易数据。这里又涉及到交易部分并且需要CTxDB支持在db.h里这个CTxDB已经被我去掉了。所以我考虑了一下CWalletTx这里也暂不实现等涉及到交易部分在来解决这个问题。我现在需要的是存储公私钥以及转换地址功能相关。先把这些实现了。将db.cpp里这部分相关的代码注释掉:else if (strType tx) {} //以上大括号里的代码注释掉然后还是会报错我们右击CWalletTx,选择查找所有引用然后一个一个都注释掉mapWallet相关好了到了这里已经没多少错误了还剩下几个小错误要解决都一些函数安全报错8.getenv现在报一些函数安全错误比如fopen,getenv这些都是老函数现在编译器认为不安全不允许使用了。如getenv: This function or variable may be unsafe. Consider using _dupenv_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.这里我们采用禁止安全报错功能在所有的头文件之前加上这个宏定义#define _CRT_SECURE_NO_WARNINGS //设置不报安全警告我这里是把它放在headers.h里面然后headers.h则必须得在所有的头文件之前。否则同样不生效。解决这些报错后编译报一些.obj错误说明编译都通过了链接时出了问题。9.GetSerializeSize我们来具体看看先来看第一个unsigned int __cdecl GetSerializeSize(class std::vectorunsigned char,class std::allocatorunsigned char const ,int,int) (?GetSerializeSizeYAIAEBV?$vectorEV?$allocatorEstdstdHHZ) 已经在 db.obj 中定义GetSerializeSize重定义这个函数是属于serialize.h文件中的,分别是main.obj和util.obj报了这个错说明这三个地方都定义了这个函数有冲突。注意这种问题用#pragma once是解决不了的。#pragma once 只能解决单CPP里编译时的重定义不能解决obj时的重定义。但是多个cpp肯定有一些会用到serialize.h这个文件那么也必须包含链接时是必然的会有冲突。为什么会出现这种情况呢可能是版本原因吧。导致一些写法现在起了冲突。当初的写法不兼容现在的版本了。我们来回忆一下https://mp.csdn.net/mp_blog/creation/editor/146285784在这一章有这样一句例外关于obj时重定义问题)内联函数和模板函数除外编译器不会让这种错误发生有多个的话只会自动选择一个某些类定义也是因为类中定义的函数默认为隐式内联类外定义成员函数则不是然后我去查了一下这个GetSerializeSize函数在serialize.h里是模板函数并且也是内联的。应该是没问题的不会引起冲突我试着在前面加上static修饰意思这个函数只属于这个CPP。这样使用具体类型调用时在CPP里生成具体的函数代码实例化后这个函数前面是带static修饰的。并不会引起冲突相当于允许每个cpp拥有一个副本不参与obj链接。但还是报错我仔细想想了终于想起了一些事为什么偏偏是GetSerializeSize这个函数报错serialize.h中的其它函数也大量采用了相同的写法。为什么单单是这个vector int, int参数类型的重载报错呢然后我在bignum.h中找到了这个#include stdexcept #include openssl/bn.h #includealgorithm //解决reverse_copy 未定义问题 using namespace std; //临时定义解决没有GetSerializeSize函数问题 注意只为解决代码报错问题 unsigned int GetSerializeSize(const std::vectorunsigned char vch, int nType 0, int nVersion 0) { return vch.size() 4; // }这是第三章时遗留的问题因为要用到bignum.h而bignum.h又会用到serialize.h里的GetSerializeSize函数修复serialize.h又将是一堆问题所以我临时自己写了个简单的类似的函数解决问题。当然了后面serialize.h修复好后则不可不避免的产生冲突了我却忘记了这件事。应该是bignum.h放到headers.h后多个bignum.h自身产生的冲突非是跟serialize.h这里就不验证了现在知道了就好解决去掉我自己写的函数将bignum.h引入serialize.h来支持。我看了下头文件bignum.h排在serialize.h后面那么只要删除掉bignum.h里的GetSerializeSize函数代码就行了。10.mapAddressBook然后在db.obj中报无法解析的外部符号mapAddressBook这是因为在db.cpp包括db.h).只声明了mapAddressBook并且也调用了但外部没有定义。我们在db.h可以看到这样一句extern mapstring, string mapAddressBook;然后代码中使用了这个变量。就会报错。它的定义在源码中我看了是在ui.cpp中。这里我们把它放到main.cpp里如下mapstring, string mapAddressBook;然后我发现这个两个也只有声明没有定义可以我之前忘记了在main.cpp定义一下mapvectorunsigned char, CPrivKey mapKeys; mapuint160, vectorunsigned char mapPubKeys;至此全局LoadWallet函数代码已修复。并且项目编译成功。在下一章我们会测试一下具体的功能。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询