Exemple #1
0
def readVarInt(b, pver):
    """
    readVarInt reads a variable length integer from b and returns it as an int.

    Args:
        b ByteArray: the encoded integer.
        pver int: the protocol version (unused).
    """
    data = {
        0xFF: dict(
            pop_bytes=8,
            minRv=0x100000000,
        ),
        0xFE: dict(
            pop_bytes=4,
            minRv=0x10000,
        ),
        0xFD: dict(
            pop_bytes=2,
            minRv=0xFD,
        ),
    }
    discriminant = b.pop(1).int()
    if discriminant not in data.keys():
        return discriminant
    rv = b.pop(data[discriminant]["pop_bytes"]).unLittle().int()
    # The encoding is not canonical if the value could have been
    # encoded using fewer bytes.
    minRv = data[discriminant]["minRv"]
    if rv < minRv:
        raise DecredError("ReadVarInt noncanon error: {} - {} <= {}".format(
            rv, discriminant, minRv))
    return rv
Exemple #2
0
def request(url, postData=None, headers=None, urlEncode=False, context=None):
    # GET method used when encoded data is None.
    encoded = None
    if postData:
        if urlEncode:
            # Encode the data in URL query string form.
            encoded = urlencode(postData).encode("utf-8")
        else:
            # Encode the data as JSON.
            encoded = json.dumps(postData).encode("utf-8")

    headers = headers if headers else {}
    req = urlrequest.Request(url, headers=headers, data=encoded)

    try:
        raw = urlrequest.urlopen(req, context=context).read().decode()
    except Exception as err:
        raise DecredError(
            f"Error in requesting URL {url}: {formatTraceback(err)}")

    try:
        # Try to decode the response as JSON, but fall back to just
        # returning the string.
        return json.loads(raw)
    except json.JSONDecodeError:
        return raw
Exemple #3
0
    def btcEncode(self, pver):
        """
        btcEncode encodes the receiver using the Decred protocol encoding.
        This is part of the Message API.

        Args:
            pver (int): The protocol version.

        Returns:
            ByteArray: The encoded MsgVersion.
        """
        if not validateUserAgent(self.userAgent):
            raise DecredError(f"bad user agent {self.userAgent}")

        b = ByteArray()
        b += ByteArray(self.protocolVersion, length=4).littleEndian()
        b += ByteArray(self.services, length=8).littleEndian()
        b += ByteArray(self.timestamp, length=8).littleEndian()
        b += netaddress.writeNetAddress(self.addrYou, False)
        b += netaddress.writeNetAddress(self.addrMe, False)
        b += ByteArray(self.nonce, length=8).littleEndian()
        b += wire.writeVarString(pver, self.userAgent)
        b += ByteArray(self.lastBlock, length=4).littleEndian()
        b += [0] if self.disableRelayTx else [1]

        return b
Exemple #4
0
def readNetAddress(b, hasStamp):
    """
    Reads an encoded NetAddress from b depending on the protocol version and
    whether or not the timestamp is included per hasStamp.  Some messages like
    version do not include the timestamp.

    Args:
        b (ByteArray): The encoded NetAddress.
        hasStamp (bool): Whether or not the NetAddress has a timestamp.

    Returns:
        NetAddress: The decoded NetAddress.
    """
    expLen = 30 if hasStamp else 26
    if len(b) != expLen:
        raise DecredError(
            f"readNetAddress wrong length (hasStamp={hasStamp}) expected {expLen}, got {len(b)}"
        )

    # NOTE: The Decred protocol uses a uint32 for the timestamp so it will
    # stop working somewhere around 2106.  Also timestamp wasn't added until
    # protocol version >= NetAddressTimeVersion
    stamp = b.pop(4).unLittle().int() if hasStamp else 0
    services = b.pop(8).unLittle().int()
    ip = b.pop(16)
    if ip[:12] == ipv4to16prefix:
        ip = ip[12:]

    # Sigh.  Decred protocol mixes little and big endian.
    port = b.pop(2).int()

    return NetAddress(ip=ip, port=port, services=services, stamp=stamp,)
Exemple #5
0
    def authorize(self, address):
        """
        Authorize the stake pool for the provided address and network. DecredError
        is raised on failure to authorize.

        Args:
            address (string): The base58-encoded pubkey address that the wallet
                uses to vote.
        """
        try:
            self.getPurchaseInfo()
            self.validate(address)
        except DecredError as e:
            # code 9 is address not set
            addressNotSet = (isinstance(self.err, dict) and "code" in self.err
                             and self.err["code"] == 9)
            if not addressNotSet:
                raise e

            # address is not set
            data = {"UserPubKeyAddr": address}
            res = tinyhttp.post(self.apiPath("address"),
                                data,
                                headers=self.headers(),
                                urlEncode=True)
            if resultIsSuccess(res):
                self.getPurchaseInfo()
                self.validate(address)
            else:
                raise DecredError("unexpected response from 'address': %s" %
                                  repr(res))
