def generate_multisig_redeem_script(signatures_required: int, public_key_bytes: List[bytes]) -> bytes: """ Generate the redeem script for the multisig output <signatures_required> <pubkey 1> <pubkey 2> ... <pubkey N> <pubkey_count> <OP_CHECKMULTISIG> :param signatures_required: How many signatures are required to spend the outputs :type signatures_required: int :param public_key_bytes: Array of public keys that created the multisig wallet :type public_key_bytes: List[bytes] :return: The redeem script for the multisig wallet :rtype: bytes """ if signatures_required > settings.MAX_MULTISIG_SIGNATURES: raise ValueError('Signatures required {} is over the limit'.format( signatures_required)) if len(public_key_bytes) > settings.MAX_MULTISIG_PUBKEYS: raise ValueError('PubKeys length {} is over the limit'.format( len(public_key_bytes))) redeem_script = HathorScript() redeem_script.addOpcode( getattr(Opcode, 'OP_{}'.format(signatures_required))) for pubkey_bytes in public_key_bytes: redeem_script.pushData(pubkey_bytes) redeem_script.addOpcode( getattr(Opcode, 'OP_{}'.format(len(public_key_bytes)))) redeem_script.addOpcode(Opcode.OP_CHECKMULTISIG) return redeem_script.data
def test_pushdata(self): stack = [] random_bytes = b'a' * 50 s = HathorScript() s.pushData(random_bytes) op_pushdata(0, s.data, stack) self.assertEqual(random_bytes, stack.pop()) with self.assertRaises(OutOfData): op_pushdata(0, s.data[:-1], stack)
def create_script_with_sigops(nops: int) -> bytes: """ Generate a script with multiple OP_CHECKMULTISIG that amounts to `nops` sigops """ hscript = HathorScript() # each step adds 16 sigops up to `nops`, but not exceding nops for _ in range(nops // 16): hscript.addOpcode(Opcode.OP_16) hscript.addOpcode(Opcode.OP_CHECKMULTISIG) # add `nops % 16` sigops hscript.addOpcode(getattr(Opcode, 'OP_{}'.format(nops % 16))) hscript.addOpcode(Opcode.OP_CHECKMULTISIG) return hscript.data
def generate_multisig_redeem_script(signatures_required: int, public_key_bytes: List[bytes]) -> bytes: """ Generate the redeem script for the multisig output <signatures_required> <pubkey 1> <pubkey 2> ... <pubkey N> <pubkey_count> <OP_CHECKMULTISIG> :param signatures_required: How many signatures are required to spend the outputs :type signatures_required: int :param public_key_bytes: Array of public keys that created the multisig wallet :type public_key_bytes: List[bytes] :return: The redeem script for the multisig wallet :rtype: bytes """ redeem_script = HathorScript() redeem_script.addOpcode( getattr(Opcode, 'OP_{}'.format(signatures_required))) for pubkey_bytes in public_key_bytes: redeem_script.pushData(pubkey_bytes) redeem_script.addOpcode( getattr(Opcode, 'OP_{}'.format(len(public_key_bytes)))) redeem_script.addOpcode(Opcode.OP_CHECKMULTISIG) return redeem_script.data
def test_data_pattern(self): # up to 75 bytes, no Opcode is needed s = HathorScript() re_match = re_compile('^DATA_75$') data = [0x00] * 75 s.pushData(bytes(data)) self.assertEqual(76, len(s.data)) # data_len + data match = re_match.search(s.data) self.assertIsNotNone(match) # for now, we also accept <= 75 bytes with OP_PUSHDATA1 match = re_match.search(bytes([Opcode.OP_PUSHDATA1]) + s.data) self.assertIsNotNone(match) # with more, use OP_PUSHDATA1 s = HathorScript() re_match = re_compile('^DATA_76$') data = [0x00] * 76 s.pushData(bytes(data)) self.assertEqual(78, len(s.data)) # OP_PUSHDATA1 + data_len + data match = re_match.search(s.data) self.assertIsNotNone(match) # test without PUSHDATA1 opcode. Should fail match = re_match.search(s.data[1:]) self.assertIsNone(match) # DATA_ between other opcodes s = HathorScript() re_match = re_compile('^OP_HASH160 (DATA_20) OP_EQUALVERIFY$') data = [0x00] * 20 s.addOpcode(Opcode.OP_HASH160) s.pushData(bytes(data)) s.addOpcode(Opcode.OP_EQUALVERIFY) match = re_match.search(s.data) self.assertIsNotNone(match) # wrong length s = HathorScript() re_match = re_compile('^DATA_20$') data = [0x00] * 20 s.pushData(bytes(data)) s.data = s.data.replace(b'\x14', b'\x15') print(s.data) match = re_match.search(s.data) self.assertIsNone(match)
def test_push_integers(self): # 1 byte s = HathorScript() s.pushData(255) n = get_pushdata(s.data) self.assertEqual(1, len(n)) self.assertEqual(255, binary_to_int(n)) # 2 bytes s = HathorScript() s.pushData(65535) n = get_pushdata(s.data) self.assertEqual(2, len(n)) self.assertEqual(65535, binary_to_int(n)) # 4 bytes s = HathorScript() s.pushData(4294967295) n = get_pushdata(s.data) self.assertEqual(4, len(n)) self.assertEqual(4294967295, binary_to_int(n)) # 8 bytes s = HathorScript() s.pushData(4294967296) n = get_pushdata(s.data) self.assertEqual(8, len(n)) self.assertEqual(4294967296, binary_to_int(n))
def test_get_script_op(self): """ - pushdata, pushdata1, OP_N, OP_X - OutOfData in case pos > data_len (tested in get_data_single_byte?) - make script and iterate on it to make sure each step is returned """ # for opcode test script0 = HathorScript() solution0 = [] # for pushdata stack test script1 = HathorScript() solution1 = [] # for integer stack test script2 = HathorScript() solution2 = [] # for opcode (non OP_N) stack test script3 = HathorScript() for i in range(1, 76): solution0.append(i) solution1.append(b'1' * i) script0.pushData(b'1' * i) script1.pushData(b'1' * i) for i in range(0, 17): opc = getattr(Opcode, 'OP_{}'.format(i)) solution0.append(opc) solution2.append(int(opc) - int(Opcode.OP_0)) script0.addOpcode(opc) script2.addOpcode(opc) for o in [ Opcode.OP_DUP, Opcode.OP_EQUAL, Opcode.OP_EQUALVERIFY, Opcode.OP_CHECKSIG, Opcode.OP_HASH160, Opcode.OP_GREATERTHAN_TIMESTAMP, Opcode.OP_CHECKMULTISIG, Opcode.OP_CHECKDATASIG, Opcode.OP_DATA_STREQUAL, Opcode.OP_DATA_GREATERTHAN, Opcode.OP_FIND_P2PKH, Opcode.OP_DATA_MATCH_VALUE, ]: solution0.append(o) script0.addOpcode(o) script3.addOpcode(o) data0 = script0.data data1 = script1.data data2 = script2.data data3 = script3.data # test opcode recognition pos = i = 0 while pos < len(data0): opcode, pos = get_script_op(pos, data0, None) self.assertEqual(opcode, solution0[i]) i += 1 # test for pushdata stack pos = i = 0 stack = [] while pos < len(data1): opcode, pos = get_script_op(pos, data1, stack) self.assertEqual(stack.pop(), solution1[i]) i += 1 # test for push integer stack pos = i = 0 stack = [] while pos < len(data2): opcode, pos = get_script_op(pos, data2, stack) self.assertEqual(stack.pop(), solution2[i]) i += 1 # test for opcode (non OP_N) stack test pos = i = 0 stack = [] while pos < len(data3): opcode, pos = get_script_op(pos, data3, stack) self.assertEqual(len(stack), 0) i += 1 # try to get opcode outside of script with self.assertRaises(OutOfData): pos = len(data0) get_script_op(pos, data0, None) with self.assertRaises(OutOfData): pos = len(data0) + 1 get_script_op(pos, data0, None)
def test_get_sigops_count(self): multisig_script = MultiSig.create_output_script(BURN_ADDRESS) p2pkh_script = P2PKH.create_output_script(BURN_ADDRESS) redeem_script = HathorScript() redeem_script.addOpcode(Opcode.OP_9) redeem_script.addOpcode(Opcode.OP_CHECKMULTISIG) input_script = HathorScript() input_script.pushData(BURN_ADDRESS) input_script.addOpcode(Opcode.OP_CHECKSIG) input_script.pushData(redeem_script.data) # include redeem_script if output is MultiSig self.assertEqual(get_sigops_count(input_script.data, multisig_script), 10) # if output is not MultiSig, count only input self.assertEqual(get_sigops_count(input_script.data, p2pkh_script), 1) # if no output_script, count only input self.assertEqual(get_sigops_count(input_script.data), 1)
def test_count_sigops(self): script_0 = HathorScript() script_1 = HathorScript() script_10 = HathorScript() script_100 = HathorScript() script_0.addOpcode(Opcode.OP_0) self.assertEqual(count_sigops(script_0.data), 0) # script_1.addOpcode(Opcode.OP_10) script_1.addOpcode(Opcode.OP_CHECKSIG) self.assertEqual(count_sigops(script_1.data), 1) # script_10.addOpcode(Opcode.OP_10) script_10.addOpcode(Opcode.OP_CHECKMULTISIG) self.assertEqual(count_sigops(script_10.data), 10) # for i in range(6): script_100.addOpcode(Opcode.OP_16) script_100.addOpcode(Opcode.OP_CHECKMULTISIG) for i in range(4): script_100.addOpcode(Opcode.OP_CHECKSIG) self.assertEqual(count_sigops(script_100.data), 100)