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

发布时间:2026/10/21 1:50:55

十二.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函数代码已修复。并且项目编译成功。在下一章我们会测试一下具体的功能。
延伸阅读

更多相关文章

2026/10/21 1:50:55

TortoiseGit拉取开源子仓库失败

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:50:55

Java面试常问问题——Mysql篇

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 1:45:55

ULN2803:八通道达林顿晶体管阵列,驱动控制的可靠之选

一、产品简介ULN2803 是一款高性能的八通道达林顿晶体管阵列,专为满足各种复杂驱动控制需求而设计。它采用先进的半导体制造工艺,将八个 NPN 达林顿连接晶体管集成在一个芯片上,能够实现低逻辑电平数字电路(如 TTL、CMOS 或 PMOS/…

2026/10/21 3:11:06

AI Agent可靠性工程:从搭建到上线的完整实战指南

近两年“AI Agent”这个词在技术社区里几乎被说烂了。我见过不少团队拿大模型API拼了个Agent demo,演示时效果惊艳,结果一上真实场景就频繁翻车:工具参数传错、上下文串台、模型自作主张执行了不该执行的操作。问题往往不是模型不够聪明&…

2026/10/21 3:11:06

零GPU本地部署大模型:LocalAI CPU推理实战与OpenAI API无缝兼容

最近在折腾内部工具的时候,发现一个特别有意思的问题:我们团队手上只有几台普通的台式机,没有专业显卡,但业务上又确实需要跑一些大语言模型来处理文档、做点简单的智能问答。云上的模型服务不是不能用,但数据要过一道…

2026/10/21 3:11:06

MYPHP企业建站系统解析:三语切换、后台维护与二次开发实战

简介:MYPHP企业建站系统 v4.2 UTF-8 简繁英三语版是一套免费开源的企业建站PHP源码,面向需要快速搭建企业官网的站长、建站公司及PHP初学者。系统专注企业建站场景,操作简便、界面友好,内置简繁英三语支持,可在较短时间…

2026/10/21 3:11:06

顶帽变换实战:形态学运算解决光照不均与缺陷检测

1. 顶帽变换到底是什么:从一次表面缺陷检测说起1.1 光照不均为什么让阈值分割直接失灵两年前我接过一个金属零件表面质量检测的小项目,任务并不复杂:从生产线上拍下来的零件图里,找出表面残留的加工亮斑。图片分辨率不高&#xff…

2026/10/21 3:11:06

动物园管理系统实战:从数据库设计到业务闭环

1. 项目背景与核心价值1.1 动物园管理系统到底在解决什么问题我在一些线下走访和交流中经常听到这样的抱怨:某家动物园的游客服务中心还在用Excel登记团队预约,饲养员喂完动物后靠微信群上报,动物生病了翻阅纸质病历卡,库存饲料过…

2026/10/21 3:06:06

RAG效果差?问题多半出在知识库管理:清洗、分块与治理实战

先说明一点:这个标题我深有同感。做了大半年RAG相关的东西,见过不少团队把精力全压在召回链路调参上,结果效果上不去,最后排查一圈,问题全出在知识库本身——脏数据、错分块、无版本、无评测,源头一乱&…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/21 0:00:35

交通流仿真实战:IDM跟驰与MOBIL换道模型参数调优与避坑指南

简介:这份资源是面向交通工程学习者与Python开发者的微观交通流仿真项目,核心实现智能驾驶模型(IDM)下的车辆跟驰与换道行为,可用于理解速度、密度与流量之间的动态关系,适合具备Python基础、希望深入交通模…

2026/10/21 0:00:35

揭穿智能体表面顺从:用追踪代码重建任务执行确定性

1. 表面顺从不是智能体的“礼貌”,而是任务执行链路上的隐性断点最近在调试一个跨平台长周期任务调度系统时,我反复遇到一种令人困惑的现象:智能体在用户指令发出后,几乎秒级响应“已收到”“正在处理”“请稍候”,UI上…

2026/10/21 0:00:35

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