资讯详情

编出个区块链:数据结构的序列化,看看数字货币如何传输数据

有区块链开发相关工作经验的同学知道,要开发智能合同的应用,首先需要通过geth同步以太坊主网络意味着您需要从其他节点下载大量数据。此外,当使用区块链技术,如支付和接收数字货币时,钱包应用程序需要向对方发送一系列数据,当我们需要通过网络收发数据时,我们需要序列化数据。

我们了解了很多数据结构,如有限组、椭圆曲线、公钥、私钥等,相关数据需要通过网络传输,因此相关数据结构需要序列化。对于椭圆曲线上的一个点,一种称为非压缩SEC(standard for efficient cryptography)的数据格式,其步骤为: 一、开头加0x04作为标志 2.放置点的x坐标值有32字节 3.有32字节的y坐标值。

相应的序列化代码如下:

    def sec(self):         '''         sec压缩在04开头,然后跟随32字节的x坐标值,最后跟随32字节的y坐标值         '''         return b'\x04'   self.x.num.to_bytes(32, 'big')   self.y.num.to_bytes(32, 'big') 

既然有非压缩,就有相应的压缩形式SEC,由于椭圆曲线是x轴对称的,所以给出一个x坐标,它最多对应两个y坐标,两个y相反。但是,由于我们作用在椭圆曲线上的点是有限组中的元素,对于含有p元素的组(注意p是元素),如果y是组中的元素,那么"-y"就是p-y,假如y是偶数,那么p-y相反,如果y是奇数,那么p-y就是偶数。利用这一特点,我们可以压缩y坐标对应的内容。

所以给定椭圆曲线上一点点(x,y),压缩形态SEC生成步骤如下: 1.如果y是奇数,那么0x03开始,如果是偶数,则为0x02开头 2.添加32字节的x坐标值 因此,相应的实现代码是:

 def sec(self, compressed = True):         if compressed:             if self.y.num % 2 == 0:                 return b'\x02'   self.x.num.to_bytes(32, 'big')             else:                 return b'\x03'   self.x.num.to_bytes(32, 'big')         '''         sec将04压缩到开头,然后跟随32字节的x坐标值,最后,跟随32字节的y坐标值         '''         return b'\x04'   self.x.num.to_bytes(32, 'big')   self.y.num.to_bytes(32, 'big') 

与非压缩形式相比,压缩形式的序列化节省了32字节。既然节省了,y,然后接收方需要在收到数据后恢复。请记住椭圆曲线的格式是 y ^ 2 = x^3 a * x b,我们知道了x的值,那意味着知道了y平方的值,现在我们需要计算y的值。

假设w, v它是有限群的元素,有w^2 = v,总共有P组元素。现在我们知道了v,我们需要计算w。如果p % 4 = 3.然后我们有一个好的算法可以快速计算w。由于p % 4 = 3, 于是有(p 1) % 4 = 0。也就是(p 1) 可以整除4,即(p 1) / 4 是整数。根据费马的小定理 w ^ (p-1) % p = 1, 于是有w ^ 2 = w ^ 2 * 1 = w ^ 2 * (W ^ (p-1) ) = w ^ (p 1),因为p是奇数,所以(p 1)可以整除2,即(p 1)/2 是整数,所以有 w = w ^ ((p 1)/2)。

我们上面提到的(p 1)/4是一个整数,所以有 w = w ^ ((p 1)/2) = w ^ (2 * (p 1)/2) = (w ^ 2) ^ ((p 1)/4) = v ^ ((p 1)/4),所以如果 w ^ 2 = v, 并且有 p % 4 = 3, 那么 w = v ^ ((p 1)/4)。椭圆曲线用于比特币,p值得满足p % 4 = 3。对于比特币使用的椭圆曲线,p值得满足p % 4 = 3.这样,我们就把开方运算转化为求指数运算,因此有限群元素开方运算的实现代码是:

P = 2 ** 256 - 2 ** 32 - 977 class BitcoinFieldElement(FieldElemet):  #S256Field     def __init__(self, num, prime = None):         super().__init__(num, P)     def __repr__(self):         return "{:x}".format(self.num).zfill(64)  # 填满64个数字      def sqrt(self):         return self ** ((P 1) // 4) 