Exemple #6
0
    def __init__(self, pkHash=None, netParams=None, sigType=STEcdsaSecp256k1):
        """
        Args:
            pkHash (ByteArray): The hashed pubkey.
            netParams (module): The network parameters.
            sigType (int): The signature type.
        """

        if sigType == STEd25519:  # nocover
            raise NotImplementedError("Edwards not implemented")
        elif sigType == STSchnorrSecp256k1:  # nocover
            raise NotImplementedError("Schnorr not implemented")
        elif sigType != STEcdsaSecp256k1:
            raise NotImplementedError(f"unsupported signature type {sigType}")

        super().__init__(netParams)
        pkh_len = len(pkHash)
        if pkh_len != RIPEMD160_SIZE:
            raise DecredError(
                f"AddressPubKeyHash expected {RIPEMD160_SIZE} bytes, got {pkh_len}"
            )

        self.sigType = sigType
        self.netID = netParams.PubKeyHashAddrID
        self.pkHash = pkHash
Exemple #7
0
    def tx(self, txid):
        """
        Get the MsgTx. Retreive it from the blockchain if necessary.

        Args:
            txid (str): A hex encoded transaction ID to fetch.

        Returns:
            MsgTx: The transaction.
        """
        hashKey = hashFromHex(txid).bytes()
        try:
            return self.txDB[hashKey]
        except database.NoValueError:
            try:
                # Grab the hex encoded transaction
                txHex = self.dcrdata.tx.hex(txid)
                msgTx = msgtx.MsgTx.deserialize(ByteArray(txHex))
                self.txDB[hashKey] = msgTx
                return msgTx
            except Exception as e:
                log.warning(
                    "unable to retrieve tx data from dcrdata at %s: %s" %
                    (self.dcrdata.baseURL, e))
        raise DecredError("failed to retrieve transaction")
Exemple #8
0
    def __init__(self, scriptHash, netParams):

        if len(scriptHash) != RIPEMD160_SIZE:
            raise DecredError(f"incorrect script hash length {len(scriptHash)}")

        super().__init__(netParams)
        self.netID = netParams.ScriptHashAddrID
        self.scriptHash = scriptHash
Exemple #9
0
    def readNBits(self, n):
        """
        Read n number of LSB bits of data from the bit stream in big endian
        format.

        Args:
            n (int): The number of bits to read.

        Returns:
            int: The bits interpreted as a big-endian integer.
        """
        if n > 64 or n < 0:
            raise DecredError(f"n > 64 or < 0 not allowed. given {n}")
        if n == 0:
            return 0

        if len(self.b) == 0:
            raise EncodingError("end of bytes")

        value = 0

        # If byte is partially read, read the rest.
        if self.next != 1 << 7:
            while n > 0:
                if self.next == 0:
                    self.next = 1 << 7
                    self.b = self.b[1:]
                    break
                n -= 1
                if self.b[0] & self.next != 0:
                    value |= 1 << n
                self.next >>= 1

        if n == 0:
            return value

        # Read 8 bits at a time.
        while n >= 8:
            if len(self.b) == 0:
                raise EncodingError("end of bytes")

            n -= 8
            value |= self.b[0] << n
            self.b = self.b[1:]

        if len(self.b) == 0:
            if n != 0:
                raise EncodingError("bytes exhausted")
            return value

        # Read the remaining bits.
        while n > 0:
            n -= 1
            if self.b[0] & self.next != 0:
                value |= 1 << n
            self.next >>= 1

        return value
