区块链编程

ecc.py

import hashlib
  import hmac#hmac算法是一种加入key的hasnlib方法,与普通方法相比更难被碰撞
  from serialization import *
  from helper import *
  import random
  import helper
  class FieldElement:
      '''有限域'''
      def __init__(self,num,prime):
          if num>=prime or num<0:
              error='Num{} not in field range 0 to {}'.format(num,prime-1)
              raise ValueError(error)
          self.num=num
          self.prime=prime
      def __repr__(self):
          return 'FieldElement_{}({})'.format(self.num,self.prime)
      def __eq__(self,other):  #我们通过重载eq方法检测field类中两个对象是否相等
          if other is None:
              return False
          return self.num==other.num and self.prime==other.prime
      def __ne__(self,other):
          if other is None:
              return False
          return self.num!=other.num or self.prime != other.prime or (self.num!=other.num and self.prime != other.prime)
      '''
      模运算的结果总是小于除数,这是一个非常好的性质,我们可以通过模运算将一个非常大的数转换成一个相对较小的数。我们将使用模运算来定义域的运算,
      某种程度上,有限域的大部分运算都使用了模运算。因为如果我们把域的阶作为除数,就可以通过模运算来满足有限域的封闭性
      '''
      def __add__(self,other):
          if self.prime!=other.prime:
              raise TypeError('Cannot add two numbers in different Fields')
          num=(self.num+other.num)%self.prime
          return self.__class__(num,self.prime)
      '''注意和应当是有限域中的一个元素,即它应该是一个对象;所以方法应当返回一可以通过self.__class__()方便获取的类的实例,而我们要做的只是将
      算出来的num和prime传给它'''
      def __sub__(self,other):
          if self.prime!=other.prime:
              raise TypeError('Cannot sub two numbers in different Fields')
          num=(self.num-other.num)%self.prime
          return self.__class__(num,self.prime)
      '''下面给出域运算的乘法。为了满足乘法的封闭性,域的阶数必须是质数。因为如果域的阶数是一个合数,那么当整个集合的元素乘上一个除数k之后就会得到
      一个较小的、不大于k的集合'''
      def __mul__(self,other):
          if self.prime!=other.prime:
              raise TypeError('Cannot mul two numbers in different Fields.')
          num=(self.num*other.num)%self.prime
          return self.__class__(num,self.prime)
      '''下面编写指数运算,注意指数运算幂部分不再要求是一个对象,只需是一个整数。因为指数的幂部分不一定是一个有限域的元素
      这在数学上也是合理的,否则也不会表现出同底数相乘指数相加的现象'''
      def __pow__(self,exponent):
          while exponent<0:
              exponent+=(self.prime-1)#利用费马小定理进行1的代换,加到exponent为正整数为止
          num=pow(self.num,exponent,self.prime)#用pow不用**进一步提高速度
          return self.__class__(num,self.prime)
      '''实际上还有更好的写法,就是用%将负的幂强行转化为正的幂;还可以一并将非常大的指数化为较小的指数
      使得运行pow函数也不会太耗时'''
      def __pow__(self,exponent):
          n=exponent%(self.prime-1)#这句话实际上就是上个方法的while循环。因为循环去加(阶数-1)实际上就是取模的过程
          num=pow(self.num,n,self.prime)
          return self.__class__(num,self.prime)
      '''下面给出难度最大的除法运算,除法看作是乘以一个幂为-1的乘数。由费马小定理1==a^(p-1),这样我们就可以在有限域中进行“1”的代换
      可以证明a^-1==a^(p-2)。这样我们就将除法运算转化为指数运算。
      可以用pow()代替**来进一步提高效率,因为pow方法的第三个参数可以是模'''
      def __truediv__(self,other):
          if (self.prime!=other.prime):
              raise TypeError('Cannot Truediv two numbers in different Fields.')
          num=self.num.__mul__(other.__pow__(self.prime-2))
          return self.__class__(num,self.prime)

  class Point():
      '''ecc中的点'''
      def __init__(self,x,y,a,b):
          self.x=x
          self.y=y
          self.a=a
          self.b=b
          if self.x is None and self.y is None:#这表明该点是个无穷远点
              return
          if self.y**2!=self.x**3+a*x+b:
              raise ValueError('({},{})is not on the curve'.format(x,y))

      def __add__(self, other):
          if self.a != other.a or self.b != other.b:
              raise TypeError('Points {},{} is not on the same curve'.format(self, other))
          '''我们使用坐标为None来表示无穷,而在点加法中无穷远点相当于加法恒等元。所以这是返回自己的值'''
          if self.x is None: return Point.__class__(other.x, other.y, other.a, other.b)
          if other.x is None: return Point.__class__(self.x, self.y, self.a, self.b)
          if (self.x == other.x) and (self.y + other.y == 0):  # 两个点加法互逆的情况
              self.x = None
              self.y = None
              return Point.__class__(self.x, self.y, self.a, self.b)
          '''以上为相同横坐标情况的点加法,下面给出不同横坐标的点加法'''
          if self.x != other.x:
              s = (other.y - self.y) / (other.x - self.x)
              x3 = s ** 2 - self.x - other.x
              self.y = s * (self.x - x3) - self.y
              return Point.__class__(x3, self.y, self.a, self.b)
          """下面给出两个点重合的情况,在计算斜率时需用导数求出切线斜率"""
          if self.x == other.x and self.y == other.y:
              if self.y == 0:
                  return Point.__class__(None, None, self.a, self.b)
              s = (3 * self.x ** 2 + self.a) / 2 * self.y
              x3 = s ** 2 - 2 * self.x
              self.y = s * (self.x - x3) - self.y
              return Point.__class__(x3, self.y, self.a, self.b)
          '''下面考虑切线垂直于x轴的特例,这种情况只会出现于P1=P2且它们的y坐标为0的情况.此时返回无穷远点'''
          if self == other and self.y == 0 * self.x:
              return Point.__class__(None, None, self.a, self.b)
          '''下面我们重载左乘法并希望借此实现标量乘法。我们将product初始化为0,来处理被标量乘法的点是无穷远的点。每次循环都对product加一次被标量乘法的点,共循环cofficient次'''

    def __rmul__(self, coefficient):
        coef = coefficient#把一个很大的数通过二进制的方法进行分解
        current = self
        result = self.__class__(None, None, self.a, self.b)
        while coef:
            if coef & 1:  # 判断最右的比特位是否为1,如果是,result要增加current
                result += current
            current += current  # 对目前的点加倍,这是因为
            coef >>= 1  # coef的比特位右移
        return result