让我们来看看如何分析压缩形SEC格式数据,其实现方法为:

    def parse(self, sec_bin): #解析sec压缩数据         if sec_bin[0] == 4: #非压缩sec格式             x = int.from_bytes(sec_bin[1:33], 'big')             y = int.from_bytes(sec_bin[33:65], 'big')             return BitcoinEllipticPoint(x = x, y = y)          #在压缩sec在格式下,先获取x,然后计算y的平方,最后用开方算法获得y的值         is_even = sec_bin[0] == 2         x = BitcoinEllipticPoint(int.from_bytes(sec_bin[1:], 'big'))         #y ^ 2 = x ^3   7         alpha = x ** 3   BitcoinEllipticPoint(B)         beta = alpha.sqrt()         '''         在实数域中,y对应一正一负两个值,如果y和在有限域也是如此(p-y)互为正和负.         也就是说,如果y在有限群中满足椭圆方程,那么p-y椭圆方程也是如此。由于比特币对应于有限组中的元素P,         因此如果y 是偶数,所以p-y是奇数,如果y是奇数,那么p-y是偶数。于是在压缩SEC在格式中,如果开头标志为0x02,         如果计算出的y是奇数,则应使用P-y         '''         if beta.num % 2 == 0: #y是偶数,P-y是奇数             even_beta = beta             odd_beta = BitcoinFieldElement(P - beta.num)         else: #y是奇数,P-y是偶数             even_beta = BitcoinFieldElement(P - beta.num)             odd_beta = beta          if is_even:             return BitcoinEllipticPoint(x, even_beta)         else:             return BitcoinEllipticPoint(x, odd_beta) 

接下来,我们给几个私钥e,通过e * G 获得相应的公钥,然后查看相应的公钥SEC代码如下:

''' 给出以下私钥,要求公钥压缩sec个数: 5001, 2019 ^ 5, 0xdeadbeef54321 ''' priv = PrivateKey(5001) print(priv.point.sec(True))  priv = PrivateKey(2019 ** 5) print(priv.point.sec(True))  priv = PrivateKey(0xdeadbeef54321) print(priv.point.sec(True)) 

上述代码运行后的结果如下:

0357a4f368868a8a6d572991e484e664810ff14c05c0fa023275251151fe0e53d1 02933ec2d2b111b92737ec12f1c5d20f3233a0ad21cd8b36d0bca7a0cfa5cb8701 0296be5b1292f6c856b3c5654e886fc13511462059089cdf9c479623bfcbe77690 

此外,需要序列化的结构是签名。他有两个值需要处理,即s和r,这两个值没有逻辑关联,不能像上面那样压缩。区块链中序列化签名的格式称为DER(Distinguished Encoding sinatures)。DER格式如下: 1,以0x30字节开头 2,添加s和r通常是(0x44和0x45)。 3,添加0x02作为分隔符 4.添加r的长度(一字节)将r转换为大端字节,如果它开头的字节>=0x80,先加0x然后添加r的内容, 5.添加s的长度(1字节),将s转换为大端格式,如果是首字节>=0x然后先添加080x后面跟着s的内容。 因为r是256位数值,所以它最多有32字节,如果32个字节>=0x然后我们需要在它前面添加0x所以r最多有33字节。s同理可以推断。让我们看看代码实现:

class Signature:     def __init__(self, r, s):         self.r = r         self.s = s      def __repr__(self):        return f"Signature({hex(self.r)}, {hex(self.s)})"

    def der(self):
        #现将r转换为大端格式
        rbin = self.r.to_bytes(32, byteorder='big')
        #去掉开始的0x00内容
        rbin = rbin.lstrip(b'\0x00')
        if rbin[0] & 0x80: # 如果头字节>=0x80,在前头添加0x00
            rbin = '\0x00' + rbin
        #以0x2开头,接着是r的长度,最后是r的内容
        result = bytes([2, len(rbin)]) + rbin

        sbin = self.s.to_bytes(32, byteorder='big')
        sbin.lstrip(b'\0x00')
        if sbin[0] & 0x80:
            sbin = b'0x00' + sbin
        result += bytes([2, len(sbin)]) + sbin
        '''
        以0x30开头,跟着是r和s的总长度,然后是r和s的编码
        '''
        print(f"der total bytes:{len(result)}")
        return bytes([0x30, len(result)]) + result

我们运行上面编码试试:

sig = Signature(0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6,
                0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec)
print(f"signature der format:{sig.der().hex()}")

代码运行后结果如下:

signature der format:3048022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60224307830308ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec

由于目前所有编码都是二进制格式,这种格式有个问题就是不利于人的阅读理解。因此比特币后来采用base58来将二进制数据再次进行编码,之所以不用base64是因为后者有限字符容易令人混淆,例如数字0和字母O,小写的l和大写的I,于是使用base58能避免这些问题,我们看看base58编码实现:

'''
base58 的编码字符没有小写的l和大写的I,以及大写的字母O
'''
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def encode_base58(s):
    '''
    先统计要编码的数据以多少个0开头
    '''
    count = 0
    for c in s:
        if c == 0:
            count += 1
        else:
            break

    num = int.from_bytes(s, 'big')
    prefix = '1' * count
    result = ''
    while num > 0:
        num, mod = divmod(num, 58)
        result = BASE58_ALPHABET[mod] + result

    return prefix + result