Exemple #10
0
    def decodePrefix(self, b, pver):
        """
        decodePrefix decodes a transaction prefix and stores the contents
        in the embedded msgTx.
        """
        count = wire.readVarInt(b, pver)
        # Prevent more input transactions than could possibly fit into a
        # message.  It would be possible to cause memory exhaustion and panics
        # without a sane upper bound on this count.
        if count > maxTxInPerMessage:
            raise DecredError(
                "MsgTx.decodePrefix: too many input transactions to fit into"
                " max message size [count %d, max %d]" %
                (count, maxTxInPerMessage))

        # TxIns.
        txIns = self.txIn = [TxIn(None, 0) for i in range(count)]
        for txIn in txIns:
            readTxInPrefix(b, pver, self.serType, self.version, txIn)

        count = wire.readVarInt(b, pver)

        # Prevent more output transactions than could possibly fit into a
        # message.  It would be possible to cause memory exhaustion and panics
        # without a sane upper bound on this count.
        if count > maxTxOutPerMessage:
            raise DecredError(
                "MsgTx.decodePrefix: too many output transactions to fit into"
                " max message size [count %d, max %d]" %
                (count, maxTxOutPerMessage))

        # TxOuts.
        totalScriptSize = 0
        txOuts = self.txOut = [TxOut(None, None) for i in range(count)]
        for txOut in txOuts:
            # The pointer is set now in case a script buffer is borrowed
            # and needs to be returned to the pool on error.
            b = readTxOut(b, pver, self.version, txOut)
            totalScriptSize += len(txOut.pkScript)

        # Locktime and expiry.
        self.lockTime = b.pop(4).unLittle().int()

        self.expiry = b.pop(4).unLittle().int()
        return b, totalScriptSize
Exemple #11
0
 def serializeCompressed(self):
     fmt = PUBKEY_COMPRESSED
     if not isEven(self.y):
         fmt |= 0x1
     b = ByteArray(fmt)
     b += ByteArray(self.x, length=COORDINATE_LEN)
     if len(b) != PUBKEY_COMPRESSED_LEN:
         raise DecredError("invalid compressed pubkey length %d", len(b))
     return b
Exemple #12
0
def parseCoinType(coinType):
    """
    Parse the coin type. If coinType is a string, it will be converted to the
    BIP0044 ID. If it is already an integer, it is returned as is.

    Args:
        coinType (int or str): The asset. BIP0044 ID or ticker symbol.

    Returns:
        int: The BIP0044 ID.
    """
    if isinstance(coinType, str):
        ticker = coinType.lower()
        if ticker not in SymbolIDs:
            raise DecredError(f"ticker symbol {ticker} not found")
        coinType = SymbolIDs[ticker]
    if not isinstance(coinType, int):
        raise DecredError(f"unsupported type for coinType {type(coinType)}")
    return coinType
Exemple #13
0
def parse(name):
    """
    Get the network parameters based on the network name.
    """
    # Set testnet to DCR for now. If more coins are added, a better solution
    # will be needed.
    try:
        return the_nets[name]
    except KeyError:
        raise DecredError(f"unrecognized network name {name}")
Exemple #14
0
    def decodeWitness(self, b, pver, isFull):
        """
        Witness only; generate the TxIn list and fill out only the sigScripts.

        Args:
            b ByteArray: the encoded witnesses.
            pver int: the protocol version.
            isFull book: whether this is a full transaction.
        """
        totalScriptSize = 0
        count = wire.readVarInt(b, pver)

        # Prevent more input transactions than could possibly fit into a
        # message, or memory exhaustion and panics could happen.
        if count > maxTxInPerMessage:
            raise DecredError(
                "MsgTx.decodeWitness: too many input transactions to fit into"
                f" max message size [count {count}, max {maxTxInPerMessage}]")
        if isFull:
            # We're decoding witnesses from a full transaction, so make sure
            # the number of signature scripts is the same as the number of
            # TxIns we currently have, then fill in the signature scripts.
            if count != len(self.txIn):
                raise DecredError(
                    "MsgTx.decodeWitness: non equal witness and prefix txin"
                    f" quantities (witness {count}, prefix {len(self.txIn)})")

            # Read in the witnesses, and copy them into the already generated
            # by decodePrefix TxIns.
            if self.txIn is None or len(self.txIn) == 0:
                self.txIn = [TxIn(None, 0) for i in range(count)]
            for txIn in self.txIn:
                b = readTxInWitness(b, pver, self.version, txIn)
                totalScriptSize += len(txIn.signatureScript)
        else:
            self.txIn = [TxIn(None, 0) for i in range(count)]
            for txIn in self.txIn:
                b = readTxInWitness(b, pver, self.version, txIn)
                totalScriptSize += len(txIn.signatureScript)
            self.txOut = []

        return b, totalScriptSize
Exemple #15
0
 def updateTip(self):
     """
     Update the tip block. If the wallet is subscribed to block updates,
     this can be used sparingly.
     """
     try:
         self.tipHeight = self.bestBlock()["height"]
     except Exception as e:
         log.error("failed to retrieve tip from blockchain: %s" %
                   formatTraceback(e))
         raise DecredError("no tip data retrieved")
