Resolve a DID to a public key.
    This is a multi-step process:
    1. get the name record
    2. get the zone file
    3. parse the zone file to get its URLs (if it's not well-formed, then abort)
    4. fetch and authenticate the JWT at each URL (abort if there are none)
    5. extract the public key from the JWT and return that.

    Return {'public_key': ..., 'document': ...} on success
    Return {'error': ...} on error

def resolve_DID(did, hostport=None, proxy=None):
    """
    Resolve a DID to a public key.
    This is a multi-step process:
    1. get the name record
    2. get the zone file
    3. parse the zone file to get its URLs (if it's not well-formed, then abort)
    4. fetch and authenticate the JWT at each URL (abort if there are none)
    5. extract the public key from the JWT and return that.

    Return {'public_key': ..., 'document': ...} on success
    Return {'error': ...} on error
    """
    assert hostport or proxy, 'Need hostport or proxy'

    did_rec = get_DID_record(did, hostport=hostport, proxy=proxy)
    if 'error' in did_rec:
        log.error("Failed to get DID record for {}: {}".format(did, did_rec['error']))
        return {'error': 'Failed to get DID record: {}'.format(did_rec['error']), 'http_status': did_rec.get('http_status', 500)}
    
    if 'value_hash' not in did_rec:
        log.error("DID record for {} has no zone file hash".format(did))
        return {'error': 'No zone file hash in name record for {}'.format(did), 'http_status': 404}

    zonefile_hash = did_rec['value_hash']
    zonefile_res = get_zonefiles(hostport, [zonefile_hash], proxy=proxy)
    if 'error' in zonefile_res:
        log.error("Failed to get zone file for {} for DID {}: {}".format(zonefile_hash, did, zonefile_res['error']))
        return {'error': 'Failed to get zone file for {}'.format(did), 'http_status': 404}

    zonefile_txt = zonefile_res['zonefiles'][zonefile_hash]
    log.debug("Got {}-byte zone file {}".format(len(zonefile_txt), zonefile_hash))

    try:
        zonefile_data = blockstack_zones.parse_zone_file(zonefile_txt)
        zonefile_data = dict(zonefile_data)
        assert 'uri' in zonefile_data
        if len(zonefile_data['uri']) == 0:
            return {'error': 'No URI records in zone file {} for {}'.format(zonefile_hash, did), 'http_status': 404}

    except Exception as e:
        if BLOCKSTACK_TEST:
            log.exception(e)

        return {'error': 'Failed to parse zone file {} for {}'.format(zonefile_hash, did), 'http_status': 404}

    urls = [uri['target'] for uri in zonefile_data['uri']]
    for url in urls:
        jwt = get_JWT(url, address=str(did_rec['address']))
        if not jwt:
            continue

        if 'payload' not in jwt:
            log.error('Invalid JWT at {}: no payload'.format(url))
            continue

        if 'issuer' not in jwt['payload']:
            log.error('Invalid JWT at {}: no issuer'.format(url))
            continue

        if 'publicKey' not in jwt['payload']['issuer']:
            log.error('Invalid JWT at {}: no public key'.format(url))
            continue

        if 'claim' not in jwt['payload']:
            log.error('Invalid JWT at {}: no claim'.format(url))
            continue

        if not isinstance(jwt['payload'], dict):
            log.error('Invalid JWT at {}: claim is malformed'.format(url))
            continue

        # found!
        public_key = str(jwt['payload']['issuer']['publicKey'])
        document = jwt['payload']['claim']

        # make sure it's a well-formed DID
        document['@context'] = 'https://w3id.org/did/v1'
        document['publicKey'] = [
            {
                'id': did,
                'type': 'secp256k1',
                'publicKeyHex': public_key
            }
        ]

        return {'public_key': public_key, 'document': document}

    log.error("No zone file URLs resolved to a JWT with the public key whose address is {}".format(did_rec['address']))
    return {'error': 'No public key found for the given DID', 'http_status': 404}