[Bitcoin] トランザクションの作成と検証

ノードは、トランザクションを受信すると、各トランザクションがネットワークのルールに従っている事を確認する
トランザクションの検証と呼ぶ

1. トランザクションのインプットを過去に支払っていないない事
2. インプットの合計額がアウトプットの合計額以上になっている事
3. ScriptSigが前のScriptPubKeyのアンロックに成功している事

### インプット支払い状況の確認
どのフルノードでも、UTXOセットを検索すると確認できる

### インプットの合計額とアウトプットの合計額の確認
手数料を求めるメソッド

	def fee(self, testnet=False):
		input_sum, output_sum = 0, 0
		for tx_in in self.tx_ins:
			input_sum += tx_in.value(testnet=testnet)
		for tx_out in self.tx_outs:
			output_suum += tx_out.amount
		return input_sum - output_sum

手数料がマイナスでないかを確認する

from tx import Tx
from io import BytesIO

raw_tx = ('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf830\
3c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88a\
c99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
stream = BytesIO(bytes.fromhex(raw_tx))
transaction = Tx.parse(stream)
print(transaction.fee() >= 0)

### 署名の確認
トランザクションの検証で最も難しい部分は、その署名を確認するプロセス
ECDSA署名アルゴリズムには、公開鍵P、署名ハッシュ、署名(r, s)が必要

from ecc import S256Point, Signature

sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
213bf016b278a')
der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
c8e10615bed')
z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
point = S256Point.parse(sec)
signature = Signature.parse(der)
print(point.verify(z, signature))

インプットごとに異なる署名ハッシュを計算する
1. 全てのScriptSigを空にする
2. 署名されているインプットのScriptSigを前のScriptPubKeyに置き換える
3. ハッシュタイプを付加する

from helper import hash256
from ecc import S256Point, Signature

modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914\
bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\
bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\
e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\
c1943060001000000')
h256 = hash256(modified_tx)
z = int.from_bytes(h256, 'big')
print(hex(z))

sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
213bf016b278a')
der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
c8e10615bed')
z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
point = S256Point.parse(sec)
signature = Signature.parse(der)
print(point.verify(z, signature))

sig_hash, verify_input

    def sig_hash(self, input_index):
        s = int_to_little_endian(self.version, 4)
        s += encode_varint(len(self.tx_ins))
        for i, tx_in in enumerate(self.tx_ins):
        	if i == input_index:
        		s += TxIn(
        				prev_tx=tx_in.prev_tx,
        				prev_index=tx_in.prev_index,
        				script_sig=tx_in.script_pubkey(self.testnet),
        				sequence=tx_in.sequence,
        			).serialize()
        	else:
        		s += TxIn(
        				prev_tx = tx_in.prev_tx,
        				prev_index=tx_in.prev_index,
        				sequence=tx_in.sequence,
        			).serialize()
    	s += encode_varint(len(self.tx_outs))
    	for tx_out in self.tx_outs:
    		s += tx_out.serilize()
    	s += int_to_little_endian(self.locktime, 4)
    	s += int_to_little_endian(SIGHASH_ALL, 4)
    	h256 = hash256(s)
    	return int.from_bytes(h256, 'big')

    def verify_input(self, input_index):
        tx_in = self.tx_ins[input_index]
        script_pubkey = tx_in.script_pubkey(testnet=self.testnet)
        z = self.sig_hash(input_index)
        combined = tx_in.script_sig + script_pubkey
        return combined.evaluate(z)

トランザクション全体の検証

    def verify(self):
        if self.fee() < 0:
            return False
        for i in range(len(self.tx_ins)):
            if not self.verify_input(i):
                return False
        return True

### トランザクションの作成

def decode_base58(s):
	num = 0
	for c in s:
		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]
def p2pkh_script(h160):
    return Script([0x76, 0xa9, h160, 0x88, 0xac])

トランザクションの作成

