区块链系统探索之路:钱包地址的实现

这篇具有很好参考价值的文章主要介绍了区块链系统探索之路:钱包地址的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在区块链,特别是比特币网络,一个非常关键的组件是钱包。它主要用来实现“价值转移”,既然要转移,那就必须要有转移人和接收人,在转移过程中,我们必须确保转移的发送必须由资产的所有者发起,这就是私钥的作用,一笔交易要生效必须由资产的所有人使用它的私钥确认后才能发起,同时要有办法准确找到价值的正确接受者,这就是公钥的作用,公钥类似于银行账号用于接收转移的资产。

在前面章节我们介绍过,私钥是一个随机数,而公钥是将椭圆曲线上的G点与私钥对应的数字进行“乘法”后所得的结果。有了公钥和私钥,我们就很容易确认一笔交易的合法性。交易的发起者将他的公钥公布出来,然后使用其私钥对交易内容进行数字签名,任何人都可以用公钥来确认这笔交易的签名是否属于该发起者,只有掌握私钥的人才能实现正确的签名,在数学上可以证明,没有任何人在没有拿到私钥的情况下能伪造合法的签名。

下面我们看看钱包地址生成的具体步骤。首先假设我们有一个私钥记为N, 让它与椭圆曲线上的G点做 “乘法”操作后得到公钥P,因为公钥依然是椭圆曲线上的一点,因此P由x,y两个坐标组成,他们分别是32字节大小的数值,我们需要对这两个数值进行预处理。处理方法有两种,一种是不压缩形式,它的做法是先将这两个32字节的数值转换成以“大端”形式存储的数组,然后将这两个数组合成一个数组,最后在数组的前头再插入一个字节0x04,我们看看具体的实现方法:

class S256Point(EllipticPoint):
    ...

    def sec(self):
        """
        将32字节的x,y转换成大端形式数组,然后两个数组合成一个,最后在合成的数组前头插入一个字节0x04
        """
        return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big')

privKey = 0x038109007313a5807b2eccc082c8c3fbb988a973cacf1a7df9ce725c31b14776
pubKey = privKey * G
print(pubKey)
P = S256Point(pubKey.x, pubKey.y)
uncompress_pub_key = P.sec()
print(", ".join(hex(b) for b in uncompress_pub_key))

上面代码运行后结果如下:

EllipticPoint(x:LimitFieldElement_0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f_(0x2a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa),y:LimitFieldElement_0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f_(0x9f3bc9f3948a19dabb796a2a744aae50367ce38a3e6b60ae7d72159caeb0c102),a:0000000000000000000000000000000000000000000000000000000000000000, b:0000000000000000000000000000000000000000000000000000000000000007)
0x4, 0x2, 0xa4, 0x6, 0x62, 0x42, 0x11, 0xf2, 0xab, 0xbd, 0xc6, 0x8d, 0xa3, 0xdf, 0x92, 0x9f, 0x93, 0x8c, 0x33, 0x99, 0xdd, 0x79, 0xfa, 0xc1, 0xb5, 0x1b, 0xe, 0x4a, 0xd1, 0xd2, 0x6a, 0x47, 0xaa, 0x9f, 0x3b, 0xc9, 0xf3, 0x94, 0x8a, 0x19, 0xda, 0xbb, 0x79, 0x6a, 0x2a, 0x74, 0x4a, 0xae, 0x50, 0x36, 0x7c, 0xe3, 0x8a, 0x3e, 0x6b, 0x60, 0xae, 0x7d, 0x72, 0x15, 0x9c, 0xae, 0xb0, 0xc1, 0x2

从输出结果可以看到,x,y两部分被分解成32字节的数组,然后两个数组合并成一个,最后在数组的最前头多了一个字节0x04。

第二种预处理方式是压缩模式。在前面章节中我们看到,在求余模式下, -x % p 等价于 (p-x) % p,因此基于有限域的椭圆曲线,如果点(x ,y)在曲线上,那么点(x , p - y)同样在曲线上。同时有限域的元素个数p我们都选取为素数,因此如果y是偶数,那么p - y就是奇数,基于这个特性,给定椭圆曲线上一点(x, y),我们其实只需要存储x的值,然后确定y是奇数还是偶数即可,因为我们可以通过x计算出y或者p - y,因此给定曲线上一点,在压缩模式下,它的预处理步骤如下:
1,选取数组起始字节,如果y是偶数,那么起始字节取值0x02,要不然取值0x03
2,将x的值转换成32个字节的数组,以大端的模式存储,由此我们前面的sec函数可以修改如下:

def sec(self, compressed=True):
        """
        将32字节的x,y转换成大端形式数组,然后两个数组合成一个,最后在合成的数组前头插入一个字节0x04
        """
        if compressed:
            if self.y.num % 2 == 0:
                return b'\x02' + self.x.num.to_bytes(32, 'big')
            else:
                return b'\x03' + self.num.to_bytes(32, 'big')
        return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big')

基于以上修改,我们前面的测试代码在压缩模式下再运行一遍,所得结果如下:

EllipticPoint(x:LimitFieldElement_0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f_(0x2a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa),y:LimitFieldElement_0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f_(0x9f3bc9f3948a19dabb796a2a744aae50367ce38a3e6b60ae7d72159caeb0c102),a:0000000000000000000000000000000000000000000000000000000000000000, b:0000000000000000000000000000000000000000000000000000000000000007)
0x2, 0x2, 0xa4, 0x6, 0x62, 0x42, 0x11, 0xf2, 0xab, 0xbd, 0xc6, 0x8d, 0xa3, 0xdf, 0x92, 0x9f, 0x93, 0x8c, 0x33, 0x99, 0xdd, 0x79, 0xfa, 0xc1, 0xb5, 0x1b, 0xe, 0x4a, 0xd1, 0xd2, 0x6a, 0x47, 0xaa

在上面结果中,我们只存储了x部分,同时起始字节是0x02,因为点的y部分是偶数。采用压缩方式能够把公钥长度从64字节减少到33字节,这就非常有利于在网络上传输。但这么做也带来也给问题是,当我们接收到x后,如果把y的值计算出来。下面我们推导一下计算过程。

给定 w ^ 2 = v, 如果我们知道v, 如何在不用计算开方的情况下得到w。这个问题在求余的基础上变得比较容易。前面章节我们提到过,如果给定有限域集合的元素个数为p(p为素数),w为集合中的一个元素,那么我们有费马小定理 w ^ (p-1) % p = 1, 在等式两边同时乘以w ^ 2就有:

w ^ 2 w ^(p-1) % p = w ^ 2 \ 1 => [w ^ (p+1)] % p= (w ^ 2) % p

由于p是素数,因此p是奇数,于是p+1是偶数,因此 (p+1) / 2是一个整数,因此上面的等式左右两边的指数除以2就有:

w % p = (w ^ (p+1)/2) % p

注意到, (p+1) / 2 = (2 \ (p+1)) / 4,如果(p+1)/4是一个整数的话* 那就有:
w % p= w ^ ((p+1)/2) % p= w ^ (2*(p+1)/4) % p = (w^2) ^ ((p+1)/4) % p

幸运的是对比特币的椭圆曲线,我们设置的有限域集合中元素的个数p能满足 p % 4 == 3, 因此就有 (p+1) % 4 == 0,也就是 (p+1) / 4 正好是一个整数。这里我们另 v = w ^ 2,那么就有:
v = w ^ 2, w % p = v ^ ((p+1)/4) % p

回忆一下比特币使用的椭圆曲线:y ^ 2 = x ^ 3 + 7, 将其与上面等式对应起来就有, v 对应 x ^ 3 + 7, y 对应w, 这样我们有了x之后,先计算x ^ 3 + 7的值,然后在此结果上计算其指数(p+1)/4,最后将所得结果对p求余即可得到y,如果计算所得的y是偶数,同时压缩编码的首字节是0x2,那么就可以直接返回y最为结果,如果首字节是0x3,那么我们就返回p - y,我们看看代码实现:

class S256Field(LimitFieldElement):
   ...

    def sqrt(self):
        return self ** ((P+1) // 4)

class S256Point(EllipticPoint):
    ....
    @classmethod
    def parse(clsse, sec_array):
        """
        如果是非压缩模式,那么直接得出公钥的x,y
        """
        if sec_array[0] == 4:
            x = int.from_bytes(sec_array[1:33], 'big')
            y = int.from_bytes(sec_array[33:65], 'big')
            return S256Point(x=x, y=y)

        # 首字节如果是2那么y的值是偶数,要不然就是奇数
        is_even = (sec_array[0] == 2)
        # 先获得x部分的值
        x = S256Field(int.from_bytes(sec_array[1:], 'big'))
        #计算 y ^ 2的值,也就是 x ^ 3 + 7
        y2 = x ** 3 + S256Field(7)
        # 计算开方,也就是计算 y2 ^ ((P+1)/4)
        y = y2.sqrt()
        if y.num % 2 == 0:
            even_y = y
            odd_y = S256Field(P - even_y.num)
        else:
            even_y = S256Field(P - y.num)
            odd_y = y

        if is_even:
            return S256Point(x, even_y)
        else:
            return S256Point(x, odd_y)

下面我们调用上面代码测试一下效果:

privKey = 0x038109007313a5807b2eccc082c8c3fbb988a973cacf1a7df9ce725c31b14776
pubKey = privKey * G

point = S256Point(pubKey.x.num, pubKey.y.num)
print(f"public key: {point}")
compress_pub_key = point.sec()
print(", ".join(hex(b) for b in compress_pub_key))

recover_pub_key = S256Point.parse(compress_pub_key)
print(f"recover pub key: {recover_pub_key}")

上面代码运行后结果如下:

public key: S256Point(02a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa, 9f3bc9f3948a19dabb796a2a744aae50367ce38a3e6b60ae7d72159caeb0c102)
0x2, 0x2, 0xa4, 0x6, 0x62, 0x42, 0x11, 0xf2, 0xab, 0xbd, 0xc6, 0x8d, 0xa3, 0xdf, 0x92, 0x9f, 0x93, 0x8c, 0x33, 0x99, 0xdd, 0x79, 0xfa, 0xc1, 0xb5, 0x1b, 0xe, 0x4a, 0xd1, 0xd2, 0x6a, 0x47, 0xaa
recover pub key: S256Point(02a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa, 9f3bc9f3948a19dabb796a2a744aae50367ce38a3e6b60ae7d72159caeb0c102)

可以看到我们把公钥压缩后,又能准确的将其还原回来。完成了公钥预处理后所得数据要顺利在网络传输,我们还需要对其进行编码,这里使用的是base58编码,他是我们熟悉的base64的子集,它主要在编码数据时,去掉四个容易混淆的数字和字符,也就是数字 1 和大写字符I,数字 0 和大写字符 O,在网上有大量base58的编码实现,在这里我们给出自己的实现:

#用于base58编码的字符集,这里去掉了数字 1 和大写字母I,数字 0 和大写字母O
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def encode_base58(s):
    count = 0
    for c in s: # 统计输入的二进制数据在开头有多少个 0
        if c == 0:
            count += 1
        else:
            break

    num = int.from_bytes(s, 'big')
    prefix = '1' * count #将开头的数字 0 用字符'1'替换
    result = ''
    while num > 0:
        num, mod = divmod(num, 58)
        result = BASE58_ALPHABET[mod] + result

    return prefix + result

我们把上面的代码调用起来看看:

s_bin = '7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d'
s_bin_base58 = encode_base58(bytes.fromhex(s_bin))
print(f"base58 encode: {s_bin_base58}")

代码运行后所得结果如下:

base58 encode: 9MA8fRQrT4u8Zj8ZRd6MAiiyaxb2Y1CMpvVkHQu5hVM6

有了上面的技术准备后,我们可以开始实现钱包地址的实现。它主要包括以下几个步骤:
1,先获得公钥的预处理结果,可以是压缩格式,也可以是非压缩格式
2,将步骤1的结果先进行sha256计算,然后将计算结果再进行ripemd160哈希计算,这两步运算统称为hash160操作
3,如果要生成主网钱包地址,在步骤2结果前头加上1字节数值0x00,如果是用于测试网络的地址,那么在步骤2前头加1字节0x6f
4,将步骤3的结果连续进行两次的sha256运算,我们把这个过程称为hash256操作,然后取运算结果的前4个字节
5,将步骤4的结果添加到步骤3结果的后面
6,将步骤5的结果进行base58编码

我们看看具体的代码实现:

def hash160(s):
    #先进行sha256哈希,然后再进行ripemd160哈希
    return hashlib.new("ripemd160", hashlib.sha256(s).digest()).digest()

def hash256(s):
    # 连续进行两次sha256运算
    return hashlib.sha256(hashlib.sha256(s).digest()).digest()

def encode_base58_checksum(b):
    return encode_base58(b + hash256(b)[:4])

class S256Point(EllipticPoint):
....
    def hash160(self, compressed=True):
        return hash160(self.sec(compressed))

    def address(self, compressed=True, testnet=False):
        h160 = self.hash160(compressed)
        if testnet:
            prefix = b'\x6f'
        else:
            prefix = b'\x00'

        return encode_base58_checksum(prefix + h160)

我们将上面实现的代码调用起来看看结果:

G = S256Point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
              0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)

privKey = 0x038109007313a5807b2eccc082c8c3fbb988a973cacf1a7df9ce725c31b14776
pubKey = privKey * G

point = S256Point(pubKey.x.num, pubKey.y.num)
print(f"mainnet address is :{point.address(compressed=True,testnet=False)}")

上面代码运行后所得结果如下:

mainnet address is :1PRTTaJesdNovgne6Ehcdu1fpEdX7913CK

也就是说在给定私钥情况下,该钱包对应主网地址为1PRTTaJesdNovgne6Ehcdu1fpEdX7913CK。