Exemple #16
0
def readTxInPrefix(b, pver, serType, ver, ti):
    if serType == wire.TxSerializeOnlyWitness:
        raise DecredError(
            "readTxInPrefix: tried to read a prefix input for a witness only tx"
        )

    # Outpoint.
    b, ti.previousOutPoint = readOutPoint(b, pver, ver)

    # Sequence.
    ti.sequence = b.pop(4).unLittle().int()
Exemple #17
0
    def getStats(self):
        """
        Get the stats from the stake pool API.

        Returns:
            Poolstats: The PoolStats object.
        """
        res = tinyhttp.get(self.apiPath("stats"), headers=self.headers())
        if resultIsSuccess(res):
            self.stats = PoolStats(res["data"])
            return self.stats
        raise DecredError("unexpected response from 'stats': %s" % repr(res))
Exemple #18
0
def checkOutput(output, fee):
    """
    checkOutput performs simple consensus and policy tests on a transaction
    output.

    Args:
        output (TxOut): The output to check
        fee (float): The transaction fee rate (/kB).

    Raises:
        DecredError if an output is deemed invalid.
    """
    if output.value < 0:
        raise DecredError("transaction output amount is negative")
    if output.value > txscript.MaxAmount:
        raise DecredError("transaction output amount exceeds maximum value")
    if output.value == 0:
        raise DecredError("zero-value output")
    # need to implement these
    if txscript.isDustOutput(output, fee):
        raise DecredError("policy violation: transaction output is dust")
Exemple #19
0
def decode(words):
    """
    DecodeMnemonics returns the decoded value that is encoded by words.  Any
    words that are whitespace are empty are skipped.
    """
    _, byteMap = pgWords()
    decoded = [0] * len(words)
    idx = 0
    for word in words:
        word = word.strip().lower()
        if word == "":
            continue
        if word not in byteMap:
            raise DecredError("unknown words in mnemonic key: %s" % word)
        b = byteMap[word]
        if int(b % 2) != idx % 2:
            raise DecredError(
                f"word {word} is not valid at position {idx}, check for missing words"
            )
        decoded[idx] = b // 2
        idx += 1
    return ByteArray(decoded[:idx])
Exemple #20
0
def checkSeedLength(length):
    """
    Check that seed length is correct.

    Args:
        length int: the seed length to be checked.

    Raises:
        DecredError if length is not between MinSeedBytes and MaxSeedBytes
        included.
    """
    if length < MinSeedBytes or length > MaxSeedBytes:
        raise DecredError(f"Invalid seed length {length}")
Exemple #21
0
 def create(walletDir, pw, network, signals=None):
     """
     Create a new wallet. Will not overwrite an existing wallet file. All
     arguments are the same as the SimpleWallet constructor.
     """
     netParams = nets.parse(network)
     _, dbPath, _ = paths(walletDir, netParams.Name)
     if Path(dbPath).is_file():
         raise DecredError("wallet already exists at %s" % dbPath)
     wallet = SimpleWallet(walletDir, pw, network, signals, True)
     words = wallet.words
     wallet.words.clear()
     return wallet, words
Exemple #22
0
def b58CheckDecode(s):
    """
    Decode the base-58 encoded address, parsing the version bytes and the pubkey
    hash. An exception is raised if the checksum is invalid or missing.

    Args:
        s (str): The base-58 encoded address.

    Returns:
        ByteArray: Decoded bytes minus the leading version and trailing
            checksum.
        int: The version (leading two) bytes.
    """
    decoded = b58decode(s)
    if len(decoded) < 6:
        raise DecredError("decoded lacking version/checksum")
    version = decoded[:2]
    included_cksum = decoded[len(decoded) - 4:]
    computed_cksum = checksum(decoded[:len(decoded) - 4])
    if included_cksum != computed_cksum:
        raise DecredError("checksum error")
    payload = ByteArray(decoded[2:len(decoded) - 4])
    return payload, version
Exemple #23
0
def decodeBlob(b):
    """
    decodeBlob decodes a versioned blob into its version and the pushes extracted
    from its data.

    Args:
        b (bytes-like): The bytes to decode.

    Returns:
        int: The blob version (the version passed to BuildyBytes).
        list(bytes-like): The data pushes.
    """
    if len(b) == 0:
        raise DecredError("zero length blob not allowed")
    return b[0], extractPushes(b[1:])