from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx = bytes.fromhex('0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6\
eb20a080843a299')
prev_index = 13
tx_in = TxIn(prev_tx, prev_index)
tx_outs = []
change_amount = int(0.33*100000000)
change_h160 = decode_base58('mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2')
change_script = p2pkh_script(change_h160)
change_output = TxOut(amount=change_amount, script_pubkey=change_script)
target_amount = int(0.1*100000000)
target_h160 = decode_base58('mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf')
target_script = p2pkh_script(target_h160)
target_output = TxOut(amount=target_amount, script_pubkey=target_script)
tx_obj = Tx(1, [tx_in], [change_output, target_output], 0, True)
print([tx_obj])

[tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11
version: 1
tx_ins:
0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13
tx_outs:
33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVERIFY OP_CHECKSIG
10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVERIFY OP_CHECKSIG
locktime: 0]

### トランザクションへの署名

from ecc import PrivateKey
from helper import SIGHASH_ALL
z = transaction.sig_hash(0)
private_key = PrivateKey(secret=8675309)
der = private_key.sign(z).der()
sig = der + SIGHASH_ALL.to_bytes(1, 'big')
sec = private_key.point.sec()
script_sig = Script([sig, sec])
transaction.tx_ins[0].script_sig = script_sig
print(transaction.serialize().hex())

sign_input

    def sign_input(self, input_index, private_key):
        z = self.sig_hash(input_index)
        der = private_key.sign(z).der()
        sig = der + SIGHASH_ALL.to_bytes(1, 'big')
        sec = private_key.point.sec()
        self.tx_ins[input_index].script_sig = Script([sig, sec])
        return self.verify_input(input_index)

### 自分のトランザクションを作成

from ecc import PrivateKey
from helper import hash256, little_endian_to_int
secret = little_endian_to_int(hash256(b'hpscript secret'))
private_key = PrivateKey(secret)
print(private_key.point.address(testnet=True))

$ python3 main.py
mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk

from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx = bytes.fromhex('75a1c4bc671f55f626dda1074c7725991e6f68b8fcefcfca7\
b64405ca3b45f1c')
prev_index = 1
target_address = 'miKegze5FQNCnGw6PKyqUbYUeBa4x2hFeM'
target_amount = 0.01
change_address = 'mtWT4Tga6ru2HXokJJYSp3wJKGXukTRaFk'
change_amount = 0.009
secret = 98765309
priv = PrivateKey(secret=secret)
tx_ins = []
tx_ins.append(TxIn(prev_tx, prev_index))
tx_outs = []
h160 = decode_base58(target_address)
script_pubkey = p2pkh_script(h160)
target_satoshis = int(target_amount*100000000)
tx_outs.append(TxOut(target_satoshis, script_pubkey))
h160 = decode_base58(change_address)
script_pubkey = p2pkh_script(h160)
change_satoshis = int(change_amount*100000000)
tx_outs.append(TxOut(change_satoshis, script_pubkey))
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
print(tx_obj.sign_input(0, priv))
print(tx_obj.serialize().hex())
from ecc import PrivateKey
from helper import decode_base58, SIGHASH_ALL
from script import p2pkh_script, Script
from tx import TxIn, TxOut, Tx
prev_tx_1 = bytes.fromhex('11d05ce707c1120248370d1cbf5561d22c4f83aeba04367\
92c82e0bd57fe2a2f')
prev_index_1 = 1
prev_tx_2 = bytes.fromhex('51f61f77bd061b9a0da60d4bedaaf1b1fad0c11e65fdc74\
4797ee22d20b03d15')
prev_index_2 = 1
target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
target_amount = 0.0429
secret = 8675309
priv = PrivateKey(secret=secret)
tx_ins = []
tx_ins.append(TxIn(prev_tx_1, prev_index_1))
tx_ins.append(TxIn(prev_tx_2, prev_index_2))
tx_outs = []
h160 = decode_base58(target_address)
script_pubkey = p2pkh_script(h160)
target_satoshis = int(target_amount*100000000)
tx_outs.append(TxOut(target_satoshis, script_pubkey))
tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)
print(tx_obj.sign_input(0, priv))
print(tx_obj.sign_input(1, priv))
print(tx_obj.serialize().hex())