更多详实的调试演示,请在b站搜索coding迪斯尼。本节代码下载地址为:

链接: https://pan.baidu.com/s/1mfWpW--FG0jbBSR9Z7OkMg 提取码: jxqi文章来源地址https://www.toymoban.com/news/detail-732208.html

到了这里,关于区块链系统探索之路:钱包地址的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手把手教你使用Java生成助记词、私钥、地址|Java区块链钱包生成助记词、地址

    在spring boot 项目中的 pom.xml文件中加入需要的依赖 可见恢复的地址和我们生成地址一样。

    2024年02月11日
    浏览(51)
  • 区块链钱包“BTC,TRX,ETH”地址生成、助记词导入、keyStron文件生成等功能

    #最近闲着没事做,然后看听到说什么BTC疯涨,这类的小道消息。          哦豁~,这一下就让我觉得非常好奇,说,他们这些东西是怎样在链上存在的呢,然后他们那个一串串的地址又是啥,imtoken钱包又是怎么实现的呢。         根据以上疑问,我自己去研究,自己

    2024年04月14日
    浏览(48)
  • 探索区块链技术的未来之路 - 《区块链指南》

    项目地址:https://gitcode.com/yeasy/blockchain_guide 在数字化的世界里,区块链技术以其去中心化、安全性高和透明度强的特点逐渐崭露头角。如果你对区块链领域充满好奇,或者正在寻找一个全面了解这一技术的资源,《区块链指南》是一个绝佳的学习平台。 《区块链指南》是由知

    2024年04月11日
    浏览(45)
  • 区块链与社交媒体革命:剖析Facebook的探索之路

    在当今数字化时代,社交媒体不仅是人们日常生活的一部分,更是连接世界的桥梁。然而,随着信息时代的发展,隐私泄露、数据滥用等问题愈发凸显,引发了对社交媒体平台的担忧和质疑。正是在这样的背景下,区块链技术作为一种去中心化、不可篡改的技术手段,逐渐被

    2024年04月09日
    浏览(79)
  • 【该文章已被封禁】区块链钱包APP逆向分析及实现

    0、原文链接:点击我进行打开: https://qqizai.gitee.io/qqizai/views/Android_reverse/20210910_BlockchainWalletReverse.html 1、发布了很久的文章,突然间收到信息说,违反社区规定。。。 2、不知道哪里违反了规定哦,我也没指名道姓是什么APP,也没放源码教你们破坏。。。

    2024年02月12日
    浏览(36)
  • Golang区块链钱包_go语言钱包

    Golang区块链钱包的特点 Golang区块链钱包具有以下几个特点: 1. 高性能 Golang是一种编译型语言,具有快速的执行速度和较低的内存消耗。这使得Golang区块链钱包在处理大规模交易数据时表现出色,能够满足高性能的需求。 2. 并发支持 Golang内置了轻量级线程——goroutine,以及

    2024年04月15日
    浏览(62)
  • TronLink钱包连接后如何获取钱包地址

    如果不知道怎么连接钱包就看我前面一个博客(https://blog.csdn.net/HHyuang/article/details/125636089#comments_22333298) 完成,年轻的程序员,大佬勿喷,希望对大家有用,有问题可以留言。

    2024年02月12日
    浏览(41)
  • 猫头虎分享: 探索软件系统架构的革新之路

    博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接 : 🔗 精选专栏 : 《面试题大全》 — 面试准备的宝典! 《IDEA开发秘籍》 — 提升你的IDEA技能! 《100天精通鸿蒙》 — 从Web/安卓到鸿蒙大师! 《100天精通Golang(基础入门篇)》 — 踏入

    2024年02月22日
    浏览(79)
  • 区块链钱包-android篇

    ​ 1:使用Protocol Buffers  首先根目录gradle中添加依赖: classpath \\\"com.google.protobuf:protobuf-gradle-plugin:0.8.3\\\" 然后项目文件中添加plugin,添加依赖包: apply plugin: \\\'com.google.protobuf\\\' protobuf { protoc { artifact = \\\'com.google.protobuf:protoc:3.6.1\\\' } plugins { javalite { artifact = \\\"com.google.protobuf:protoc-gen-jav

    2024年03月09日
    浏览(51)
  • 区块链钱包安全汇总

    一:钱包发生过那些安全事件? 1、“私钥丢失” 部分用户使用钱包时,会习惯性将私钥截图保存在手机,但当手机出现了丢失或者损坏,助记词可能永远无法复原;部分用户有一定安全意识,会将助记词手写一份,但当该份助记词丢失时,助记词也可能无法复原。 2、“伪装

    2024年01月18日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包