秘密鍵が盗まれた時のために、複数署名、マルチシグの仕組みがある
1-of-2だと比較的短いが、5-of-7のようにDER署名が必要になると長くなる
OP_CHECKMULTISIGはm m個の署名、n n個の異なる公開鍵
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(statck.pop())
if len(stack) < m + 1:
return False
der_signatures = []
for _ in range(m)
der_signatures.append(stack.pop()[:-1])
stack.pop()
try:
points = [S256.Point.parse(sec) for sec in sec_pubkeys]
sigs = [Signature.parse(der) for der in der_signatures]
for sig in sigs:
if len(points) == 0:
LOGGER.info("signatures no good or not in right order")
return False
success = False
while points:
point = points.pop(0)
if point.verify(z, sig):
success = True
break
if not success:
return False
stack.append(encode_num(1))
except (ValueError, SyntaxError):
return False
return True
ベアマルチシグは長くなる
### Pay-to-script-hash(p2sh)
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)
from helper import encode_base58_checksum
h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
print(encode_base58_checksum(b'\x05' + h160))
def h160_to_p2pkh_address(h160, testnet=False):
if testnet:
prefix = b'\x6f'
else:
prefix = b'\x00'
return encode_base58_checksum(prefix + h160)
def h160_to_p2sh_address(h160, testnet=False):
if testnet:
prefix = b'\xc4'
else:
prefix = b'\x05'
return encode_base58_checksum(prefix + h160)
p2shの署名検証
-全てのscriptSigを空にする
– 署名対象となるp2shのインプットのScriptSigをRedeemScriptに置き換える
– ハッシュタイプを末尾に追加
from helper import hash256
modified_tx = bytes.fromhex('0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a38\
5aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036\
b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98be\
c453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa0\
5de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b9\
5abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2\
788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687000000000\
1000000')
s256 = hash256(modified_tx)
z = int.from_bytes(s256, 'big')
print(hex(z))
from io import BytesIO from ecc import S256Point, Signature from helper import hash256, int_to_little_endian, encode_varint from script import Script from tx import Tx, TxIn, SIGHASH_ALL hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6\ bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8\ fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4e\ e942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef\ 53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022\ 01475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103\ b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04\ d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f40090000\ 0000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976\ a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da\ 1574e6b3c192ecfb52cc8984ee7b6c568700000000' hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4b\ b71' hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8ee\ f53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402\ 2' hex_redeem_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8ee\ f53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402\ 2' hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca88\ 30bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7f\ bdbd4bb7152ae' sec = bytes.fromhex(hex_sec) der = bytes.fromhex(hex_der) redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script))) stream = BytesIO(bytes.fromhex(hex_tx)) tx_obj = Tx.parse(stream) s = int_to_little_endian(tx_obj.version, 4) s += encode_varint(len(tx_obj.tx_ins)) i = tx_obj.tx_ins[0] s += TxIn(i.prev_tx, i.prev_index, redeem_script, i.sequence).serialize() s += encode_varint(len(tx_obj.tx_outs)) for tx_out in tx_obj.tx_outs: s += tx_out.serialize() s += int_to_little_endian(tx_obj.locktime, 4) s += int_to_little_endian(SIGHASH_ALL, 4) z = int.from_bytes(hash256(s), 'big') point = S256Point.parse(sec) sig = Signature.parse(der) print(point.verify(z, sig))
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:
if redeem_script:
script_sig = redeem_script
else:
script_sig = tx_in.script_pubkey(self.testnet)
else:
s += TxIn(
prev_tx = tx_in.prev_tx,
prev_index=tx_in.prev_index,
script_sig=script_sig,
sequence=tx_in.sequence,
).serialize()
s += encode_varint(len(self.tx_outs))
for tx_out in self.tx_outs:
s += tx_out.serialize()
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)
if script_pubkey.is_p2sh_script_pubkey():
cmd = tx_in.script_sig.cmds[-1]
raw_redeem = encode_varint(len(cmd)) + cmd
redeem_script = Script.parse(BytesIO(raw_redeem))
else:
redeem_script = None
z = self.sig_hash(input_index, redeem_script)
combined = tx_in.script_sig + script_pubkey
return combined.evaluate(z)