少しずつ見えてきましたね

[Bitcoin] Script

コインをロック/アンロックする機能はビットコインを転送する仕組み(スマートコントラクト)
ロック: ビットコインにエンティティを与える
アンロック: 受け取ったビットコインを支払う

楕円曲線暗号はScriptがトランザクションに適切に使用許可が与えられているか検証する
Scriptはビットコインのスマートコントラクト言語、使用可能な条件を表すためのプログラミング言語
ScriptはForthに似たスタックベース言語、ループ処理できない
L Ethereumはチューリング完全だが、プログラム実行時にgasを支払わなければならない

### Scriptの動作
スタック上の要素を操作する 
コマンドはエレメント(データ)とオペレーション(何らかの操作)
エレメントe.g. signature, pubkey
オペレーションe.g. OP_DUB スタックの先頭エレメントを複製してスタックにプッシュ
OP_CHECKSIG: pubkey, signatureをプップし、有効かどうかを確認する

def op_dup(stack):
    if len(stack) < 1:
        return False
    stack.append(stack[-1])
    return True

    118: op_dup,
def op_hash160(stack):
    if len(stack) < 1:
        return False
    element = stack.pop()
    h160 = hash160(element)
    stack.append(h160)
    return True

### スクリプトのパースとシリアライズ

    def __add__(self, other):
        return Script(self.cmds + other.cmds)

    @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]
            if current_byte >= 1 and current_byte <= 75:
                n = current_byte
                cmds.append(s.read(n))
                count += n
            elif current_byte == 76:
                data_length = little_endian_to_int(s.read(1))
                cmds.append(s.read(data_length))
                count += data_length + 1
            elif current_byte == 77:
                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:
                    result += int_to_little_endian(length, 1)
                elif length > 75 and length < 0x100:
                    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

ビットコインには様々な種類の標準的なスクリプトがある
p2pk, p2pkh, p2sh, p2wpkh, p2wsh

### p2pk
pubkey, OP_CHECHSIG, signature

from script import Script

z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
sec = bytes.fromhex('04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e\
4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34')
sig = bytes.fromhex('3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f4048\
76325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fd\
dbdce6feab601')
script_pubkey = Script([sec, 0xac])
script_sig = Script([sig])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(z))

op_checksig

def op_checksig(stack, z):
    if len(stack) < 2:
        return False
    sec_pubkey = stack.pop()
    der_signature = stack.pop()[:-1]
    try:
        point = S256Point.parse(sec_pubkey)
        sig = Signature.parse(der_signature)
    except (ValueError, SyntaxError) as e:
        return False
    if point.verify(z, sig):
        stack.append(encode_num(1))
    else:
        stack.append(encode_num(0))
    return True
from script import Script

script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87])
script_sig = Script([0x52])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(0))
from script import Script

script_pubkey = Script([0x6e, 0x87, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87])
c1 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
fe017f46dc93a6b67e013b029aaa1db2560b45ca67d688c7f84b8c4c791fe02b3df614f86db169\
0901c56b45c1530afedfb76038e972722fe7ad728f0e4904e046c230570fe9d41398abe12ef5bc\
942be33542a4802d98b5d70f2a332ec37fac3514e74ddc0f2cc1a874cd0c78305a215664613097\
89606bd0bf3f98cda8044629a1'
c2 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
fe017346dc9166b67e118f029ab621b2560ff9ca67cca8c7f85ba84c79030c2b3de218f86db3a9\
0901d5df45c14f26fedfb3dc38e96ac22fe7bd728f0e45bce046d23c570feb141398bb552ef5a0\
a82be331fea48037b8b5d71f0e332edf93ac3500eb4ddc0decc1a864790c782c76215660dd3097\
91d06bd0af3f98cda4bc4629b1'
collision1 = bytes.fromhex(c1)
collision2 = bytes.fromhex(c2)
script_sig = Script([collision1, collision2])
combined_script = script_sig + script_pubkey
print(combined_script.evaluate(0))

[Bitcoin] トランザクション