'''现在我们可以结合两个类实现有限域上的椭圆曲线'''
'''下面给出测试组件'''
class ECCTest(Testcase):
    def text_on_curve(self):
        prime=223
        a=FieldElement(0,prime)
        b=FieldElement(7,prime)
        valid_Points=((192,105),(17,56),(1,193))
        invalid_Points=((200,119),(42,99))
        for x_raw,y_raw in valid_Points:
            x=FieldElement(x_raw,prime)
            y=FieldElement(y_raw,prime)
            Point(x,y,a,b)

        for x_raw,y_raw in invalid_Points:
            x=FieldElement(x_raw,prime)
            y=FieldElement(y_raw,prime)
            with self.assertRaises(ValueError):
                Point(x, y, a, b)
'''之前选用的有限循环群的阶数太小,这意味着我们可以通过计算机穷举群的所有元素,从而破解离散对数问题。但我们可以通过选择一个较大的质数,使得计算机不可能遍历群
来提高安全性,下面的参数是比特币采用的曲线使用的参数,非常的大'''
gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
p = 2 ** 256 - 2 ** 32 - 977
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141

#print(gy ** 2 % p == (gx ** 3 + 7) % p)

class S256Field(FieldElement):
    '''secp256k1曲线为比特币采用的曲线'''
    def __init__(self,num,prime=None):
        super().__init__(num=num,prime=p)#我们通过子类来继承FieldElement类
    def __repr__(self):
        return '{:x}'.format(self.num).zfill(64)#我们希望填满64个比特
    def sqrt(self):#secp256k1曲线中的平方根公式,用来在序列化的SEC公钥中计算y
        return self**((p+1)//4)
class S256Point(Point):
    '''我们定义一个secp256k1上的点,提供了代表私钥的公开点'''
    def __init__(self,x,y,a=None,b=None):
        a,b=S256Field(A),S256Field(B)
        if type(x)==int:#此时我们只需再传入x、y即可
            super().__init__(x=S256Field(x),y=S256Field(y),a=a,b=b)
        else:
            super().__init__(x=x,y=y,a=a,b=b)
    def __rmul__(self,coefficient):
        coef=coefficient%N
        return super.__rmul__(coef)
    '''上方我们重新定义了rmul方法,是指更有效率。因为nG=0,所以可以对n取余,即每循环n次就循环回0,或无穷远点'''
    '''下方给出验证方法,total便代表点R'''
    def verify(self,z,sig):
        '''可以用来验证签名是否有效'''
        s_inv=pow(sig.s,N-2,N)
        u=z*s_inv%N
        v=sig.r*s_inv%N
        total=u*G+v*self
        return total.x.num==sig.r
    def sec(self,compressed=True):
        '''sec(高效加密标准)是一种序列化ECDSA公钥的标准,包括未压缩和压缩两种sec格式。
        压缩的原理是可以依据奇偶由x计算出y,因此我们不必对y序列化
        本方法返回两种sec格式的序列化'''
        if compressed:#压缩的SEC格式仅仅占据33字节而非65字节,将会在数百万次交易中节省巨大的空间
            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')
        else:
            return b'\x04'+self.x.num.to_bytes(32,'big')+self.y.num.to_bytes(32,'big')
    '''python中可以用to_bytes方法将数字转换为字节,第一个参数表示他应该占多少字节,第二个参数是字节序'''
    @classmethod
    def parse(self,sec_bin):
        '''本方法用于解析被SEC格式序列化的数据,显然与上面的SEC方法互为逆'''
        '''未压缩的SEC格式包括三部分,共65字节:
                    1.以0x04作为前缀,显然04两个16进制数占1字节
                    2.以大端序整数的形式放入32字节的x轴坐标
                    3.以大端序整数的形式放入32字节的y轴坐标'''
        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 S256Point(x=x,y=y)
        is_even=sec_bin[0]==2#y的奇偶性在第一个字节给出
        x=S256Field(int.from_bytes(sec_bin[1:],'big'))
        #下面解决等式右边的式子
        alpha=x**3+S256Field(B)
        #解决左边的式子
        beta=alpha.sqrt()#对椭圆曲线方程等号右边求平方根可以得到y
        #根据y的奇偶性来决定返回正确的点
        if beta.num%2==0:#因为beta继承了S256Field类所以有.num方法
            even_beta=beta
            odd_beta=S256Field(p-beta.num)#即y有两个根,一个是y,一个是p-y,这里分奇数偶数实现了对两个根的压缩
        else:#beta.num就是点的y值,因为在等式左边所以叫做beta,右边叫做alpha值
            even_beta=S256Field(p-beta.num)
            odd_beta=beta
        if is_even:
            return S256Point(x,even_beta)
        else:
            return S256Point(x,odd_beta)
    def hash160(self,compressed=True):
        return hash160(self.sec(compressed))

'''作为交易,必须有地址。地址可以是类似于公钥的东西。但是SEC格式序列化的公钥长度往往过长,
且二进制的SEC公钥格式难以阅读。这使得我们需要基于公钥构建出具有可读性、不太长、安全的地址,
这种构建过程叫转录,具体方法叫Base58编码。具体编码过程如下:
1.为了满足易于手写不易出错的性质,在Base64的基础上排除掉一些长得非常相似的数字和字母,此
时可用字符总数为58.之后Base58中的每个字符都代表一个数字
2.在结尾增加一个校验和来保证轻松检测到错误'''
#Base58的字母表
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def encode_base58(s):  # s为我们要进行编码的信息
    count = 0
    for c in s:  # 这个循环体目的是计算原公钥前缀有多少个0,最后加回去
        if c == 0:
            count += 1
        else:
            break
    num = int.from_bytes(s, 'big')
    prefix = '1' * count
    result = ''
    while num > 0:  # 这个循环体的目的是判断用Base58中的哪个数字
        num, mod = divmod(num, 58)
        result = BASE58_ALPHABET[mod] + result
        return prefix + result  # 最后统计原公钥前缀的0;

def encode_base58_checksum(b):
    '''本方法一并实现了构建比特币地址的第四步、第五步'''
    return encode_base58(b + hashlib.sha256(b)[:4])

def address(self,compressed=True,testnet=False):
    '''比特币地址的生成步骤:
    1.对于比特币主链地址,采用0x00前缀,测试链采用0x6f
    2.对SEC格式(包括压缩的和未压缩的)做一次sha256运算,再做一次ripemd160运算
    这两次称为一次hash160运算
    3.将步骤1的前缀和步骤2的结果拼接
    4.对步骤3的结果做一次hash256,并取其前四个字节,作为Base58中的校验和
    5.拼接步骤3和步骤4的结果,并用Base58对其编码。'''
    h160=self.hash160(compressed)
    if testnet:
        prefix=b'\x6f'
    else:
        prefix=b'\x00'
    return encode_base58_checksum(prefix+h160)
'''有了本类之后我们可以直接定义比特币的生成元G并保存,以后会经常用到G'''

G=S256Point(gx,gy)

'''以下给出ecc的数字签名过程
显然只有私钥持有者需要提供签名证明自己持有私钥,因为公钥是公开的
对于方程eG=P;我们把e作为私钥,P作为公钥;
我们选定的靶是一个256位的随机数k,并且与kG=R;于是R被替换为新的靶,事实上我们只关心R的横坐标,记为r;我们把r作为随机数
我们同时宣称eG=P与方程uG+vP=kG是等价的,其中k是随机选取的,而u、v是由签名者提供的且不为0,G和P是已知的
这样,对于一个拥有私钥e的人,当给定一个k、u或k、v,可以很快找到另一个参数使方程成立;而对于没有私钥e的人,只能通过穷举的方法使得方程成立
于是我们完成了签名的构造工作,签名的目的时作为一种可靠的方式向外界声明自己是私钥的所有者
签名哈希是包含设计者意图的消息指纹,记为z;我们通过如下方式将z雕刻入签名中,即:
u=z/s,v=r/s。即z作为签名哈希;r与s作为签名;于是我们构造出基础的椭圆曲线数字签名算法,即:
uG+veG=kG,u+ve=k,z/s+re/s=k,s=(z+re)/k'''

'''为了确保我们的签名是32位,采用hash256方法(即两轮sha256),这保证了消息可以转化为定长32位'''

'''以下给出ecc的加密解密过程
    A:加密者,持有公钥;B解密者,持有私钥
    加密:A选一条曲线Ep(a,b)、一个点生成元G、公钥Pa=n*G;并将传递的信息嵌入曲线上一点Pm。对于任意的随机数k,生成一个点对作为密文(kG,Pm+kPa)
    解密:A向B传递密文。B接到密文后,用第二个点减去自己持有的私钥n乘以第一个点,即:
    Pm+kPa-n*kG=Pm.从而实现解密'''
'''我们创建一个签名类来储存r、s'''
class Signature:
    '''用来存储r和s的值'''
    def __init__(self,r,s):
        self.r=r
        self.s=s
    def __repr__(self):
        return 'Signature({:x},{:x})'.format(self.r,self.s)
    def der(self):
        '''签名序列化的标准是DER(可分别编码规则)而非SEC,DER签名格式如下定于:
        1.以0x30字节作为前缀
        2.编码剩余签名的长度(通常是0x44或者0x45)
        3.追加标记字节0x02
        4.以大端序编码r,如果r的第一个字节大于等于0x80,则在r前置0x00,计算r序列化的长度并止于r的编码结果前追加以上内容
        5.追加标记字节0x02
        6.以大端序编码s,如果s的第一个字节大于等于0x80,则在r前置0x00,计算s序列化的长度并止于s的编码结果前追加以上内容
        可见这种编码r和s的方法很是低效,至少有6个字节不必要'''
        rbin=self.r.to_bytes(32,byteorder='big')#remove all null bytes at beginning
        rbin=rbin.lstrip(b'\x00')#if rbin has a high bit,add a \x00
        if rbin[0]&0x80:
            rbin=b'\x00'+rbin
        result=bytes([2,len(rbin)])+rbin#bytes可以将一个数字列表转换为对应的字节
        sbin=self.s.to_bytes(32,byteorder='big')
        sbin=sbin.lstrip(b'\x00')
        if sbin[0]&0x80:
            sbin=b'\x00'+sbin
        result+=bytes([2,len(sbin)])
        return bytes([0x30,len(result)])+result
class PrivateKey:
    '''为了程序化消息签名,本类用来储存私钥'''
    def __init__(self,secret):
        self.secret=secret
        self.point=secret*G#为了以后方便使用,一并保存了公钥
    def hex(self):
        return '{:x}'.format(self.secret).zfill(64)
    def sign(self,z):#用来产生签名的函数
        k=self.deterministic_k(z)
        r=(k*G).x.num
        k_inv=pow(k,N-2,N)
        s=(z+r*self.secret)*k_inv%N
        if s>N/2:
            s=N-s
        return Signature(r,s)
    def deterministic_k(self,z):#用来生成足够随机的随机数
        k=b'\x00'*32
        v=b'\x01'*32
        if z>N:
            z-=N
        z_bytes=z.to_bytes(32,'big')
        secret_bytes=self.secret.to_bytes(32,'big')
        s256=hashlib.sha256
        k=hmac.new(k,v+b'\x00'+secret_bytes+z_bytes,s256).digest()
        v=hmac.new(k,v,s256).digest()#hmac的new方法用来创造哈希对象
        k=hmac.new(k,v+b'\x01'+secret_bytes+z_bytes,s256).digest()
        v=hmac.new(k,v,s256).digest()
        while True:
            v=hmac.new(k,v,s256).digest()
            candidate=int.from_bytes(v,'big')
            if candidate>=1 and candidate<N:
                return candidate
            k=hmac.new(k,v+b'\x00',s256).digest()
            v=hmac.new(k,v,s256).digest()
    def wif(self,compressed=True,testnet=False):
        '''一般我们不需要序列化私钥,这样往往很危险。但在某些情况下,你可能需要
        将私钥从一个钱包传输到另一个钱包,就需要对私钥序列化。WIF(钱包导入格式)
        便是一种序列化私钥的方法。与地址编码一样,WIF使用Base58编码
        下面给出WIF格式的生成步骤:
        1.对于主链的私钥,以0x80作为前缀;测试链用0xef
        2,大端序编码私钥为32字节
        3.如果使用压缩的SEC公钥地址则增加后缀0x01
        4.结合步骤1的前缀和步骤2的序列化私钥以及步骤3的后缀
        5.对步骤4的结果进行hash256,取其前四个字节作为Base58编码的校验和
        6.结合步骤4和步骤5的结果,并使用Base58对其编码'''
        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''
        return encode_base58_checksum(prefix+secret_bytes+suffix)

helper.py

'''helper是包含一些非常有用的工具函数的模块,比如单独使用的单元测试'''
from ecc import *
import hashlib
def little_endian_to_int(b):
    return int.from_bytes(b,'little')
def int_to_little_endian(n,length):
    return n.to_bytes(length,'little')
def hash160(s):
    '''一次sha256运算,之后再做一次ripemd160哈希运算,这两次哈希运算被称为一次hash160运算'''
    return hashlib.new('ripemd160',hashlib.sha256(s).digest()).digest()
def decode_base58(s):
    '''这个工具用于将地址转换到20字节哈希,也就是编码地址的反向计算'''
    num=0
    for c in s: #获取被编码成Base58地址的对应数字
        num*=58
        num+=BASE58_ALPHABET.index(c)
    combined=num.to_bytes(25,byteorder='big') #对数字以大端序编码
    checksum=combined[-4:]
    if hash256(combined[:-4])[:4] != checksum:#校验过程
        raise ValueError('bad address:{}'.format(checksum,hash256(combined[:-4])[:4]))
    return combined[1:-4]#第一个字节代表网格前缀,最后4个字节是校验和,中间的20个字节是实际上20个字节的哈希,也就是hash160
def bits_to_target(bits):
    exponent = bits[-1]
    coefficient = little_endian_to_int(bits[:-1])
    return coefficient * 256**(exponent-3)

tx.py

'''构建Tx的交易类'''
import hashlib
import hmac
from helper import *
class Tx:
    def __init__(self,version,tx_ins,tx_outs,locktime,testnet=False):
        self.version=version
        self.tx_ins=tx_ins#input和output是非常基础的原生组件,我们之后会定义该对象的类型
        self.tx_outs=tx_outs
        self.locktime=locktime
        self.testnet=testnet#我们需要知道交易具体发生在那个网络上,才能完整验证交易
    def __repr__(self):
        tx_ins=''
        for tx_in in self.tx_ins:
            tx_ins +=tx_in.__repr__()+'\n'
        tx_outs=''
        for tx_out in self.tx_outs:
            tx_outs +=tx_out.__repr__()+'\n'
        return 'tx:{}\n version:{}\n tx_ins:{}\n tx_outs:{}\n locktime:{}'.format(self.id(),self.version,
                tx_ins,tx_outs,self.locktime)
    def id(self):#id字段是区块链查找交易时使用的索引,是交易的十六进制格式的hash256运算的结果
        '''Human-readable hexdecimal of the transaction hash'''
        return self.hash().hex()
    def hash(self):
        '''Binary hash of the legacy serialization'''
        return hashlib.sha256(self.serialize())[::-1]#牢记
    '''@classmethod
    def parse(cls,serialization):
        version=serialization[0:4]'''
    @classmethod#与上面的方法相比,我们采用从流中解析,这有助于我们没有获得全部序列化数据之前久开始解析数据,提早发现错误
    def parse(cls,stream):
        serialized_version=stream.read(4)#read方法允许我们实时的解析
    def read_varint(self,s):
        '''本函数用于变长整数解析'''
        i=s.read(1)[0]
        if i==0xfd:
            #变长整数规则规定0xfd前缀后的两个字节(253->2^16-1)以小端序编码数字
            return little_endian_to_int(s.read(2))
        elif i==0xfe:
            return little_endian_to_int(s.read(4))
        elif i==0xff:
            return little_endian_to_int(s.read(8))
        else:
            return i
    def encode_varint(self,i):
        '''encodes an integer as a varint,即序列化数字 '''
        if i<0xfd:
            return bytes([i])
        elif i<0x10000:
            return b'\xfd'+int_to_little_endian(i,2)
        elif i<0x100000000:
            return b'\xfe'+int_to_little_endian(i,4)
        elif i<0x10000000000000000:
            return b'\xff'+int_to_little_endian(i,8)
        else:
            raise ValueError('integer too large'.format(i))
    def serialize(self):
        '''实现序列化'''
        result=int_to_little_endians(self.version,4)
        result+=encode_varint(len(self.tx_ins))#提前用变长整数表明该变长字符有多长
        for tx_in in self.tx_ins:
            result+=tx_in.serialize()
        result+=encode_varint(len(self.tx_outs))
        for tx_out in self.tx_outs:
            result+=tx_out.serialize()
        result+=int_to_little_endian(self.locktime,4)
        return result#在序列化tx类中,我们使用了Txin和Txout类中的serialize方法
    def fee(self):
        '''Return the fee of this transaction in satoshi,通过手续费的正负判断交易是否合法'''
        input_sum,output_sum=0,0
        for tx_in in self.tx_ins:
            input_sum+=tx_in.value(self.testnet)
        for tx_out in self.tx_outs:
            output_sum+=tx_out.amount
        return inout_sum-output_sum
    def verify(self):
        '''Verify this transaction'''
        if self.fee()<0:
            return False
        for i in range(len(self.tx_ins)):
            if not self.verify_input(i):
                return True
    def is_coinbase(self):
        if len(self.tx_ins)!=1:
            return False
        first_input = self.tx_ins[0]
        if first_input.prev_tx!=b'\x00'*32:
            return False
        if first_input.prev_index != 0xffffffff:
            return False
        return True
    def coinbase_height:
        if not self.is_coinbase():
            return None
        element = self.tx_ins[0].script_sig.cmds[0]
        return little_endian_to_int(element)
    '''input由父交易ID、父交易序号、签名脚本、序列号组成,父交易ID是对父交易内容的hash256运算结果,父交易序号指明本input究竟指向父交易
    的哪个output,签名脚本是一个变长字段。序列号是用来处理手续费替代和操作符的小端序。下面我们就可以设计Txin类'''
class TxIn:#需要注意我们没有指明每个input代表多少比特币,除非在区块链上查找我们的父交易
    def __init__(self,prev_tx,prev_index,script_sig=None,sequence=0xffffffff):
        self.prev_tx=prev_tx
        self.prev_index=prev_index
        if script_sig is None:#签名脚本的默认值为空
            self.script_sig=Script()
        else:
            self.script_sig=script_sig
            self.sequence=sequence
    def __repr__(self):
        return '{}{}'.format(self.prev_tx.hex(),self.prev_index)
    def serialize(self):
        '''类中实现交易序列化的方法'''
        result=self.prev_tx[::-1]
        result+=int_to_little_endian(self.prev_index,4)
        result+=self.script_sig.serialize()
        result+=int_to_little_endian(self.sequence,4)
        return result
    def fetch_tx(self,testnet=False):
        return TxFetcher.fetch(self.prev_tx.hex(),testnet=testnet)
    def value(self,testnet=False):#有了TxFetcher类,我们就可以构建用于获取父交易和其output的金额(比特币多少)
        tx=self.fetch_tx(testnet=testnet)
        return tx.tx_outs[self.prev_index].amount
    def script_pubkey(self,testnet=False):#同上,构建用于获取父交易的公钥脚本
        tx=self.fetch_tx(testnet=testnet)
        return tx.tx_outs[self.prev_index].script_pubkey
'''output包含两个字段,一个是amount,以聪为单位,占8个字节;另一个是公钥脚本,类似于Input中的签名脚本,二者均有一个变长的字段,并有一个变长整数
作为前缀表示它的长度。UTXO类代表了流通中的比特币'''
class TxOut:
    def __init__(self,amount,script_pubkey):
        self.amount=amount
        self.script_pubkey=script_pubkey
    def __repr__(self):
        return '{}{}'.format(self.amount,self.script_pubkey)
    def serialize(self):
        '''反过来实现交易的序列化'''
        result=int_to_little_endian(self.amount,8)
        result+=self.script_pubkey.serialize()
        return result
'''交易手续费作为给矿工的报酬,等于totalinput-totaloutput。由上可知input没有金额字段,所以需要我们自己查找金额,而这需要我们获取区块链数据
,尤其是UTXO集,下面我们创建一个新的类解决这个问题'''
class TxFetcher:
    cache={}
    @classmethod
    def get_url(cls,testnet=False):
        if testnet:
            return 'http://testnet.programmingbitcoin.com'
        else:
            return 'http://mainnet.programmingbitcoin.com'
    @classmethod
    def fetch(cls,tx_id,testnet=False,fresh=False):
        if fresh or (tx_id not in cls.cache):
            url='{}/tx/{}.hex'.format(cls.get_url(testnet),tx_id)
    response=requests.get(url)
    try:
        raw=bytes.fromhex(response.text.strip())
    except ValueError:
        raise ValueError('unexpected response:{}'.format(response.text))
    if raw[4]==0:
        raw=raw[:4]+raw[6:]
        tx=Tx.parse(BytesIO(raw),testnet=testnet)
    if tx.id()!=tx_id:#在这里检验ID是为了确保我们想要的获取信息
        raise ValueError('not the same id:{} vs {}'.format(tx.id(),tx_id))
    cls.cache[tx_id]=tx
    cls.cache[tx_id].testnet=testnet
    return cls.cache[tx_id]
    '''我们不愿意相信第三方数据,而是通过获取完整的交易信息,通过交易哈希来确保这是我们想要获取的交易'''

script.py

'''script是一种编程语言,每次只执行一个命令,用来对栈内元素进行计算.命令有两种:元素和操作符'''
import hashlib
from helper import *
def op_dup(stack):
    '''本操作符会复制栈顶一个元素并将其入栈'''
    if(len(stack)<1):#栈顶至少存在一个元素,否则不能执行操作符
        return False
    stack.append(stack[-1])#复制栈顶的一个元素并将其入栈
    return True#我们返回一个布尔值来辨别操作符是否正常运算
def op_hash256(stack):
    if len(stack)<1:
        return False
    element=stack.pop()
    stack.append(hashlib.sha256(element))
    return True
OP_CODE_FUNCTIONS={118:op_dup,170:op_hash256}#118、170分别为这两个操作的操作符
'''实际上栈中的元素和操作符均以字节的形式存储,下面两个函数给出了字节编码和解码的方法'''
def encode_num(num):
    if num==0:
        return b''
    abs_num=abs(num)
    negative=num<0
    result=bytearray()
    while abs_num:
        result.append(abs_num&0xff)
        abs_num>>=8
    if result[-1]&0x80:
        if negative:
            result.append(0x80)
        else:
            result.append(0)
    elif negative:
        result[-1]|=0x80
    return bytes(result)
def decode_num(element):
    if element==b'':
        return 0
    big_endian=element[::-1]
    if big_endian[0]&0x80:
        negative=True
        result=big_endian[0]&0x7f
    else:
        negative=False
        result=big_endian[0]
    for c in big_endian[1:]:
        result<<=8
        result+=c
    if negative:
        return -result
    else:
        return result
def op_0(stack):
    stack.append(encode_num(0))
    return True

class Script:
    def __init__(self,cmds=None):
        if cmds is None:
            self.cmds=[]
        else:
            self.cmds=cmds#每个命令不是一个需要执行的操作符就是一个带压入栈的元素
        '''我们用到的int_to_little_endian工具和little_endian_to_int工具都是之前用到的解析工具'''
    @classmethod
    def parse(cls,s):
        '''本函数是脚本的解析工具'''
        length=read_varint(s)#脚本序列化需要考虑脚本长度
        cmds=[]
        count=0
        while count<length:#一直解析脚本直到消耗完所有字节
            current=s.read(1)#这个字节决定我们解析的是操作符还是元素
            count+=1
            current_byte=current[0]#从字节转换为Python中的整数
            if current_byte>=1 and current_byte<=75:#由规则知,接下来n个字节为元素
                n=current_byte
                cmds.append(s.read(n))
                count+=n
            elif current_byte==76:#76指OP_PUSHDATA1,接下来的一个字节会指出之后元素长度
                data_length=little_endian_to_int(s.read(1))
                cmds.append(s.read(data_length))
                count+=data_length+1
            elif current_byte==77:#77指OP_PUSHDATA2,接下来的两个字节会指出之后的元素长度
                data_length=little_endian_to_int(s.read(2))
                cmds.append(s.read(data_length))
                count+=data_length+2
            else:
                op_code=current_byte
                cmds.append(op_code)
            if count!=length:#至此应完全消耗字节长度,否则会抛出异常
                raise SyntaxError('parsing script failed')
            return cls(cmds)
    def raw_serialize(self):
        '''本函数作为脚本的序列化工具,用来实现编码(即序列化过程)'''
        result=b''
        for cmd in self.cmds:
            if type(cmd)==int:
                result+=int_to_little_endian(cmd,1)
            else:
                length=len(cmd)
                if length <75:#对于长度小于75位的元素,将其编码为1个字节
                    result+=int_to_little_endian(length,1)
                elif length>75 and length<0x100:#长度在[75,255]之间,将其编码为
                    result+=int_to_little_endian(76,1)
                    result+=int_to_little_endian(length,1)
                elif length>=0x100 and length<=520:
                    result+=int_to_little_endian(77,1)
                    result+=int_to_little_endian(length,2)
                else:
                    raise ValueError('too long an cmd!')
                result+=cmd
        return result
    def serialize(self):#实现序列化
        result=self.raw_serialize()
        total=len(result)
        return encode_varint(total)+result
    def __add__(self,other):
        '''通过合并命令集创建一个新对象,从而把签名脚本和公钥脚本合在一起作为计算脚本'''
        return Script(self.cmds+other.cmds)
    def evaluate(self,z):
        '''实际上合并脚本的过程并不完全按照我们的方式,签名脚本和公钥脚本会分开计算,使得二者不会相互影响'''
        cmds=self.cmds[:]#因为命令集会在下面计算过程中发生改变,所以需要单独拷贝一份
        stack=[]
        altstack=[]
        while len(cmds)>0:#持续执行指令,直到指令为空
            cmd=cmds.pop(0)
            if type(cmd)==int:
                operation=OP_CODE_FUNCTIONS[cmd]#执行的操作符位于OP_CODE_FUNCTIONS这个数组内
                if cmd in (99,100):#99和100分别对应OP_IF与OP_NOTIF,他们需要基于栈顶元素对cmds数组进行操作
                    if not operation(stack,cmds):
                        LOGGER.info('bad op:{}'.format(OP_CODE_NAMES[cmd]))
                        return False
                elif cmd in (107,108):#107、108分别对应OP_TOALTSTACK和OP_FROMALTSTACK。这两个命令会在栈和备用栈(altstack)间移动元素
                    if not operation(stack,altstack):
                        LOGGER.info('bad op:{}'.format(OP_CODE_NAMES[cmd]))
                        return False
                elif cmd in(172,173,174,175):#分别对应OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKMULTISIG,OP_CHECKMULTSIGVERIFY
                    if not operation(stack,z):#这些命令需要用到签名哈希z
                        LOGGER.info('bad op:{}'.format(OP_CODE_NAMES[cmd]))
                        return False
                else:
                    if not operation(stack):
                        LOGGER.info('bad op:{}'.format(OP_CODES_NAMES[cmd]))
            else:#如果这些命令不是操作符,那么他们就是元素,所以把元素压入栈中
                stack.append(cmd)
                if len(cmds)==3 and cmds[0]==0xa9 and type(cmds[1]==bytes and len(cmds[1])==20 and cmds[2]==0x87:
                    cmds.pop()
                    h160 = cmds.pop()
                    cmds.pop()
                    if not op_hash160(stack):
                        return False
                    stack.append(h160)
                    if not op_equal(stack):
                        return False
                    if not op_verify(stack):
                        LOGGER.info('bad p2sh h160')
                        return False
                    redeem_script = encode_varint(len(cmd))+cmd
                    stream = BytesIO(redeem_script)
                    cmds.extend(Script.parse(stream).cmds)
        if len(stack)==0:#如果命令全部计算完且栈为空,则脚本运行失败,返回False
            return False
        if stack.pop()==b'':#如果栈顶为空字符串,则脚本运算失败
            return False
        return True#任何其他情况都说明脚本验证有效
    def p2pkh_script(self,h160):
        '''这个工具是根据20个字节哈希生成公钥哈希,
        即根据hash160生成p2pkh'''
        return Script([0x76,0xa9,h160,0x88,0xac])

block.py

from hashlib import *
class Block:
    def __init__(self,version,prev_block,merkle_root,timestamp,bits,nonce):
        self.version=version
        self.prev_block=prev_block
        self.merkle_root=merkle_root
        self.timestamp=timestamp
        self.bits=bits
        self.nonce=nonce
    @classmethod
    def parse(cls,s):
        version = little_endian_to_int(s.read(4))
        prev_block = s.read(32)[::-1]
        merkle_root = s.read(32)[::-1]
        timestamp = little_endian_to_int(s.read(4))
        bits = s.read(4)
        nonce = s.read(4)
        return cls(version,prev_block.merkle_root,timestamp,bits,nonce)
    def serialize(self):
        result = int_to_little_endian(self.version,4)
        result+= self.prev_block[::-1]
        result+=self.merkle_root[::-1]
        result+=int_to_little_endian(self,timestamp,4)
        result+=self.bits
        result+=self.nonce
        return result
    def hash(self):
        s=self.serialize()
        sha=hash256(s)
        return sha[::-1]
    def bip9(self):
        return self.version>>29==0b001
    def bip91(self):
        return self.version>>4&1==1
    def bip141(self):
        return self.version>>1&1==1
    def difficulty(self):
        lowest = 0xffff*256**(0x1d-3)
        return lowest/self.target()
    def check_pow(self):
        sha=hash256(self.serialize())
        proof = little_endian_to_int(sha)
        return proof<self.target()

op.py

def op_checkmultisig(stack,z):
    if len(stack)<1:
        return False
    n=decode_num(stack.pop())
    if len(stack)<n+1:
        return False
    sec_pubkeys=[]
    for _ in range(n):
        sec_pubkeys.append(stack.pop())
    m=decode_num(stack.pop())
    if len(stack)<m+1:
        return False
    der_signatures=[]
    for _ in range(m):
        der_signatures.append(stack.pop()[:-1])
    stack.pop()
    try:
        raise NotImplementedError
    except(ValueError,SyntaxError):
        return False
    return True