Exemple #24
0
    def parsePubKey(self, pubKeyB):
        """
        parsePubKey parses a secp256k1 public key encoded according to the
        format specified by ANSI X9.62-1998, which means it is also compatible
        with the SEC (Standards for Efficient Cryptography) specification which
        is a subset of the former.  In other words, it supports the
        uncompressed and compressed formats as follows:

        Compressed:
          <format byte = 0x02/0x03><32-byte X coordinate>
        Uncompressed:
          <format byte = 0x04><32-byte X coordinate><32-byte Y coordinate>

        It does not support the hybrid format, however.
        """
        if len(pubKeyB) == 0:
            raise DecredError("empty pubkey")

        fmt = pubKeyB[0]
        ybit = (fmt & 0x1) == 0x1
        fmt &= 0xFF ^ 0x01

        ifunc = lambda b: int.from_bytes(b, byteorder="big")

        pkLen = len(pubKeyB)
        if pkLen == PUBKEY_LEN:
            if PUBKEY_UNCOMPRESSED != fmt:
                raise DecredError("invalid magic in pubkey: %d" % pubKeyB[0])
            x = ifunc(pubKeyB[1:33])
            y = ifunc(pubKeyB[33:])

        elif pkLen == PUBKEY_COMPRESSED_LEN:
            # format is 0x2 | solution, <X coordinate>
            # solution determines which solution of the curve we use.
            # / y^2 = x^3 + Curve.B
            if PUBKEY_COMPRESSED != fmt:
                raise DecredError("invalid magic in compressed pubkey: %d" %
                                  pubKeyB[0])
            x = ifunc(pubKeyB[1:33])
            y = self.decompressPoint(x, ybit)
        else:  # wrong!
            raise DecredError("invalid pub key length %d" % len(pubKeyB))

        if x > self.P:
            raise DecredError("pubkey X parameter is >= to P")
        if y > self.P:
            raise DecredError("pubkey Y parameter is >= to P")
        if not self.isAffineOnCurve(x, y):
            raise DecredError("pubkey [%d, %d] isn't on secp256k1 curve" %
                              (x, y))
        return PublicKey(self, x, y)
Exemple #25
0
    def child(self, name, **k):
        """
        Create a child Bucket, which is really just a Bucket with a name which
        derives from this Bucket's name.

        Args:
            name (str): The child Bucket name.
            **k: Keyword arguments are passed directly to the Bucket
                constructor.
        """
        if "$" in name:
            raise DecredError(
                "illegal character. '$' not allowed in table name")
        compoundName = "{parent}${child}".format(parent=self.name, child=name)
        return Bucket(self.conn, compoundName, **k)
Exemple #26
0
    def setVoteBits(self, voteBits):
        """
        Set the vote preference on the VotingServiceProvider.

        Returns:
            bool: True on success. DecredError raised on error.
        """
        data = {"VoteBits": voteBits}
        res = tinyhttp.post(self.apiPath("voting"),
                            data,
                            headers=self.headers(),
                            urlEncode=True)
        if resultIsSuccess(res):
            self.purchaseInfo.voteBits = voteBits
            return True
        raise DecredError("unexpected response from 'voting': %s" % repr(res))
Exemple #27
0
def modInv(a, m):
    """
    Modular inverse based on https://stackoverflow.com/a/9758173/1124661.
    Raises an exception if impossible.

    Args:
        a (int): An integer.
        m (int): The modulus.

    Returns:
        int: The modular inverse.
    """
    g, x, y = egcd(a, m)
    if g != 1:
        raise DecredError("modular inverse does not exist")
    return x % m
Exemple #28
0
    def child(self, name, **k):
        """
        Creates the root Bucket.

        Args:
            name (str): The root node name.
            **k: Keyword arguments are passed directly to the Bucket
                constructor.

        Returns:
            Bucket: The root bucket.
        """
        if "$" in name:
            raise DecredError(
                "illegal character. '$' not allowed in table name")
        return Bucket(self.conn, name, **k)
Exemple #29
0
def decodeStringIP(ip):
    """
    Parse an IP string to bytes.

    Args:
        ip (str): The string-encoded IP address.

    Returns:
        bytes-like: The byte-encoded IP address.
    """
    try:
        return socket.inet_pton(socket.AF_INET, ip)
    except OSError:
        pass
    try:
        return socket.inet_pton(socket.AF_INET6, ip)
    except OSError:
        raise DecredError(f"failed to decode IP {ip}")
Exemple #30
0
    def getPurchaseInfo(self):
        """
        Get the purchase info from the stake pool API.

        Returns:
            PurchaseInfo: The PurchaseInfo object.
        """
        self.err = None
        res = tinyhttp.get(self.apiPath("getpurchaseinfo"),
                           headers=self.headers())
        if resultIsSuccess(res):
            pi = PurchaseInfo.parse(res["data"])
            # check the script hash
            self.purchaseInfo = pi
            return self.purchaseInfo
        self.err = res
        raise DecredError("unexpected response from 'getpurchaseinfo': %r" %
                          (res, ))