トランザクションとはエンティティから別のエンティティに移動する事

– トランザクションコンポーネント
バージョン(トランザクション追加機能)
インプット(どのビットコインを使うか)
アウトプット(どこにビットコインを移動するか)
ロックタイム(いつトランザクションが有効になるか)

### バージョン

class Tx:

	def __init__(self, version, tx_ins, tx_outs, locktime, testnet=False):
		self.version = version
		self.tx_ins = tx_ins
		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: {}\nversion: {}\ntx_ins:\n{}tx_outs:\n{}locktime: {}'.format(
			self.id(),
			self.version,
			tx_ins,
			tx_outs,
			self.locktime,
		)

	def id(self):
		return self.hash().hex()

	def hash(self):
		return hash256(self.serialize())[::-1]

	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		return cls(version, None, None, None, testnet=testnet)

### インプット
前のトランザクションのアウトプットを示す
L 前に受け取ったビットコインへの参照
L 支払う本人のビットコインである証明

variable integer = varintを使用する

def read_varint(s):
    i = s.read(1)[0]
    if i == 0xfd:
        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(i):
    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))

インプットフィールド
– 前のトランザクションID, トランザクションインデックス、ScriptSig, シーケンス

class TxIn:
	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,
			)
script_hex = ('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
a')
stream = BytesIO(bytes.fromhex(script_hex))
script_sig = Script.parse(stream)
print(script_sig)

### アウトプット
ビットコインの送信先を定義
varint形式のアウトプット数で開始
フィールドには額とscriptpubkeyがある

TxOut

class tx:
	// 省略
	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		num_inputs = read_varint(s)
		inputs = []
		for _ in range(num_inputs):
			inputs.append(TxIn.parse(s))
		num_outputs = read_variant(s)
		for _ in range(num_outputs):
			outputs.append(TxOut.parse(s))
		return cls(version, inputs, outpute, None, testnet=testnet)

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)

	@classmethod
	def parse(cls, s):
		amount = little_endian_to_int(s.read(8))
		script_pubkey = Script.parse(s)
		return cls(amount, script_pubkey)

### ロックタイム

	@classmethod
	def parse(cls, s, testnet=False):
		version = little_endian_to_int(s.read(4))
		num_inputs = read_varint(s)
		inputs = []
		for _ in range(num_inputs):
			inputs.append(TxIn.parse(s))
		num_outputs = read_variant(s)
		outputs = []
		for _ in range(num_outputs):
			outputs.append(TxOut.parse(s))
		locktime = little_endian_to_int(s.read(4))
		return cls(version, inputs, outpute, locktime, testnet=testnet)
hex_transaction = '010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600'
stream = BytesIO(bytes.fromhex(hex_transaction))
tx_obj = Tx.parse(stream)
print(tx_obj.tx_ins[1].script_sig)
print(tx_obj.tx_outs[0].script_pubkey)
print(tx_obj.tx_outs[1].amount)

$ python3 app.py
304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a71601 035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937
OP_DUP OP_HASH160 ab0c0b2e98b1ab6dbf67d4750b0a56244948a879 OP_EQUALVERIFY OP_CHECKSIG
40000000

### シリアライズ

class tx
	def serialize(self):
		result = int_to_little_endian(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

class TxIn
	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

class TxOut
	def serialize(self):
		result = int_to_little_endian(self.amount, 8)
		result += self.script_pubkey.serialize()
		return result

### トランザクション手数料
マイナーがトランザクションに取り込む手数料がトランザクション手数料となる

class TxIn:
	def fetch_tx(self, testnet=False):
		return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)

	def value(self, testnet=False)
		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

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())
			ecept ValueError:
				raise ValueError('unexpected response: {}'.format(response.text))
			if raw[4] == 0:
				raw = raw[:4] + raw[6:]
				tx = Tx.parse(BytesIO(raw), testnet=testnet)
				tx.locktime = little_endian_to_int(raw[-4:])
			else:
				tx = Tx.parse(BytesIO(raw), testnet=testnet)
			if tx.id() != tx_id:
				raise ValueError('not the same id: {} vs {}'.foramt(tx.id(), tx_id))
				clc.cache[tx_id] = tx
			cls.cache[tx_id].testnet = testnet
			return cls.cache[tx_id]

