区块链编程
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