Esempio n. 1
0
 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)
Esempio n. 2
0
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)
Esempio n. 3
0
    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'
                      )))
Esempio n. 4
0
    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')
Esempio n. 5
0
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)
Esempio n. 6
0
    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])
Esempio n. 8
0
    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)
Esempio n. 10
0
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))
Esempio n. 11
0
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))
Esempio n. 12
0
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
Esempio n. 13
0
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
Esempio n. 14
0
    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)
Esempio n. 15
0
 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)
Esempio n. 16
0
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)
Esempio n. 17
0
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)
Esempio n. 18
0
    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)
Esempio n. 19
0
 def blinding_pubkey(self) -> CPubKey:
     assert isinstance(self, bytes), \
         "descendant classes must also be bytes subclasses"
     return CPubKey(self[0:33])
Esempio n. 20
0
 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')
Esempio n. 21
0
 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)
Esempio n. 22
0
    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)
Esempio n. 23
0
    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)
Esempio n. 24
0
    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())
Esempio n. 25
0
    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)
Esempio n. 26
0
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)
Esempio n. 27
0
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')
Esempio n. 28
0
    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)
Esempio n. 29
0
 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)
Esempio n. 30
0
        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)