class Tx:

	def fee(self, testnet=False):
		input_sum, output_sum = 0, 0
		for tx_in in self.tx_ins:
			input_sum += tx_in.value(testnet=testnet)
		for tx_out in self.tx_outs:
			output_suum += tx_out.amount
		return input_sum - output_sum

[Bitcoin] DER署名

– Signatureを圧縮する
– 署名をシリアライズする標準をDERフォーマットと呼ぶ

1. 0x30バイトで開始
2. 署名の残りの長さをエンコード
3. マーカーバイト0x02追加
4. rをビッグエンディアン整数としてエンコード
5. マーカーバイト0x02を追加
6. sをビッグエンディアン整数としてエンコード

class Signature:

	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):
		rbin = self.r.to_bytes(32, byteorder='big')
		rbin = rbin.lstrip(b'\x00')
		if rbin[0] & 0x80:
			rbin = b'\x00' + rbin
		result = byte([2, len(rbin)]) + rbin
		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)]) + sbin
		return bytes([0x30, len(result)]) + result
r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
sig = Signature(r, s)
print(sig.der().hex())

$ python3 app.py
3045022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60221008ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec

### Base58

def encode_base58(s):
    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

h = '7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d'
print(encode_base58(bytes.fromhex(h)))
h = 'eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c'
print(encode_base58(bytes.fromhex(h)))
h = 'c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6'
print(encode_base58(bytes.fromhex(h)))

### アドレス形式
1. メインネットアドレスは先頭を0x00, テストネットは0x6f
2. SECフォーマットを取り出し、sha256とripemd160操作を行う
3. 1のプレフィックスと2のハッシュ操作の結果を結合
4. 3の結果にhash256を行い先頭の4バイトを取得
5. 3と4を結合させてBase58でエンコード

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

def hash160(s):
	return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() 

class S256Point:

	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(priv.point.address(compressed=False, testnet=True))
priv = PrivateKey(2020**5)
print(priv.point.address(compressed=True, testnet=True))
priv = PrivateKey(0x12345deadbeef)
print(priv.point.address(compressed=True, testnet=False))

### WIF(ウォレットインポート形式)
1. メインネット用の秘密鍵は0x80、テストネット用は0xefプレフィックス
2. 秘密鍵を32バイトのビッグエンディアンでエンコード
3. SECフォーマットが圧縮形式の場合、末尾に0x01
4. 123を結合
5. 4の結果にhash256を実行し、先頭の4バイトを取得
6. 4と5を結合させてBase58でエンコード

	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''
		return encode_base58_checksum(prefix + secret_bytes + suffix)
priv = PrivateKey(5003)
print(priv.wif(compressed=True, testnet=True))
priv = PrivateKey(2021**5)
print(priv.wif(compressed=False, testnet=True))
priv = PrivateKey(0x54321deadbeef)
print(priv.wif(compressed=True, testnet=False))

### リトルエンディアン

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')

テストネットアドレスの作成

passphrase = b'info@hpscript.com my secret'
secret = little_endian_to_int(hash256(passphrase))
priv = PrivateKey(secret)
print(priv.point.address(testnet=True))

テストネットのfaucetサイト
https://testnet-faucet.mempool.co/

大分入ってきましたね

[Bitcoin] シリアライズ:非圧縮・圧縮SECフォーマット

楕円曲線暗号の公開鍵は(x,y)形式の一つの座標
ECDSA公開鍵をシリアライズする方法はSECフォーマットと呼ばれている
非圧縮と圧縮がある

1.プレフィックス0x04
2.32バイトのビッグエンディアン整数をx座標に追加
2.32バイトのビッグエンディアン整数をy座標に追加

### 非圧縮

class S256Point(Point):
	// 省略
	def sec(self):
		return b'\x04' + self.x.num.to_bytes(32, 'big')\
			+ self.y.num.to_bytes(32, 'big')