我们试试上面代码:

'''
测试base58编码
'''
encode = encode_base58(bytes.fromhex('7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d'))
print(f"base58 encode:{encode}")

上面代码运行后得到的编码内容为:

9MA8fRQrT4u8Zj8ZRd6MAiiyaxb2Y1CMpvVkHQu5hVM6

base58编码本身有很多毛病,在市场上已经很少用,只是它某些特定特点正好能在区块链或比特币上用到。在以太坊或比特币应用上,数字货币在转账时需要有对应的接收地址,而这个地址的编码就使用到了base58,我们看看具体流程: 1,如果地址来自主网,那么以0x00开头,如果来自测试网络则以0x6f开头。 2,拿到SEC编码(可以是压缩也可以非压缩),对其进行sha256哈希,然后把结果再次进行ripemd160哈希,这个过程叫hash160操作。 3,把第一和第二步所得结果前后连接起来 4,将第3步结果进行sha256哈希,取开头4个字节 5,将第3和第4步所得结果结合起来,进行base58编码 第5步所得结果也叫校验和,假设我们已经有了第3步的结果,然后看看如何实现第4,5两步,代码如下:

'''
实现地址编码的第4,5两步
'''
def encode_base58_checksum(b):
    return encode_base58(b + hashlib.sha256(b).digest()[:4])

上面提到的hash160操作其实可以直接调用hashlib接口来实现:

def hash160(s):
    return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
'''
在比特币应用中,哈希256都会连续执行两次以增强安全性
'''
def hash256(s):
    return hashlib.sha256(hashlib.sha256(s).digest()).digest()

我们把这些步骤正好到椭圆曲线的点中:

class BitcoinEllipticPoint(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)

我们运行上面实现的逻辑看看:

'''
测试椭圆曲线点的地址编码
'''
priv = PrivateKey(5002)
print(f"address for uncompressed SEC on testnet:{priv.point.address(compressed = False, testnet = True)}")

代码运行后结果如下:

address for uncompressed SEC on testnet:mmTPbXQFxboEtNRkwfh6K51jvdtHLxGeMA

还有一个数据结构需要序列化,那就是私钥。这个东西由于非常敏感,一旦你的私钥丢失,你所有的货币资产就会被别人窃取,因此我们通常不会在网络上传输私钥,但极个别时刻需要这么做,因此我们也需要对私钥进行序列化,它对应的格式叫WIF(Wallet Import Format),它的序列化步骤如下: 1,如果是主网私钥,以0x80开头,如果是测试网以0xef开头 2,将私钥转换为大端字节数组进行编码 3,如果公钥使用压缩SEC格式,那么在末尾添加0x01 4,将1,2,3三个步骤所得结果结合起来 5,将第四步进行sha256(也就是连续两轮256哈希)运算,取结果的前4个字节 6,将步骤4和5结合,使用base58进行编码 我们看看相应的代码实现:

class PrivateKey:
    ....
        def wif(self, compressed = True, testnet = False):
        #先将秘钥转换为大端字节序
        secret_bytes = self.secret.to_bytes(32, 'big')
        if testnet: #根据主网还是测试网添加前缀
            prefix = b'\xef'
        else:
            prefix = b'\x80'

        if compressed: # 根据压缩形态添加后缀
            suffix = b'\x01'
        else:
            suffix = b'\01'

        return encode_base58_checksum(prefix + secret_bytes + suffix)

我们实验一下秘钥的序列化功能:

''
检验秘钥的序列化功能
'''
priv = PrivateKey(5003)
print(f"secret key wif:{priv.wif(True, True)}")

上面代码运行后结果为:

secret key wif:cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN8rFTv2sfUK

比特币应用的代码很混乱,中本聪混淆着使用大端字节序和小端字节序,这是创始人的特权,就像鲁迅写错别字就成为了“通假字”,因此我们还需要了解小端编码的实现:

'''
我们使用一段文字进行256哈希形成秘钥,然后设置秘钥的地址格式
'''
secret_text = "this is my secret key"
secret = little_endian_to_int(hash256(secret_text.encode('utf-8')))
priv = PrivateKey(secret)
print(f"secret key address:{priv.point.address(testnet=True)}")

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

secret key address:mtg8uRgVQHjPiX15mmt7FVZeqSUbkUn3h3

我们这节内容很多,好在逻辑并不复杂,主要就是比较繁琐。代码下载路径:https://github.com/wycl16514/blockchain-serialization.git

标签: 12zj自加热风速传感器mmt330系列油中微水变送器28121zj圆形电连接器插头贴片电容cl05c040cb

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台