def T(hex_pubkey: str, is_nonempty: bool, is_fullyvalid: bool, is_compressed: bool ) -> None: key = CPubKey(x(hex_pubkey)) self.assertEqual(key.is_nonempty(), is_nonempty) self.assertEqual(key.is_fullyvalid(), is_fullyvalid) self.assertEqual(key.is_compressed(), is_compressed)
def add_pubkeys(pubkeys): '''Input a list of binary compressed pubkeys and return their sum as a binary compressed pubkey.''' pubkey_list = [CPubKey(x) for x in pubkeys] if not all([x.is_compressed() for x in pubkey_list]): raise ValueError("Only compressed pubkeys can be added.") if not all([x.is_fullyvalid() for x in pubkey_list]): raise ValueError("Invalid pubkey format.") return CPubKey.combine(*pubkey_list)
def test_from_invalid_pubkeys(self): """Create P2PKHBitcoinAddress's from invalid pubkeys""" # first test with accept_invalid=True def T(invalid_pubkey, expected_str_addr): addr = P2PKHBitcoinAddress.from_pubkey(invalid_pubkey, accept_invalid=True) self.assertEqual(str(addr), expected_str_addr) T(x(''), '1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E') T( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c72' ), '1L9V4NXbNtZsLjrD3nkU7gtEYLWRBWXLiZ') # With accept_invalid=False we should get CBitcoinAddressError's with self.assertRaises(CBitcoinAddressError): P2PKHBitcoinAddress.from_pubkey(x('')) with self.assertRaises(CBitcoinAddressError): P2PKHBitcoinAddress.from_pubkey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c72' )) with self.assertRaises(CBitcoinAddressError): P2PKHBitcoinAddress.from_pubkey( CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c72' )))
def test_from_valid_pubkey(self) -> None: """Create P2PKHBitcoinAddress's from valid pubkeys""" def T(pubkey: bytes, expected_str_addr: str) -> None: addr = P2PKHBitcoinAddress.from_pubkey(pubkey) self.assertEqual(str(addr), expected_str_addr) T(x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71'), '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8') T(x('0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455'), '1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T') T(CPubKey(x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71')), '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8') T(CPubKey(x('0478d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71a1518063243acd4dfe96b66e3f2ec8013c8e072cd09b3834a19f81f659cc3455')), '1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T')
def VerifyMessage(address, message, sig): sig = base64.b64decode(sig) hash = message.GetHash() pubkey = CPubKey.recover_compact(hash, sig) return str(P2PKHBitcoinAddress.from_pubkey(pubkey)) == str(address)
def from_unconfidential( cls: Type[T_CCoinConfidentialAddress], unconfidential_adr: CCoinAddress, blinding_pubkey: Union[CPubKey, bytes, bytearray] ) -> T_CCoinConfidentialAddress: """Convert unconfidential address to confidential Raises CConfidentialAddressError if blinding_pubkey is invalid (CConfidentialAddressError is a subclass of CCoinAddressError) unconfidential_adr can be string or CBase58CoinAddress instance. blinding_pubkey must be a bytes instance """ ensure_isinstance(blinding_pubkey, (CPubKey, bytes, bytearray), 'blinding_pubkey') if not isinstance(blinding_pubkey, CPubKey): blinding_pubkey = CPubKey(blinding_pubkey) if not blinding_pubkey.is_fullyvalid(): raise ValueError('invalid blinding pubkey') # without #noqa linter gives warning that we should use isinstance. # but here we want exact match, isinstance is not applicable if type(cls) is not type(unconfidential_adr.__class__): #noqa raise TypeError( 'cannot create {} from {}: this address class might belong ' 'to different chain'.format( cls.__name__, unconfidential_adr.__class__.__name__)) clsmap = { P2PKHCoinAddress: P2PKHCoinConfidentialAddress, P2WPKHCoinAddress: P2WPKHCoinConfidentialAddress, P2SHCoinAddress: P2SHCoinConfidentialAddress, P2WSHCoinAddress: P2WSHCoinConfidentialAddress, } for unconf_cls, conf_cls in clsmap.items(): mapped_cls_list = dispatcher_mapped_list(conf_cls) if mapped_cls_list: if len(mapped_cls_list) != 1: raise TypeError( f"{conf_cls.__name__} must be final dispatch class") chain_specific_conf_cls = mapped_cls_list[0] else: chain_specific_conf_cls = conf_cls if isinstance(unconfidential_adr, unconf_cls) and\ (issubclass(cls, (conf_cls, chain_specific_conf_cls)) or issubclass(chain_specific_conf_cls, cls)): conf_adr = conf_cls.from_bytes(blinding_pubkey + unconfidential_adr) return cast(T_CCoinConfidentialAddress, conf_adr) if issubclass(cls, (conf_cls, chain_specific_conf_cls)): raise TypeError( 'cannot create {} from {}: only subclasses of {} are accepted' .format(cls.__name__, unconfidential_adr.__class__.__name__, unconf_cls.__name__)) raise CConfidentialAddressError( 'cannot create {} from {}: no matching confidential address class'. format(cls.__name__, unconfidential_adr.__class__.__name__))
def snicker_pubkey_tweak(pub, tweak): """ use secp256k1 library to perform tweak. Both `pub` and `tweak` are expected as byte strings (33 and 32 bytes respectively). Return value is also a 33 byte string serialization of the resulting pubkey (compressed). """ base_pub = CPubKey(pub) # convert the tweak to a new pubkey tweak_pub = CKey(tweak, compressed=True).pub return add_pubkeys([base_pub, tweak_pub])
def from_pubkey(cls, pubkey, accept_invalid=False): """Create a P2WPKH address from a pubkey Raises CCoinAddressError if pubkey is invalid, unless accept_invalid is True. The pubkey must be a bytes instance; """ if not isinstance(pubkey, (bytes, bytearray)): raise TypeError( 'pubkey must be bytes or bytearray instance; got %r' % pubkey.__class__) if not accept_invalid: if not isinstance(pubkey, CPubKey): pubkey = CPubKey(pubkey) if not pubkey.is_fullyvalid(): raise P2PKHCoinAddressError('invalid pubkey') pubkey_hash = bitcointx.core.Hash160(pubkey) return cls.from_bytes(pubkey_hash)
def ecies_decrypt(privkey, encrypted): if len(privkey) == 33 and privkey[-1] == 1: privkey = privkey[:32] encrypted = base64.b64decode(encrypted) if len(encrypted) < 85: raise Exception('invalid ciphertext: length') magic = encrypted[:4] if magic != ECIES_MAGIC_BYTES: raise ECIESDecryptionError() ephemeral_pubkey = encrypted[4:37] testR = CPubKey(ephemeral_pubkey) if not testR.is_fullyvalid(): raise ECIESDecryptionError() ciphertext = encrypted[37:-32] mac = encrypted[-32:] ecdh_key = btc.multiply(privkey, ephemeral_pubkey) key = hashlib.sha512(ecdh_key).digest() iv, key_e, key_m = key[0:16], key[16:32], key[32:] if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest(): raise ECIESDecryptionError() return aes_decrypt(key_e, ciphertext, iv=iv)
def ecdh(privkey, pubkey): """ Take a privkey in raw byte serialization, and a pubkey serialized in compressed, binary format (33 bytes), and output the shared secret as a 32 byte hash digest output. The exact calculation is: shared_secret = SHA256(compressed_serialization_of_pubkey(privkey * pubkey)) .. where * is elliptic curve scalar multiplication. See https://github.com/bitcoin/bitcoin/blob/master/src/secp256k1/src/modules/ecdh/main_impl.h for implementation details. """ _, priv = read_privkey(privkey) return CKey(priv).ECDH(CPubKey(pubkey))
def multiply(s, pub, return_serialized=True): '''Input binary compressed pubkey P(33 bytes) and scalar s(32 bytes), return s*P. The return value is a binary compressed public key, or a PublicKey object if return_serialized is False. Note that the called function does the type checking of the scalar s. ('raw' options passed in) ''' try: CKey(s) except ValueError: raise ValueError("Invalid tweak for libsecp256k1 " "multiply: {}".format(bintohex(s))) pub_obj = CPubKey(pub) if not pub_obj.is_fullyvalid(): raise ValueError("Invalid pubkey for multiply: {}".format( bintohex(pub))) privkey_arg = ctypes.c_char_p(s) pubkey_buf = pub_obj._to_ctypes_char_array() ret = secp_lib.secp256k1_ec_pubkey_tweak_mul(secp256k1_context_verify, pubkey_buf, privkey_arg) if ret != 1: assert ret == 0 raise ValueError('Multiplication failed') if not return_serialized: return CPubKey._from_ctypes_char_array(pubkey_buf) return bytes(CPubKey._from_ctypes_char_array(pubkey_buf))
def ecdsa_raw_verify(msg, pub, sig, rawmsg=False): '''Take the binary message msg and binary signature sig, and verify it against the pubkey pub. If rawmsg is True, no sha256 hash is applied to msg before verifying. In this case, msg must be a precalculated hash (256 bit). If rawmsg is False, the secp256k1 lib will hash the message as part of the ECDSA-SHA256 verification algo. Return value: True if the signature is valid for this pubkey, False otherwise. Since the arguments may come from external messages their content is not guaranteed, so return False on any parsing exception. ''' try: if rawmsg: assert len(msg) == 32 newpub = CPubKey(pub) if rawmsg: retval = newpub.verify(msg, sig) else: retval = newpub.verify(Hash(msg), sig) except Exception: return False return retval
def is_valid_pubkey(pubkey, require_compressed=False): """ Returns True if the serialized pubkey is a valid secp256k1 pubkey serialization or False if not; returns False for an uncompressed encoding if require_compressed is True. """ # sanity check for public key # see https://github.com/bitcoin/bitcoin/blob/master/src/pubkey.h if require_compressed: valid_uncompressed = False elif len(pubkey) == 65 and pubkey[:1] in (b'\x04', b'\x06', b'\x07'): valid_uncompressed = True else: valid_uncompressed = False if not ((len(pubkey) == 33 and pubkey[:1] in (b'\x02', b'\x03')) or valid_uncompressed): return False # serialization is valid, but we must ensure it corresponds # to a valid EC point. The CPubKey constructor calls the pubkey_parse # operation from the libsecp256k1 library: dummy = CPubKey(pubkey) if not dummy.is_fullyvalid(): return False return True
def test_get_output_size(self): pub1 = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' )) pub2 = CPubKey( x('02546c76587482cd2468b76768da70c0166ecb2aa2eb1038624f4fedc138b042bc' )) for chainparam in get_params_list(): with ChainParams(chainparam): smpl = get_unconfidential_address_samples(pub1, pub2) # 1 byte for 'no asset', 1 byte for 'no nonce', # 9 bytes for explicit value, # minus 8 bytes of len of bitcoin nValue elements_unconfidential_size_extra = 1 + 1 + 9 - 8 # 33 bytes for asset, 33 bytes for nonce, # 33 bytes for confidential value, # minus 8 bytes of len of bitcoin nValue elements_confidential_size_extra = 33 + 33 + 33 - 8 self.assertEqual(smpl.p2pkh.get_output_size(), 34 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2wpkh.get_output_size(), 31 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2sh.get_output_size(), 32 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2wsh.get_output_size(), 43 + elements_unconfidential_size_extra) self.assertEqual(smpl.conf_p2pkh.get_output_size(), 34 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2wpkh.get_output_size(), 31 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2sh.get_output_size(), 32 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2wsh.get_output_size(), 43 + elements_confidential_size_extra)
def test_get_output_size(self) -> None: pub = CPubKey(x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71')) a0 = P2PKHCoinAddress.from_pubkey(pub) self.assertEqual(P2PKHCoinAddress.get_output_size(), 34) self.assertEqual(a0.get_output_size(), 34) a1 = P2WPKHCoinAddress.from_pubkey(pub) self.assertEqual(P2WPKHCoinAddress.get_output_size(), 31) self.assertEqual(a1.get_output_size(), 31) a2 = P2SHCoinAddress.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) self.assertEqual(P2SHCoinAddress.get_output_size(), 32) self.assertEqual(a2.get_output_size(), 32) a3 = P2WSHCoinAddress.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) self.assertEqual(P2WSHCoinAddress.get_output_size(), 43) self.assertEqual(a3.get_output_size(), 43)
def VerifyMessage(address: P2PKHCoinAddress, message: 'BitcoinMessage', sig: Union[str, bytes], validate_base64: bool = True) -> bool: if isinstance(sig, bytes): sig_b64 = sig.decode('ascii') else: sig_b64 = sig sig_bytes = base64.b64decode(sig_b64, validate=validate_base64) hash = message.GetHash() pubkey = CPubKey.recover_compact(hash, sig_bytes) if pubkey is None: return False return str(P2PKHCoinAddress.from_pubkey(pubkey)) == str(address)
def test_address_implementations(test, paramclasses=None, extra_addr_testfunc=lambda *args: False): pub = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71') ) if paramclasses is None: paramclasses = bitcointx.get_registered_chain_params() for paramclass in paramclasses: with ChainParams(paramclass): def recursive_check(aclass): test.assertTrue(issubclass(aclass, CCoinAddress)) if extra_addr_testfunc(aclass, pub): pass else: a = None if getattr(aclass, 'from_pubkey', None): a = aclass.from_pubkey(pub) elif getattr(aclass, 'from_redeemScript', None): a = aclass.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) else: assert len(dispatcher_mapped_list(aclass)) > 0,\ ("dispatcher mapped list for {} " "must not be empty".format(aclass)) if a is not None: spk = a.to_scriptPubKey() test.assertEqual(a, aclass.from_scriptPubKey(spk)) a2 = aclass.from_bytes(a) test.assertEqual(bytes(a), bytes(a2)) test.assertEqual(str(a), str(a2)) a3 = aclass(str(a)) test.assertEqual(bytes(a), bytes(a3)) test.assertEqual(str(a), str(a3)) for next_aclass in dispatcher_mapped_list(aclass): recursive_check(next_aclass) recursive_check(CCoinAddress)
def from_pubkey(cls: Type[T_P2WPKHCoinAddress], pubkey: Union[CPubKey, bytes, bytearray], accept_invalid: bool = False) -> T_P2WPKHCoinAddress: """Create a P2WPKH address from a pubkey Raises CCoinAddressError if pubkey is invalid, unless accept_invalid is True. The pubkey must be a bytes instance; """ ensure_isinstance(pubkey, (CPubKey, bytes, bytearray), 'pubkey') if not accept_invalid: if not isinstance(pubkey, CPubKey): pubkey = CPubKey(pubkey) if not pubkey.is_fullyvalid(): raise P2PKHCoinAddressError('invalid pubkey') pubkey_hash = bitcointx.core.Hash160(pubkey) return cls.from_bytes(pubkey_hash)
def blinding_pubkey(self) -> CPubKey: assert isinstance(self, bytes), \ "descendant classes must also be bytes subclasses" return CPubKey(self[0:33])
def test_add_sub(self) -> None: k1 = CKey(x('5586e3531b857c5a3d7af6d512ec84161f4531b66daf2ad72a6f647e4164c8ae')) k2 = CKey(x('9e77dd4f6693461578e32e60e9c095023e1fc98ae3eaf0c53f645d53a5ead91e')) k_sum = CKey.add(k1, k2) pub_sum = CPubKey.add(k1.pub, k2.pub) self.assertEqual(pub_sum, k_sum.pub) if secp256k1_has_pubkey_negate: k_diff = CKey.sub(k1, k2) pub_diff = CPubKey.sub(k1.pub, k2.pub) self.assertEqual(pub_diff, k_diff.pub) self.assertEqual(k1, CKey.sub(k_sum, k2)) self.assertEqual(k2, CKey.sub(k_sum, k1)) self.assertEqual(k1, CKey.add(k_diff, k2)) self.assertEqual(k2.negated(), CKey.sub(k_diff, k1)) self.assertEqual(CKey.add(k2, k2), CKey.sub(k_sum, k_diff)) self.assertEqual(k1.pub, CPubKey.sub(pub_sum, k2.pub)) self.assertEqual(k2.pub, CPubKey.sub(pub_sum, k1.pub)) self.assertEqual(k1.pub, CPubKey.add(pub_diff, k2.pub)) self.assertEqual(k2.pub.negated(), CPubKey.sub(pub_diff, k1.pub)) self.assertEqual(CPubKey.add(k2.pub, k2.pub), CPubKey.sub(pub_sum, pub_diff)) self.assertEqual(k1, CKey.combine(k1, k2, k_sum, k2.negated(), k_sum.negated())) self.assertEqual(k1.pub, CPubKey.combine(k1.pub, k2.pub, k_sum.pub, k2.pub.negated(), k_sum.pub.negated())) self.assertEqual(CKey.combine(k_sum, k2, k1, k_diff), CKey.combine(k1, k2, k_sum, k_diff)) self.assertEqual(CPubKey.combine(k_sum.pub, k2.pub, k1.pub, k_diff.pub), CPubKey.combine(k1.pub, k2.pub, k_sum.pub, k_diff.pub)) with self.assertRaises(ValueError): CKey.sub(k1, k1) with self.assertRaises(ValueError): CKey.combine(k1, k2, k1.negated(), k2.negated()) with self.assertRaises(ValueError): CPubKey.sub(k1.pub, k1.pub) with self.assertRaises(ValueError): CPubKey.combine(k1.pub, k2.pub, k1.pub.negated(), k2.pub.negated()) else: logging.basicConfig() log = logging.getLogger("Test_CKey") log.warning('secp256k1 does not export pubkey negation function. ' 'You should use newer version of secp256k1 library. ' 'Tests that involve key substraction are skipped')
def T(hex_pubkey, is_nonempty, is_fullyvalid, is_compressed): key = CPubKey(x(hex_pubkey)) self.assertEqual(key.is_nonempty(), is_nonempty) self.assertEqual(key.is_fullyvalid(), is_fullyvalid) self.assertEqual(key.is_compressed(), is_compressed)
def test(self) -> None: xpriv1 = CCoinExtKey( 'xprv9s21ZrQH143K4TFwadu5VoGfAChTWXUw49YyTWE8SRqC9ZC9AQpHspzgbAcScTmC4MURiMT7pmCbci5oKbWijJmARiUeRiLXYehCtsoVdYf' ) xpriv2 = CCoinExtKey( 'xprv9uZ4jKNZFfGEQTTunEuy2cLQMckzuy5saCmiKuxYJgHX5pGFCx3KQ8mTkSfuLNaWGNQ9LKCg5YzUihxoQv493ErnkcaS3q1udx9X8WZbwZc' ) priv1 = CCoinKey( 'L27zAtDgjDC34sG5ZSey1wvdZ9JyZsNnvZEwbbZYWUYXXQtgri5R') xpub1 = CCoinExtPubKey( 'xpub69b6hm71WMe1PGpgUmaDPkbxYoTzpmswX8KGeinv7SPRcKT22RdMM4416kqtEUuXqXCAi7oGx7tHwCRTd3JHatE3WX1Zms6Lgj5mrbFyuro' ) xpub1.assign_derivation_info( KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))) pub1 = CPubKey( x('03b0fe9cfc88fed9fcecf9dcb7bb5c90dd1a4500f4cfc5c854ffc8e54d639d6bc5' )) kstore = KeyStore( external_privkey_lookup=(lambda key_id, dinfo: priv1 if key_id == priv1.pub.key_id else None), external_pubkey_lookup=(lambda key_id, dinfo: pub1 if key_id == pub1.key_id else None)) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(kstore.get_pubkey(pub1.key_id), pub1) self.assertEqual(kstore.get_pubkey(priv1.pub.key_id), priv1.pub) kstore = KeyStore(xpriv1, priv1, xpub1, pub1, require_path_templates=False) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(kstore.get_pubkey(priv1.pub.key_id), priv1.pub) self.assertEqual(kstore.get_pubkey(pub1.key_id), pub1) # check that no-derivation lookup for (priv, pub) of extended keys # does not return anything if derivation is not supplied, # but returns pubkey when empty path is supplied self.assertEqual(kstore.get_privkey(xpriv1.pub.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m"))), xpriv1.priv) self.assertEqual(kstore.get_pubkey(xpriv1.pub.key_id), None) self.assertEqual( kstore.get_pubkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m"))), xpriv1.pub) # can't find xpub1's pub without derivation self.assertEqual(kstore.get_pubkey(xpub1.pub.key_id), None) # can find with correct derivation info supplied self.assertEqual( kstore.get_pubkey( xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path("m/0"))), xpub1.pub) # but not with incorrect derivation info self.assertEqual( kstore.get_pubkey( xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path("m"))), None) # check longer derivations self.assertEqual( kstore.get_privkey(xpriv1.derive_path("0'/1'/2'").pub.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), xpriv1.derive_path("0'/1'/2'").priv) self.assertEqual( kstore.get_pubkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), xpriv1.derive_path("0'/1'/2'").pub) self.assertEqual( kstore.get_pubkey( xpub1.derive_path("0/1/2").pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0/0/1/2'))), xpub1.derive_path("0/1/2").pub) path = BIP32Path("0'/1'/2'") derived_xpub = xpriv2.derive_path(path).neuter() derived_pub = derived_xpub.derive_path('3/4/5').pub self.assertEqual(kstore.get_pubkey(derived_pub.key_id), None) kstore.add_key(derived_xpub) self.assertEqual( kstore.get_pubkey( derived_pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/0'/1'/2'/3/4/5"))), derived_pub) kstore.add_key(xpriv2) derived_pub = xpriv2.derive_path('3h/4h/5h').pub self.assertEqual( kstore.get_pubkey( derived_pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/3'/4'/5'"))), derived_pub) derived_priv = xpriv2.derive_path('3h/4h/5h').priv self.assertEqual( kstore.get_privkey( derived_priv.pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/3'/4'/5'"))), derived_priv) # check that .remove_key() works kstore.remove_key(xpriv1) kstore.remove_key(xpub1) kstore.remove_key(priv1) kstore.remove_key(pub1) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), None) self.assertEqual(kstore.get_pubkey(pub1.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), None) self.assertEqual( kstore.get_pubkey(xpub1.derive_path("0/1/2").pub.key_id), None)
def test_addresses(self) -> None: pub = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' )) events: Dict[str, Union[threading.Event, asyncio.Event]] events = { 'mainnet': threading.Event(), 'testnet': threading.Event(), 'regtest': threading.Event(), } # list append is thread-safe, can use just the list. finished_successfully = [] def wait(name: str) -> None: evt = events[name] assert isinstance(evt, threading.Event) not_timed_out = evt.wait(timeout=5.0) assert not_timed_out async def wait_async(name: str) -> None: evt = events[name] assert isinstance(evt, asyncio.Event) await asyncio.wait_for(evt.wait(), 5.0) def ready(name: str) -> None: events[name].set() def finish(name: str) -> None: finished_successfully.append(name) def check_core_modules() -> None: # check that mutable/immutable thread-local context works CTransaction().to_mutable().to_immutable() # check secp256k1 error handling (which uses thread-local storage) _secp256k1.secp256k1_ec_pubkey_tweak_add(secp256k1_context_verify, ctypes.c_char_p(0), ctypes.c_char_p(0)) err = secp256k1_get_last_error() assert err['code'] == -2 assert err['type'] == 'illegal_argument' assert 'message' in err def mainnet() -> None: select_chain_params('bitcoin/mainnet') wait('testnet') a = P2PKHCoinAddress.from_pubkey(pub) assert CBase58Data(str(a))[0] == 0 check_core_modules() ready('mainnet') finish('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin') async def async_mainnet() -> None: select_chain_params('bitcoin/mainnet') await wait_async('testnet') a = P2PKHCoinAddress.from_pubkey(pub) assert CBase58Data(str(a))[0] == 0 check_core_modules() ready('mainnet') finish('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin') def testnet() -> None: select_chain_params('bitcoin/testnet') wait('regtest') a = P2SHCoinAddress.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) assert CBase58Data(str(a))[0] == 196 check_core_modules() ready('testnet') wait('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin/testnet') finish('testnet') async def async_testnet() -> None: select_chain_params('bitcoin/testnet') await wait_async('regtest') a = P2SHCoinAddress.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) assert CBase58Data(str(a))[0] == 196 check_core_modules() ready('testnet') await wait_async('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin/testnet') finish('testnet') def regtest() -> None: select_chain_params('bitcoin/regtest') a = P2WPKHCoinAddress.from_pubkey(pub) witver, data = bitcointx.segwit_addr.decode( P2WPKHBitcoinRegtestAddress.bech32_hrp, str(a)) assert witver == 0 assert data == Hash160(pub) check_core_modules() ready('regtest') wait('testnet') wait('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin/regtest') finish('regtest') async def async_regtest() -> None: select_chain_params('bitcoin/regtest') a = P2WPKHCoinAddress.from_pubkey(pub) witver, data = bitcointx.segwit_addr.decode( P2WPKHBitcoinRegtestAddress.bech32_hrp, str(a)) assert witver == 0 assert data == Hash160(pub) check_core_modules() ready('regtest') await wait_async('testnet') await wait_async('mainnet') self.assertEqual(get_current_chain_params().NAME, 'bitcoin/regtest') finish('regtest') assert isinstance(get_current_chain_params(), BitcoinMainnetParams), \ "tests assume bitcoin params in effect by default" mainnet_thread = threading.Thread(target=mainnet) testnet_thread = threading.Thread(target=testnet) regtest_thread = threading.Thread(target=regtest) mainnet_thread.start() testnet_thread.start() regtest_thread.start() mainnet_thread.join() testnet_thread.join() regtest_thread.join() self.assertEqual(set(finished_successfully), set(['mainnet', 'testnet', 'regtest'])) self.assertIsInstance(get_current_chain_params(), BitcoinMainnetParams) if issubclass(ContextVarsCompat, threading.local): logging.basicConfig() log = logging.getLogger("Test_Threading") log.warning( 'contextvars.ContextVar is unavailable, asyncio contexts ' 'when switching chain params will be broken. ' 'Use python >= 3.7 if you want asyncio compatibility, or ' 'just don\'set chainparams in concurrent code.') return finished_successfully = [] events = { 'mainnet': asyncio.Event(), 'testnet': asyncio.Event(), 'regtest': asyncio.Event(), } async def go() -> None: f1 = asyncio.ensure_future(async_mainnet()) f2 = asyncio.ensure_future(async_testnet()) f3 = asyncio.ensure_future(async_regtest()) await asyncio.gather(f1, f2, f3) asyncio.get_event_loop().run_until_complete(go()) self.assertEqual(set(finished_successfully), set(['mainnet', 'testnet', 'regtest'])) self.assertIsInstance(get_current_chain_params(), BitcoinMainnetParams)
def check_blind(self, unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key, asset_commitments=()): input_descriptors = [] for utxo in bundle['vin_utxo']: amount = -1 if utxo['amount'] == -1 else coins_to_satoshi( utxo['amount']) input_descriptors.append( BlindingInputDescriptor( amount=amount, asset=CAsset(lx(utxo['asset'])), blinding_factor=Uint256(lx(utxo['blinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) num_to_blind = 0 output_pubkeys = [] for vout in unblinded_tx.vout: if not vout.nNonce.is_null() and vout.nValue.is_explicit(): output_pubkeys.append(CPubKey(vout.nNonce.commitment)) num_to_blind += 1 else: output_pubkeys.append(CPubKey()) tx_to_blind = unblinded_tx.to_mutable() blind_issuance_asset_keys = [] blind_issuance_token_keys = [] for vin in blinded_tx.vin: issuance = vin.assetIssuance if not issuance.is_null(): issuance_blinding_script = CScript( [OP_RETURN, vin.prevout.hash, vin.prevout.n]) blind_issuance_key = issuance_blinding_script.derive_blinding_key( blinding_derivation_key) if issuance.nAmount.is_commitment(): blind_issuance_asset_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_asset_keys.append(None) if issuance.nInflationKeys.is_commitment(): blind_issuance_token_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_token_keys.append(None) else: blind_issuance_asset_keys.append(None) blind_issuance_token_keys.append(None) # Deterministic random was used when generating test transactions, # to have reproducible results. We need to set the random seed # to the same value that was used when test data was generated. # (see note below on that supplying _rand_func parameter to blind() # is intended only for testing code, not for production) random.seed(bundle['rand_seed']) def rand_func(n): return bytes([random.randint(0, 255) for _ in range(n)]) # Auxiliary generators will be be non-empty only for the case # when we are blinding different transaction templates that is # then combined into one common transaction, that is done in # test_split_blinding_multi_sign(). # In this case, you need to supply the asset commitments for # all of the inputs of the final transaction, even if currently # blinded transaction template does not contain these inputs. blind_result = tx_to_blind.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, # IMPORTANT NOTE: # Specifying custom _rand_func is only required for testing. # Here we use it to supply deterministically generated # pseudo-random bytes, so that blinding results will match the test # data that was generated using deterministically generated random # bytes, with seed values that are saved in 'rand_seed' fields of # test data bunldes. # # In normal code you do should NOT specify _rand_func: # os.urandom will be used by default (os.urandom is suitable for cryptographic use) _rand_func=rand_func) self.assertFalse(blind_result.error) if all(_k is None for _k in blind_issuance_asset_keys): random.seed(bundle['rand_seed']) tx_to_blind2 = unblinded_tx.to_mutable() blind_result2 = tx_to_blind2.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, _rand_func=rand_func) self.assertFalse(blind_result2.error) self.assertEqual(blind_result, blind_result2) self.assertEqual(tx_to_blind.serialize(), tx_to_blind2.serialize()) self.assertEqual(blind_result.num_successfully_blinded, num_to_blind) self.assertNotEqual(unblinded_tx_raw, tx_to_blind.serialize()) self.assertEqual(blinded_tx_raw, tx_to_blind.serialize())
def test_path_template_enforcement(self) -> None: xpriv1 = CCoinExtKey( 'xprv9s21ZrQH143K4TFwadu5VoGfAChTWXUw49YyTWE8SRqC9ZC9AQpHspzgbAcScTmC4MURiMT7pmCbci5oKbWijJmARiUeRiLXYehCtsoVdYf' ) xpriv2 = CCoinExtKey( 'xprv9s21ZrQH143K3QgBvK4tkeHuvuWc6KETTTcgGQ4NmW7g16AtCPV4hZpujiimpLM9ivFPgsMdNNVuVUnDwChutxczNKYHzP1Mo5HuqG7CNYv' ) assert xpriv2.derivation_info assert len(xpriv2.derivation_info.path) == 0 priv1 = CCoinKey( 'L27zAtDgjDC34sG5ZSey1wvdZ9JyZsNnvZEwbbZYWUYXXQtgri5R') xpub1 = CCoinExtPubKey( 'xpub69b6hm71WMe1PGpgUmaDPkbxYoTzpmswX8KGeinv7SPRcKT22RdMM4416kqtEUuXqXCAi7oGx7tHwCRTd3JHatE3WX1Zms6Lgj5mrbFyuro' ) xpub2 = xpriv2.derive(333).neuter() xpub1.assign_derivation_info( KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))) pub1 = CPubKey( x('03b0fe9cfc88fed9fcecf9dcb7bb5c90dd1a4500f4cfc5c854ffc8e54d639d6bc5' )) xpub3 = xpub1.derive(0) xpub3.assign_derivation_info( KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0'))) # No error when require_path_templates is not set KeyStore(xpriv1, xpriv2, priv1, xpub1, pub1, require_path_templates=False) with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): KeyStore((priv1, BIP32PathTemplate(''))) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): KeyStore((pub1, [BIP32PathTemplate('')])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates must be specified'): KeyStore(xpriv1) with self.assertRaisesRegex(ValueError, 'path templates must be specified'): KeyStore(xpub1) # same but via add_key ks = KeyStore() with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((priv1, BIP32PathTemplate(''))) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((pub1, [BIP32PathTemplate('')])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates list is empty'): ks.add_key((pub1, [])) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((pub1, '')) # type: ignore with self.assertRaisesRegex(ValueError, 'index template format is not valid'): ks.add_key((xpub1, 'abc')) # type: ignore with self.assertRaisesRegex(TypeError, 'is expected to be an instance of '): ks.add_key((xpub1, [10])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates must be specified'): ks.add_key(xpriv1) with self.assertRaisesRegex(ValueError, 'path templates must be specified'): ks.add_key(xpub1) # No error when path templates are specified for extended keys ks = KeyStore( (xpriv1, BIP32PathTemplate('m')), (xpriv2, 'm/[44,49,84]h/0h/0h/[0-1]/*'), (xpub1, ''), # '' same as BIP32PathTemplate('') (xpub2, ['0/1', 'm/333/3/33']), (xpub3, BIP32PathTemplate('m/0/0/1')), priv1, pub1) self.assertEqual(ks.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(ks.get_pubkey(pub1.key_id), pub1) # still can find non-extended priv even if derivation info is # specified, because there's exact match. self.assertEqual( ks.get_privkey(priv1.pub.key_id, KeyDerivationInfo(xpriv1.parent_fp, BIP32Path("m"))), priv1) self.assertEqual(ks.get_pubkey(pub1.key_id), pub1) # can't find without derivation specified self.assertEqual(ks.get_privkey(xpriv1.pub.key_id), None) # but can find with derivation specified self.assertEqual( ks.get_privkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path('m'))), xpriv1.priv) # can't find without derivation specified self.assertEqual(ks.get_pubkey(xpub1.pub.key_id), None) # can find with derivation specified self.assertEqual( ks.get_pubkey(xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))), xpub1.pub) # exception when derivation goes beyond template with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub1.derive(1).pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0/1'))) # success when template allows self.assertEqual( ks.get_pubkey( xpub3.derive(1).pub.key_id, KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0/1'))), xpub3.derive(1).pub) # fails when template not allows with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub3.derive(2).pub.key_id, KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0/2'))) long_path = BIP32Path( "m/43435/646/5677/5892/58885/2774/9943/75532/8888") with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path(long_path).pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, long_path)) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/3/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/3/25'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/0/1'").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/0/1h'))) self.assertEqual( ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/25'))), xpriv2.derive_path("44'/0'/0'/1/25").priv) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub2.derive_path('0').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/0'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub2.derive_path('3/34').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/3/34'))) self.assertEqual( ks.get_pubkey( xpub2.derive_path('3/33').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/3/33'))), xpub2.derive_path('3/33').pub) xpub49 = xpriv2.derive_path("m/49'/0'/0'/0").neuter() with self.assertRaisesRegex(ValueError, 'must specify full path'): ks = KeyStore( xpriv2, xpub49, default_path_template='[44,49,84]h/0h/0h/[0-1]/[0-50000]') ks = KeyStore( xpriv2, xpub49, default_path_template='m/[44,49,84]h/0h/0h/[0-1]/[0-50000]') with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path(long_path).pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, long_path)) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/50001").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/50001'))) self.assertEqual( ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/25'))), xpriv2.derive_path("44'/0'/0'/1/25").priv) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub49.derive_path('50001').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50001'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub49.derive_path('50000/3').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50000/3'))) self.assertEqual( ks.get_pubkey( xpub49.derive_path('50000').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50000'))), xpub49.derive_path('50000').pub)
def claim_funds_back(say, utxos, die, rpc): """Try to claim our funds by sending our UTXO to our own addresses""" # The transaction-building code here does not introduce anything new # compared to the code in participant functions, so it will not be # commented too much. input_descriptors = [] # It is better to prepare the claw-back transaction beforehand, to avoid # the possibility of unexpected problems arising at the critical time when # we need to send claw-back tx ASAP, but that would clutter the earlier # part of the example with details that are not very relevant there. tx = CMutableTransaction() for utxo in utxos: tx.vin.append( CTxIn(prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout']))) input_descriptors.append( BlindingInputDescriptor( asset=CAsset(lx(utxo['asset'])), amount=coins_to_satoshi(utxo['amount']), blinding_factor=Uint256(lx(utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) asset_amounts = {} # If some assets are the same, we want them to be sent to one address for idesc in input_descriptors: if idesc.asset == fee_asset: amount = idesc.amount - FIXED_FEE_SATOSHI assert amount >= FIXED_FEE_SATOSHI # enforced at find_utxo_for_fee else: amount = idesc.amount asset_amounts[idesc.asset] = amount output_pubkeys = [] for asset, amount in asset_amounts.items(): dst_addr, _ = get_dst_addr(None, rpc) tx.vout.append( CTxOut(nValue=CConfidentialValue(amount), nAsset=CConfidentialAsset(asset), scriptPubKey=dst_addr.to_scriptPubKey())) output_pubkeys.append(dst_addr.blinding_pubkey) # Add the explicit fee output tx.vout.append( CTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # We used immutable objects for transaction components like CTxIn, # just for our convenience. Convert them all to mutable. tx = tx.to_immutable().to_mutable() # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys) assert (not blind_result.error and blind_result.num_successfully_blinded == len(utxos)) for n, utxo in enumerate(utxos): sign_input(tx, n, utxo) # It is possible that Bob has actually sent the swap transaction. # We will get an error if our node has received this transaction. # In real application, we might handle this case, too, but # here we will just ignore it. txid = rpc.sendrawtransaction(b2x(tx.serialize())) rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc)
def alice(say, recv, send, die, rpc): """A function that implements the logic of the first participant of an asset atomic swap""" # Issue two asset that we are going to swap to Bob's 1 asset asset1_str, asset1_utxo = issue_asset(say, 1.0, rpc) asset2_str, asset2_utxo = issue_asset(say, 1.0, rpc) # We will need to pay a fee in an asset suitable for this fee_utxo = find_utxo_for_fee(say, die, rpc) say('Getting change address for fee asset') # We don't care for blinding key of change - the node will # have it, anyway, and we don't need to unblind the change. fee_change_addr, _ = get_dst_addr(say, rpc) say('Will use utxo {}:{} (amount: {}) for fee, change will go to {}'. format(fee_utxo['txid'], fee_utxo['vout'], fee_utxo['amount'], fee_change_addr)) say('Setting up communication with Bob') # Tell Bob that we are ready to communicate send('ready') # To avoid mempool synchronization problems, # in our example Alice is the one in charge of generating test blocks. # Bob gives alice txid of his transaction that he wants to be confirmed. bob_txid = recv('wait-txid-confirm') # Make sure asset issuance transactions are confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, asset1_utxo['txid'], die, rpc) wait_confirm(say, asset2_utxo['txid'], die, rpc) wait_confirm(say, bob_txid, die, rpc) # Make sure Bob is alive and ready to communicate, and send # him an offer for two assets say('Sending offer to Bob') my_offers = [ AtomicSwapOffer(asset=asset1_str, amount=coins_to_satoshi(asset1_utxo['amount'])), AtomicSwapOffer(asset=asset2_str, amount=coins_to_satoshi(asset2_utxo['amount'])) ] send('offer', my_offers) bob_offer = recv('offer') print_asset_balances(say, my_offers + [bob_offer], rpc) say('Bob responded with his offer: {}'.format(bob_offer)) # We unconditionally accept Bob's offer - his asset is # equally worthless as ours :-) # Generate an address for Bob to send his asset to. dst_addr, blinding_key = get_dst_addr(say, rpc) say('Sending my address and assetcommitments for my UTXOs to Bob') # Send Bob our address, and the assetcommitments of our UTXOs # (but not any other information about our UTXO), # so he can construct and blind a partial transaction that # will spend his own UTXO, to send his asset to our address. assetcommitments = [ asset1_utxo['assetcommitment'], asset2_utxo['assetcommitment'], fee_utxo['assetcommitment'] ] send('addr_and_assetcommitments', (str(dst_addr), assetcommitments)) partial_tx_bytes = recv('partial_blinded_tx') say('Got partial blinded tx of size {} bytes from Bob'.format( len(partial_tx_bytes))) partial_tx = CTransaction.deserialize(partial_tx_bytes) if len(partial_tx.vout) != 1: die('unexpected number of outputs in tx from Bob: expected 1, got {}'. format(len(partial_tx.vout))) result = partial_tx.vout[0].unblind_confidential_pair( blinding_key, partial_tx.wit.vtxoutwit[0].rangeproof) if result.error: die('cannot unblind output that should have been directed to us: {}'. format(result.error)) if result.asset.to_hex() != bob_offer.asset: die("asset in partial transaction from Bob {} is not the same " "as asset in Bob's initial offer ({})".format( result.asset.to_hex(), bob_offer.asset)) if result.amount != bob_offer.amount: die("amount in partial transaction from Bob {} is not the same " "as amount in Bob's initial offer ({})".format( result.amount, bob_offer.amount)) say("Asset and amount in partial transaction matches Bob's offer") bob_addr_list, bob_assetcommitment = recv('addr_list_and_assetcommitment') if len(bob_addr_list) != len(my_offers): die('unexpected address list lenth from Bob. expected {}, got {}'. format(len(my_offers), len(bob_addr_list))) say("Bob's addresses to receive my assets: {}".format(bob_addr_list)) # Convert Bob's addresses to address objects. # If Bob passes invalid address, we die with with exception. bob_addr_list = [CCoinAddress(a) for a in bob_addr_list] # Add our own inputs and outputs to Bob's partial tx # Create new mutable transaction from partial_tx tx = partial_tx.to_mutable() # We have assetcommitment for the first input, # other data is not needed for it. # initialize first elements of the arrays with empty/negative data. input_descriptors = [ BlindingInputDescriptor(asset=CAsset(), amount=-1, blinding_factor=Uint256(), asset_blinding_factor=Uint256()) ] # First output is already blinded, fill the slot with empty data output_pubkeys = [CPubKey()] # But assetcommitments array should start with Bob's asset commitment assetcommitments = [x(bob_assetcommitment)] # We will add our inputs for asset1 and asset2, and also an input # that will be used to pay the fee. # Note that the order is important: Bob blinded his transaction # with assetcommitments in the order we send them to him, # and we should add our inputs in the same order. utxos_to_add = (asset1_utxo, asset2_utxo, fee_utxo) # Add inputs for asset1 and asset2 and fee_asset and prepare input data # for blinding for utxo in utxos_to_add: # When we create CMutableTransaction and pass CTxIn, # it will be converted to CMutableTxIn. But if we append # to tx.vin or tx.vout, we need to use mutable versions # of the txin/txout classes, or else blinding or signing # will fail with error, unable to modify the instances. # COutPoint is not modified, though, so we can leave it # immutable. tx.vin.append( CMutableTxIn( prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout']))) input_descriptors.append( BlindingInputDescriptor( asset=CAsset(lx(utxo['asset'])), amount=coins_to_satoshi(utxo['amount']), blinding_factor=Uint256(lx(utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) # If we are supplying asset blinders and assetblinders for # particular input, assetcommitment data for that input do # not need to be correct. But if we are supplying assetcommitments # at all (auxiliary_generators argument to tx.blind()), # then all the elements of that array must have correct # type (bytes) and length (33). This is a requirement of the original # Elements Core API, and python-elementstx requires this, too. assetcommitments.append(b'\x00' * 33) # Add outputs to give Bob all our assets, and fill output pubkeys # for blinding the outputs to Bob's addresses for n, offer in enumerate(my_offers): tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(offer.amount), nAsset=CConfidentialAsset(CAsset(lx(offer.asset))), scriptPubKey=bob_addr_list[n].to_scriptPubKey())) output_pubkeys.append(bob_addr_list[n].blinding_pubkey) # Add change output for fee asset fee_change_amount = (coins_to_satoshi(fee_utxo['amount']) - FIXED_FEE_SATOSHI) tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(fee_change_amount), nAsset=CConfidentialAsset(fee_asset), scriptPubKey=fee_change_addr.to_scriptPubKey())) output_pubkeys.append(fee_change_addr.blinding_pubkey) # Add fee output. # Note that while we use CConfidentialAsset and CConfidentialValue # to specify value and asset, they are not in fact confidential here # - they are explicit, because we pass explicit values at creation. # You can check if they are explicit or confidential # with nValue.is_explicit(). If they are explicit, you can access # the unblinded values with nValue.to_amount() and nAsset.to_asset() tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # Our transaction lacks txin witness instances for the added inputs, # and txout witness instances for added outputs. # If transaction already have witness data attached, transaction # serialization code will require in/out witness array length # to be equal to vin/vout array length # Therefore we need to add dummy txin and txout witnesses for each # input and output that we added to transaction # we added one input and one output per asset, and an additional # input/change-output for fee asset. for _ in utxos_to_add: tx.wit.vtxinwit.append(CMutableTxInWitness()) tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And one extra dummy txout witness for fee output tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, auxiliary_generators=assetcommitments) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) # And must blind exactly three outputs (two to Bob, one fee asset change) if blind_result.num_successfully_blinded != 3: die('blinded {} outputs, expected to be 3'.format( blind_result.num_successfully_blinded)) say('Successfully blinded the combined transaction, will now sign') # Sign two new asset inputs, and fee asset input for n, utxo in enumerate(utxos_to_add): # We specify input_index as 1+n because we skip first (Bob's) input sign_input(tx, 1 + n, utxo) say('Signed my inputs, sending partially-signed transaction to Bob') send('partially_signed_tx', tx.serialize()) # Note that at this point both participants can still opt out of the swap: # Alice by double-spending her inputs to the transaction, # and Bob by not signing or not broadcasting the transaction. # Bob still have tiny advantage, because # he can pretend to have 'difficulties' in broadcasting and try to exploit # Alice's patience. If Alice does not reclaim her funds in the case Bob's # behaviour deviates from expected, then Bob will have free option to # exectute the swap at the time convenient to him. # Get the swap transaction from Bob. # Bob is expected to broadcast this transaction, and could just send txid # here, but then there would be a period of uncertainty: if Alice do not # see the txid at her own node, she does not know if this is because Bob # did not actually broadcast, and is just taking his time watching asset # prices, or the transaction just takes long time to propagate. If the # protocol requires Bob to send the transaction, the timeout required for # Alice to wait can be defined much more certainly. try: signed_tx_raw = recv('final-signed-tx', timeout=ALICE_PATIENCE_LIMIT) signed_tx = CTransaction.deserialize(x(signed_tx_raw)) # Check that this transaction spends the same inputs as the transacton # previously agreed upon for n, vin in enumerate(signed_tx.vin): if vin.prevout != tx.vin[n].prevout: die('Inputs of transaction received from Bob do not match ' 'the agreed-upon transaction') # Send the transaction from our side txid = rpc.sendrawtransaction(b2x(signed_tx.serialize())) except Exception as e: # If there is any problem, including communication timeout or invalid # communication, or invalid transaction encoding, then Alice will try # to claim her funds back, so Bob won't have an option to execute the # swap at the time convenient to him. He should execute it immediately. say('Unexpected problem on receiving final signed transaction ' 'from Bob: {}'.format(e)) say('This is suspicious. I will try to reclaim my funds now') claim_funds_back(say, utxos_to_add, die, rpc) say("Claimed my funds back. Screw Bob!") sys.exit(0) # Make sure the final transaction is confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc) # Check that everything went smoothly balance = coins_to_satoshi(rpc.getbalance("*", 1, False, bob_offer.asset)) if balance != bob_offer.amount: die('something went wrong, balance of Bob\'s asset after swap ' 'should be {} satoshi, but it is {} satoshi'.format( balance, bob_offer.amount)) print_asset_balances(say, my_offers + [bob_offer], rpc) # Wait for alice to politely end the conversation send('thanks-goodbye') say('Asset atomic swap completed successfully')
def test_from_to_unconfidential(self): #noqa pub1 = CPubKey( x('02546c76587482cd2468b76768da70c0166ecb2aa2eb1038624f4fedc138b042bc' )) pub2 = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' )) params_list = get_params_list() for pl_index, chainparam in enumerate(params_list): next_chainparam = (params_list[0] if pl_index + 1 == len(params_list) else params_list[pl_index + 1]) with ChainParams(chainparam): mapped_cls_list = dispatcher_mapped_list( CCoinConfidentialAddress) assert len(mapped_cls_list) == 1 chain_specific_cls = mapped_cls_list[0] with ChainParams(next_chainparam): mapped_cls_list = dispatcher_mapped_list( CCoinConfidentialAddress) assert len(mapped_cls_list) == 1 next_chain_specific_cls = mapped_cls_list[0] assert next_chain_specific_cls is not chain_specific_cls smpl = get_unconfidential_address_samples(pub1, pub2) for uct in unconf_types: for ct in conf_types: unconf = getattr(smpl, uct) conf = getattr(smpl, ct) with self.assertRaises(TypeError): next_chain_specific_cls.from_unconfidential( unconf, pub2) if ct.endswith(uct): self.assertEqual(str(conf.to_unconfidential()), str(unconf)) self.assertEqual( str(conf.from_unconfidential(unconf, pub2)), str(conf)) self.assertNotEqual( str(conf.from_unconfidential(unconf, pub1)), str(conf)) self.assertEqual( str( CCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( chain_specific_cls.from_unconfidential( unconf, pub2)), str(conf)) if ct.endswith('p2pkh'): self.assertEqual( str( CBase58CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2PKHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2sh'): self.assertEqual( str( CBase58CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2SHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2wpkh'): self.assertEqual( str( CBlech32CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2WPKHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2wsh'): self.assertEqual( str( CBlech32CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2WSHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) else: assert 0, "unexpected addr type" if issubclass(conf.__class__, CBlech32CoinConfidentialAddress): with self.assertRaises( CConfidentialAddressError): CBase58CoinConfidentialAddress.from_unconfidential( unconf, pub2) elif issubclass(conf.__class__, CBase58CoinConfidentialAddress): with self.assertRaises( CConfidentialAddressError): CBlech32CoinConfidentialAddress.from_unconfidential( unconf, pub2) else: assert 0, "unexpected conf.__class__" for ct2 in conf_types: if ct != ct2: conf_cls = getattr(smpl, ct2).__class__ with self.assertRaises(TypeError): conf_cls.from_unconfidential( unconf, pub2) else: self.assertNotEqual(str(conf.to_unconfidential()), str(unconf)) with self.assertRaises(TypeError): conf.from_unconfidential(unconf, pub2)
def T(hex_pubkey, is_valid, is_fullyvalid, is_compressed): key = CPubKey(x(hex_pubkey)) self.assertEqual(key.is_valid, is_valid) self.assertEqual(key.is_fullyvalid, is_fullyvalid) self.assertEqual(key.is_compressed, is_compressed)
sys.stderr.write( 'Value of txout {} is too small ' '(expecting at least 2x fee value of input transaction)\n'.format( utxo_n)) sys.exit(-1) dst_value = amount_to_spend - fee_value # An array of blinding pubkeys that we will supply to tx.blind() # It should cover all the outputs of the resulting transaction. output_pubkeys = [] if isinstance(dst_addr, CCoinConfidentialAddress): output_pubkeys.append(dst_addr.blinding_pubkey) else: output_pubkeys.append(CPubKey()) # Construct a transaction that spends the output we found # to the given destination address. # Note that the CTransaction is just a frontend for convenience, # and the created object will be the instance of the # CElementsTransaction class. The same with CTxIn, CTxOut, etc. tx = CElementsTransaction( vin=[CTxIn(prevout=COutPoint(hash=input_tx.GetTxid(), n=utxo_n))], vout=[ CElementsTxOut(nValue=CConfidentialValue(dst_value), nAsset=CConfidentialAsset(asset_to_spend), scriptPubKey=dst_addr.to_scriptPubKey()), # Fee output must be explicit in Elements CElementsTxOut(nValue=CConfidentialValue(fee_value), nAsset=fee_asset)