priv = PrivateKey(5002)
print(priv.point.sec(compressed=False).hex())
priv = PrivateKey(2018**5)
print(priv.point.sec(compressed=False).hex())
priv = PrivateKey(0xdeadbeef12345)
print(priv.point.sec(compressed=False).hex())

$ python3 app.py
040f85cb0c917647fadfd31e641231d3a01ac9d3d8a680aab2457e0036bf34d37e6949b302843a33cdf068589c8330bb5c09c1739420d65b7b33cfab6d10118d97
04027f3da1918455e03c46f659266a1bb5204e959db7364d2f473bdf8f0a13cc9dff87647fd023c13b4a4994f17691895806e1b40b57f4fd22581a4f46851f3b06
04d90cd625ee87dd38656dd95cf79f65f60f7273b67d3096e68

### 圧縮

class S256Field(FieldElement):

	def __init__(self, num, prime=None):
		super().__init__(num=num, prime=P)

	def __repr__(self):
		return '{:x}'.format(self.num).zfill(64)

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

	@classmethod
	def parse(self, sec_bin):
		if sec_bin[0] == 4:
			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
		x = S256Field(int.from_bytes(sec_bin[1:], 'big'))
		alpha = x**3 + S256Field(B)
		beta = alpha.sqrt()
		if beta.num % 2 == 0:
			even_beta = beta
			odd_beta = S256Field(P - beta.num)
		else:
			even_beta = S256Field(P - beta.num)
			odd_beta = beta
		if is_even:
			return S256Point(x, even_beta)
		else:
			return S256Point(x, odd_beta)

priv = PrivateKey(5001)
print(priv.point.sec(compressed=True).hex())
priv = PrivateKey(2019**5)
print(priv.point.sec(compressed=True).hex())
priv = PrivateKey(0xdeadbeef54321)
print(priv.point.sec(compressed=True).hex())

$ python3 app.py
0357a4f368868a8a6d572991e484e664810ff14c05c0fa023275251151fe0e53d1
02933ec2d2b111b92737ec12f1c5d20f3233a0ad21cd8b36d0bca7a0cfa5cb8701
0296be5b1292f6c856b3c5654e886fc13511462059089cdf9c479623bfcbe77690

[Bitcoin] 署名の作成

### 署名の作成

e = int.from_bytes(hash256(b'my secret'), 'big') 
z = int.from_bytes(hash256(b'my message'), 'big') # 署名しようとしているメッセージ
k = 1234567890 
r = (k*G).x.num # x座標のみ取り出す
k_inv = pow(k, N-2, N)
s = (z+r*e) * k_inv % N # モジュロ演算
point = e*G # 公開点は知らせる必要がある
print(point)

print(hex(z))
print(hex(r))
print(hex(s))

$ python3 app.py
Point(1153752822844410451703720272380683670327470420405725541447346968941621636178,4935740600172189071191766981850281297554521083605982627869195587800421042658)_0_7 FieldElement(115792089237316195423570985008687907853269984665640564039457584007908834671663)
0x231c6f3d980a6b0fb7152f85cee7eb52bf92433d9919b9c5218cb08e79cce78
0x2b698a0f0a4041b77e63488ad48c23e8e8838dd1fb7520408b121697b782ef22
0xbb14e602ef9e3f872e25fad328466b34e6734b7a0fcd58b1eb635447ffae8cb9

e = 12345
z = int.from_bytes(hash256(b'programming bitcoin'), 'big')
k = 1234567890
r = (k*G).x.num
k_inv = pow(k, N-2, N)
s = (z+r*e) * k_inv % N
point = e*G
print(point)

print(hex(z))
print(hex(r))
print(hex(s))

$ python3 app.py
Point(108607064596551879580190606910245687803607295064141551927605737287325610911759,6661302038839728943522144359728938428925407345457796456954441906546235843221)_0_7 FieldElement(115792089237316195423570985008687907853269984665640564039457584007908834671663)
0x6aac771a641117f22150921737c72c77e73150945342f7a490b77332abaac5c
0x2b698a0f0a4041b77e63488ad48c23e8e8838dd1fb7520408b121697b782ef22
0xd6f075528e124345b4b81029288861d9c33ddc49f8327005dfb203b3e9c1ee9c

### メッセージ署名

from random import randint

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 = randint(0, N-1)
		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)

k は署名ごとに一意である必要がある。
RFC6979

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

これが基本要素なのか
なんか凄いな

[Bitcoin] 公開鍵暗号

P = eG
eは秘密鍵(256ビット)、Pは公開鍵(x, y座標256ビット)

署名アルゴリズムは楕円曲線署名アルゴリズム(Elliptic Curve Digital Signature Algorithm) ECDSA

eG = P
ランダムな256ビットの数字をkとして
kG = R
uG + vP = kG
u,vは0ではない値を署名者が選ぶ GPは既知の数値

-署名ハッシュ
任意のデータを固定サイズのデータにする決定関数
kはランダムな数値を用いて明かさない

ビットこんんはハッシュ関数はhash256(sha256を2回繰り返す)
s = (z + re)/k

### 署名のアルゴリズム
1.署名を(r, s)、署名対称のハッシュをz、署名者の公開鍵をP
2. u = z/s, v = r/s
3. uG + vP = R
4. 点Rのx座標がrと同じであれば署名は有効

z = 0xbc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423
r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
px = 0x04519fac3d910ca7e7138f7013706f619fa8f033e6ec6e09370ea38cee6a7574
py = 0x82b51eab8c27c66e26c858a079bcdf4f1ada34cec420cafc7eac1a42216fb6c4
point = S256Point(px, py)
s_inv = pow(s, N-2, N) # フェルマー小定理
u = z * s_inv % N # u = z/s
v = r * s_inv % N # v = r /s
print((u*G + v*point).x.num == r) # 点Rのx座標がrと同じであれば署名は有効 

$ python3 app.py
True

point = S256Point(
	0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c,
	0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34)

z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60
r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395
s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4
s_inv = pow(s, N-2, N) 
u = z * s_inv % N 
v = r * s_inv % N
print((u*G + v*point).x.num == r)

point = S256Point(
	0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c,
	0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34)

z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c
s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6

s_inv = pow(s, N-2, N) 
u = z * s_inv % N 
v = r * s_inv % N
print((u*G + v*point).x.num == r)
class S256Point(Point):

	def __init__(self, x, y, a=None, b=None):
		a, b = S256Field(A), S256Field(B)
		if type(x) == int:
			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)

	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


class Signature:

	def __init__(self, r, s):
		self.r = r
		self.s = s

	def __repr__(self):
		return 'Signature({:x},{:x})'.format(self.r, self.s)

1. zが与えられており、eG=Pを満たすeが分かっている
2. ランダムにkを選ぶ
3. R = kGとrを算出
4. s = (z+re)/kを算出
5. 署名は(r,s)となる

publickeyは誰にも転送されなければならず、zは検証者が必ず知る必要がある

なんか凄いんだなBitcoinって…

[Bitcoin] secp256k1

### Bitcoinのsecp256k1
a = 0, b = 7 つまり y^2 = x^3 + 7
p = 2^256 – 2^32 ^ 077
Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
(x座標・16進数)
Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
(y座標)
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
– 256ビットで表す
– ビットコインの秘密鍵は途方もない

secp256k1の計算

gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
p = 2**256 - 2**32 - 977
print(gy**2 % p == (gx**3 + 7) % p)
gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
p = 2**256 - 2**32 - 977
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
x = FieldElement(gx, p)
y = FieldElement(gy, p)
seven = FieldElement(7, p)
zero = FieldElement(0, p)
G = Point(x, y, zero, seven)
print(n*G)
class S256Field(FieldElement):

	def __init__(self, num, prime=None):
		super().__init__(num=num, prime=P)

	def __repr__(self):
		return '{:x}'.format(self.num).zfill(64)


class S256Point(Point):

	def __init__(self, x, y, a=None, b=None):
		a, b = S256Field(A), S256Field(B)
		if type(x) == int:
			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)

P = 2**256 - 2**32 - 977
A = 0
B = 7
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
G = S256Point(
	0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
	0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)


print(N*G)

なんか一気に来たな

[Bitcoin] 楕円曲線暗号 スカラー倍算

同じ点を加算することができるため、次のような表記になる
(170,142)+(170,142) = 2(170,142)

この加算は何度も繰り返し行うことができ、スカラー倍算と呼ぶ
計算をせずに予測することが非常に難しい

prime = 223
a = FieldElement(num=0, prime=prime)
b = FieldElement(num=7, prime=prime)
x1 = FieldElement(num=192, prime=prime)
y1 = FieldElement(num=105, prime=prime)
p = Point(x1, y1, a, b)
print(p+p)
x1 = FieldElement(num=143, prime=prime)
y1 = FieldElement(num=98, prime=prime)
p = Point(x1, y1, a, b)
print(p+p)
x1 = FieldElement(num=47, prime=prime)
y1 = FieldElement(num=71, prime=prime)
p = Point(x1, y1, a, b)
print(p+p)
print(p+p+p+p)
print(p+p+p+p+p+p+p+p)
print(p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p)
for s in range(1, 21):
	result = s*p
	print('{}*(47,71)=({},{})'.format(s,result.x.num, result.y.num))

スカラー倍数ははっきりとしたパターンがないため、逆の離散対数は難しい

## 数学の群

prime = 223
a = FieldElement(num=0, prime=prime)
b = FieldElement(num=7, prime=prime)
x = FieldElement(num=15, prime=prime)
y = FieldElement(num=86, prime=prime)
p = Point(x, y, a, b)
inf = Point(None, None, a, b)
product = p
count = 1
while product != inf:
	product += p
	count += 1
print(count)
    def __rmul__(self, coefficient):
        coef = coefficient
        current = self
        result = self.__class__(None, None, self.a, self.b)
        while coef:
            if coef & 1:
                result += current
            current += current
            coef >>= 1
        return result

[Bitcoin] 楕円曲線暗号1

楕円曲線暗号: メッセージの署名と検証

– 実数上の楕円曲線
実数は有理数、無理数を含む数(π, sqrt(2), e +7√19 など)

### 有限体上の楕円曲線
F103 でのy^2 = x^3 + 7
y^2 = 64^2 % 103 = 79
x^3 + 7 = (17^3 + 7) % 103 = 79

有限体に負が存在しないと、y^2の項によって真ん中の線に対して対称になる

a = FieldElement(num=0, prime=223)
b = FieldElement(num=7, prime=223)
x = FieldElement(num=192, prime=223)
y = FieldElement(num=105, prime=223)
p1 = Point(x, y, a, b)
print(p1)

$ python3 app.py
<__main__.Point object at 0x7f1a3ceef2b0>

### 有限体における点の加算

prime = 223
a = FieldElement(num=0, prime=prime)
b = FieldElement(num=7, prime=prime)
x1 = FieldElement(num=192, prime=prime)
y1 = FieldElement(num=105, prime=prime)
x2 = FieldElement(num=17, prime=prime)
y2 = FieldElement(num=56, prime=prime)
p1 = Point(x1, y1, a, b)
p2 = Point(x2, y2, a, b)
print(p1+p2)
prime = 223
a = FieldElement(num=0, prime=prime)
b = FieldElement(num=7, prime=prime)
p1 = Point(FieldElement(170, prime), FieldElement(142, prime), a, b)
p2 = Point(FieldElement(60, prime), FieldElement(139, prime), a, b)
print(p1+p2)
p1 = Point(FieldElement(47, prime), FieldElement(71, prime), a, b)
p2 = Point(FieldElement(17, prime), FieldElement(56, prime), a, b)
print(p1+p2)
p1 = Point(FieldElement(143, prime), FieldElement(98, prime), a, b)
p2 = Point(FieldElement(76, prime), FieldElement(66, prime), a, b)
print(p1+p2)

ほう、なるほどー