def verify_twitter(oauth_verifier, eth_address):
        # Verify authenticity of user
        if 'request_token' not in session:
            raise TwitterVerificationError('Session not found.')
        oauth = OAuth1(
            settings.TWITTER_CONSUMER_KEY,
            settings.TWITTER_CONSUMER_SECRET,
            session['request_token']['oauth_token'],
            session['request_token']['oauth_token_secret'],
            verifier=oauth_verifier)
        r = requests.post(url=twitter_access_token_url, auth=oauth)
        if r.status_code != 200:
            raise TwitterVerificationError(
                'The verifier you provided is invalid.')

        # TODO: determine what the text should be
        data = 'twitter verified'
        # TODO: determine claim type integer code for phone verification
        signature = attestations.generate_signature(
            signing_key, eth_address, CLAIM_TYPES['twitter'], data)

        attestation = Attestation(
            method=AttestationTypes.TWITTER,
            eth_address=eth_address,
            signature=signature
        )
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['twitter'],
            'data': data
        })
    def verify_phone(phone, code, eth_address):
        phone = normalize_number(phone)

        db_code = VC.query \
            .filter(VC.phone == phone) \
            .first()
        if db_code is None:
            raise PhoneVerificationError(
                'The given phone number was not found.')
        if code != db_code.code:
            raise PhoneVerificationError('The code you provided'
                                         ' is invalid.')
        if time_.utcnow() > db_code.expires_at:
            raise PhoneVerificationError('The code you provided'
                                         ' has expired.')
        # TODO: determine what the text should be
        data = 'phone verified'
        # TODO: determine claim type integer code for phone verification
        signature = attestations.generate_signature(signing_key, eth_address,
                                                    CLAIM_TYPES['phone'], data)
        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['phone'],
            'data': data
        })
 def verify_facebook(code, eth_address):
     base_url = 'graph.facebook.com'
     client_id = settings.FACEBOOK_CLIENT_ID
     client_secret = settings.FACEBOOK_CLIENT_SECRET
     redirect_uri = urls.absurl("/redirects/facebook/")
     code = code
     path = ('/v2.12/oauth/access_token?client_id={}'
             '&client_secret={}&redirect_uri={}&code={}').format(
                 client_id, client_secret, redirect_uri, code)
     conn = http.client.HTTPSConnection(base_url)
     conn.request('GET', path)
     response = json.loads(conn.getresponse().read())
     has_access_token = ('access_token' in response)
     if not has_access_token or 'error' in response:
         raise FacebookVerificationError(
             'The code you provided is invalid.')
     # TODO: determine what the text should be
     data = 'facebook verified'
     # TODO: determine claim type integer code for phone verification
     signature = attestations.generate_signature(signing_key, eth_address,
                                                 CLAIM_TYPES['facebook'],
                                                 data)
     return VerificationServiceResponse({
         'signature':
         signature,
         'claim_type':
         CLAIM_TYPES['facebook'],
         'data':
         data
     })
    def verify_email(email, code, eth_address):
        """Check a email verification code against the verification code stored
        in the session for that email.

        Args:
            email (str): Email address being verified
            code (int): Verification code for the email address
            eth_address (str): Address of ERC725 identity token for claim

        Returns:
            VerificationServiceResponse

        Raises:
            ValidationError: Verification request failed due to invalid arguments
        """
        verification_obj = session.get('email_attestation', None)
        if not verification_obj:
            raise EmailVerificationError('No verification code was found.')

        if not check_password_hash(verification_obj['email'], email):
            raise EmailVerificationError(
                'No verification code was found for that email.'
            )

        if verification_obj['expiry'] < datetime.datetime.utcnow():
            raise ValidationError('Verification code has expired.', 'code')

        if verification_obj['code'] != code:
            raise ValidationError('Verification code is incorrect.', 'code')

        session.pop('email_attestation')

        # TODO: determine what the text should be
        data = 'email verified'
        # TODO: determine claim type integer code for email verification
        signature = attestations.generate_signature(
            signing_key, eth_address, CLAIM_TYPES['email'], data
        )

        attestation = Attestation(
            method=AttestationTypes.EMAIL,
            eth_address=eth_address,
            value=email,
            signature=signature
        )
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['email'],
            'data': data
        })
    def verify_airbnb(eth_address, airbnbUserId):
        validate_airbnb_user_id(airbnbUserId)

        code = get_airbnb_verification_code(eth_address, airbnbUserId)

        # TODO: determine if this user agent is acceptable.
        # We need to set an user agent otherwise Airbnb returns 403
        request = Request(
            url='https://www.airbnb.com/users/show/' + airbnbUserId,
            headers={'User-Agent': 'Origin Protocol client-0.1.0'}
        )

        try:
            response = urlopen(request)
        except HTTPError as e:
            if e.code == 404:
                raise AirbnbVerificationError(
                    'Airbnb user id: ' + airbnbUserId + ' not found.')
            else:
                raise AirbnbVerificationError(
                    "Can not fetch user's Airbnb profile.")
        except URLError as e:
            raise AirbnbVerificationError(
                "Can not fetch user's Airbnb profile.")

        if code not in response.read().decode('utf-8'):
            raise AirbnbVerificationError(
                "Origin verification code: " + code +
                " has not been found in user's Airbnb profile."
            )

        # TODO: determine the schema for claim data
        data = 'airbnbUserId:' + airbnbUserId
        signature = attestations.generate_signature(
            signing_key, eth_address, CLAIM_TYPES['airbnb'], data
        )

        attestation = Attestation(
            method=AttestationTypes.AIRBNB,
            eth_address=eth_address,
            value=airbnbUserId,
            signature=signature
        )
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['airbnb'],
            'data': data
        })
Beispiel #6
0
    def verify_facebook(code, eth_address):
        base_url = "https://graph.facebook.com"

        response = requests.get("{}/v2.12/oauth/access_token".format(base_url),
                                params={
                                    "client_id":
                                    settings.FACEBOOK_CLIENT_ID,
                                    "client_secret":
                                    settings.FACEBOOK_CLIENT_SECRET,
                                    "redirect_uri":
                                    urls.absurl("/redirects/facebook/"),
                                    "code":
                                    code
                                })

        if "access_token" not in response.json() or "error" in response.json():
            if "error" in response.json():
                logger.error(response.json()["error"])
            raise FacebookVerificationError(
                "The code you provided is invalid.")

        access_token = response.json()["access_token"]

        response = requests.get("{}/me".format(base_url),
                                params={"access_token": access_token})

        # TODO: determine what the text should be
        data = 'facebook verified'
        # TODO: determine claim type integer code for phone verification
        signature = attestations.generate_signature(signing_key, eth_address,
                                                    CLAIM_TYPES['facebook'],
                                                    data)

        attestation = Attestation(method=AttestationTypes.FACEBOOK,
                                  eth_address=eth_address,
                                  value=response.json()['name'],
                                  signature=signature,
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature':
            signature,
            'claim_type':
            CLAIM_TYPES['facebook'],
            'data':
            data
        })
    def verify_twitter(oauth_verifier, eth_address):
        ipfs_helper = IPFSHelper()
        # Verify authenticity of user
        if 'request_token' not in session:
            raise TwitterVerificationError('Session not found.')

        oauth = OAuth1(settings.TWITTER_CONSUMER_KEY,
                       settings.TWITTER_CONSUMER_SECRET,
                       session['request_token']['oauth_token'],
                       session['request_token']['oauth_token_secret'],
                       verifier=oauth_verifier)

        response = requests.post(url=twitter_access_token_url, auth=oauth)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            logger.exception(exc)
            raise TwitterVerificationError(
                'The verifier you provided is invalid.')

        query_string = urllib.parse.parse_qs(response.content)
        screen_name = query_string[b'screen_name'][0].decode('utf-8')

        ipfs_hash = ipfs_helper.add_json({
            'schemaId':
            'https://schema.originprotocol.com/twitter-attestation_1.0.0.json',
            'screen_name': screen_name
        })

        signature = attestations.generate_signature(signing_key, eth_address,
                                                    TOPICS['twitter'],
                                                    base58_to_hex(ipfs_hash))

        attestation = Attestation(method=AttestationTypes.TWITTER,
                                  eth_address=eth_address,
                                  value=screen_name,
                                  signature=signature,
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': TOPICS['twitter'],
            'data': ipfs_hash
        })
Beispiel #8
0
    def verify_twitter(oauth_verifier, eth_address):
        # Verify authenticity of user

        if 'request_token' not in session:
            raise TwitterVerificationError('Session not found.')

        oauth = OAuth1(settings.TWITTER_CONSUMER_KEY,
                       settings.TWITTER_CONSUMER_SECRET,
                       session['request_token']['oauth_token'],
                       session['request_token']['oauth_token_secret'],
                       verifier=oauth_verifier)

        response = requests.post(url=twitter_access_token_url, auth=oauth)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            logger.exception(exc)
            raise TwitterVerificationError(
                'The verifier you provided is invalid.')

        # TODO: determine what the text should be
        data = 'twitter verified'
        # TODO: determine claim type integer code for phone verification
        signature = attestations.generate_signature(signing_key, eth_address,
                                                    CLAIM_TYPES['twitter'],
                                                    data)

        query_string = urllib.parse.parse_qs(response.content)
        screen_name = query_string[b'screen_name'][0].decode('utf-8')

        attestation = Attestation(method=AttestationTypes.TWITTER,
                                  eth_address=eth_address,
                                  value=screen_name,
                                  signature=signature,
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['twitter'],
            'data': data
        })
    def verify_email(email, code, eth_address):
        db_code = VC.query \
            .filter(func.lower(VC.email) == func.lower(email)) \
            .first()
        if db_code is None:
            raise EmailVerificationError('The given email was' ' not found.')
        if code != db_code.code:
            raise EmailVerificationError('The code you provided'
                                         ' is invalid.')
        if time_.utcnow() > db_code.expires_at:
            raise EmailVerificationError('The code you provided'
                                         ' has expired.')

        # TODO: determine what the text should be
        data = 'email verified'
        # TODO: determine claim type integer code for email verification
        signature = attestations.generate_signature(signing_key, eth_address,
                                                    CLAIM_TYPES['email'], data)
        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': CLAIM_TYPES['email'],
            'data': data
        })
Beispiel #10
0
    def verify_airbnb(eth_address, airbnbUserId):
        validate_airbnb_user_id(airbnbUserId)

        code = get_airbnb_verification_code(eth_address, airbnbUserId)

        try:
            # TODO: determine if this user agent is acceptable.
            # We need to set an user agent otherwise Airbnb returns 403
            response = urlopen(
                Request(
                    url='https://www.airbnb.com/users/show/' + airbnbUserId,
                    headers={'User-Agent': 'Origin Protocol client-0.1.0'}))
        except HTTPError as e:
            if e.code == 404:
                raise AirbnbVerificationError('Airbnb user id: ' +
                                              airbnbUserId + ' not found.')
            else:
                raise AirbnbVerificationError(
                    "Can not fetch user's Airbnb profile.")
        except URLError as e:
            raise AirbnbVerificationError(
                "Can not fetch user's Airbnb profile.")

        if code not in response.read().decode('utf-8'):
            raise AirbnbVerificationError(
                "Origin verification code: " + code +
                " has not been found in user's Airbnb profile.")

        data = {
            'issuer': ISSUER,
            'issueDate': current_time(),
            'attestation': {
                'verificationMethod': {
                    'pubAuditableUrl': {}
                },
                'site': {
                    'siteName': 'airbnb.com',
                    'userId': {
                        'raw': airbnbUserId
                    }
                }
            }
        }

        # Note: use sort_keys option to make the output deterministic for hashing purposes.
        json_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
        signature = {
            'bytes':
            attestations.generate_signature(signing_key, eth_address,
                                            json_data),
            'version':
            '1.0.0'
        }

        attestation = Attestation(method=AttestationTypes.AIRBNB,
                                  eth_address=eth_address,
                                  value=airbnbUserId,
                                  signature=signature['bytes'],
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'schemaId':
            'https://schema.originprotocol.com/attestation_1.0.0.json',
            'data': data,
            'signature': signature
        })
Beispiel #11
0
    def verify_twitter(oauth_verifier, eth_address):
        # Verify authenticity of user
        if 'request_token' not in session:
            raise TwitterVerificationError('Session not found.')

        oauth = OAuth1(settings.TWITTER_CONSUMER_KEY,
                       settings.TWITTER_CONSUMER_SECRET,
                       session['request_token']['oauth_token'],
                       session['request_token']['oauth_token_secret'],
                       verifier=oauth_verifier)

        response = requests.post(url=twitter_access_token_url, auth=oauth)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            logger.exception(exc)
            raise TwitterVerificationError(
                'The verifier you provided is invalid.')

        query_string = urllib.parse.parse_qs(response.content)
        screen_name = query_string[b'screen_name'][0].decode('utf-8')

        data = {
            'issuer': ISSUER,
            'issueDate': current_time(),
            'attestation': {
                'verificationMethod': {
                    'oAuth': True
                },
                'site': {
                    'siteName': 'twitter.com',
                    'userId': {
                        'raw': screen_name
                    }
                }
            }
        }

        # Note: use sort_keys option to make the output deterministic for hashing purposes.
        json_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
        signature = {
            'bytes':
            attestations.generate_signature(signing_key, eth_address,
                                            json_data),
            'version':
            '1.0.0'
        }

        attestation = Attestation(method=AttestationTypes.TWITTER,
                                  eth_address=eth_address,
                                  value=screen_name,
                                  signature=signature['bytes'],
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'schemaId':
            'https://schema.originprotocol.com/attestation_1.0.0.json',
            'data': data,
            'signature': signature,
        })
Beispiel #12
0
    def verify_facebook(code, eth_address):
        base_url = "https://graph.facebook.com"

        response = requests.get("{}/v2.12/oauth/access_token".format(base_url),
                                params={
                                    "client_id":
                                    settings.FACEBOOK_CLIENT_ID,
                                    "client_secret":
                                    settings.FACEBOOK_CLIENT_SECRET,
                                    "redirect_uri":
                                    urls.absurl("/redirects/facebook/"),
                                    "code":
                                    code
                                })

        if "access_token" not in response.json() or "error" in response.json():
            if "error" in response.json():
                logger.error(response.json()["error"])
            raise FacebookVerificationError(
                "The code you provided is invalid.")

        access_token = response.json()["access_token"]

        response = requests.get("{}/me".format(base_url),
                                params={"access_token": access_token})

        data = {
            'issuer': ISSUER,
            'issueDate': current_time(),
            'attestation': {
                'verificationMethod': {
                    'oAuth': True
                },
                'site': {
                    'siteName': 'facebook.com',
                    'userId': {
                        'verified': True
                    }
                }
            }
        }

        # Note: use sort_keys option to make the output deterministic for hashing purposes.
        json_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
        signature = {
            'bytes':
            attestations.generate_signature(signing_key, eth_address,
                                            json_data),
            'version':
            '1.0.0'
        }

        attestation = Attestation(method=AttestationTypes.FACEBOOK,
                                  eth_address=eth_address,
                                  value=response.json()['name'],
                                  signature=signature['bytes'],
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'schemaId':
            'https://schema.originprotocol.com/attestation_1.0.0.json',
            'data': data,
            'signature': signature
        })
Beispiel #13
0
    def verify_email(email, code, eth_address):
        """Check a email verification code against the verification code stored
        in the session for that email.

        Args:
            email (str): Email address being verified
            code (int): Verification code for the email address
            eth_address (str): ETH address of the user

        Returns:
            VerificationServiceResponse

        Raises:
            ValidationError: Verification request failed due to invalid arguments
        """
        verification_obj = session.get('email_attestation', None)
        if not verification_obj:
            raise EmailVerificationError('No verification code was found.')

        if not check_password_hash(verification_obj['email'], email):
            raise EmailVerificationError(
                'No verification code was found for that email.')

        if verification_obj['expiry'] < datetime.datetime.utcnow():
            raise ValidationError('Verification code has expired.', 'code')

        if verification_obj['code'] != code:
            raise ValidationError('Verification code is incorrect.', 'code')

        session.pop('email_attestation')

        data = {
            'issuer': ISSUER,
            'issueDate': current_time(),
            'attestation': {
                'verificationMethod': {
                    'email': True
                },
                'email': {
                    'verified': True
                }
            }
        }

        # Note: use sort_keys option to make the output deterministic for hashing purposes.
        json_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
        signature = {
            'bytes':
            attestations.generate_signature(signing_key, eth_address,
                                            json_data),
            'version':
            '1.0.0'
        }

        attestation = Attestation(method=AttestationTypes.EMAIL,
                                  eth_address=eth_address,
                                  value=email,
                                  signature=signature['bytes'],
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'schemaId':
            'https://schema.originprotocol.com/attestation_1.0.0.json',
            'data': data,
            'signature': signature
        })
Beispiel #14
0
    def verify_phone(country_calling_code, phone, code, eth_address):
        """Check a phone verification code against the Twilio Verify API for a
        phone number.

        Args:
            country_calling_code (str): Dialling prefix for the country.
            phone (str): Phone number in national format.
            code (int): Verification code for the country_calling_code and phone
                combination
            eth_address (str): ETH address of the user

        Returns:
            VerificationServiceResponse

        Raises:
            ValidationError: Verification request failed due to invalid arguments
            PhoneVerificationError: Verification request failed for a reason not
                related to the arguments
        """
        method = session.get('phone_verification_method', None)
        if method not in ['sms', 'call']:
            raise ValidationError('Invalid phone verification method ', method)

        params = {
            'country_code': country_calling_code,
            'phone_number': phone,
            'verification_code': code
        }

        headers = {'X-Authy-API-Key': settings.TWILIO_VERIFY_API_KEY}

        url = 'https://api.authy.com/protected/json/phones/verification/check'
        response = requests.get(url, params=params, headers=headers)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            logger.exception(exc)
            if response.json()['error_code'] == '60023':
                # This error code could also mean that no phone verification was ever
                # created for that country calling code and phone number
                raise ValidationError('Verification code has expired.',
                                      field_names=['code'])
            elif response.json()['error_code'] == '60022':
                raise ValidationError('Verification code is incorrect.',
                                      field_names=['code'])
            else:
                raise PhoneVerificationError(
                    'Could not verify code. Please try again shortly.')

        # This may be unnecessary because the response has a 200 status code
        # but it a good precaution to handle any inconsistency between the
        # success field and the status code
        if response.json()['success'] is True:
            data = {
                'issuer': ISSUER,
                'issueDate': current_time(),
                'attestation': {
                    'verificationMethod': {
                        method: True
                    },
                    'phone': {
                        'verified': True
                    }
                }
            }

            # Note: use sort_keys option to make the output deterministic for hashing purposes.
            json_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
            signature = {
                'bytes':
                attestations.generate_signature(signing_key, eth_address,
                                                json_data),
                'version':
                '1.0.0'
            }

            attestation = Attestation(method=AttestationTypes.PHONE,
                                      eth_address=eth_address,
                                      value="{} {}".format(
                                          country_calling_code, phone),
                                      signature=signature['bytes'],
                                      remote_ip_address=request.remote_addr)
            db.session.add(attestation)
            db.session.commit()

            session.pop('phone_verification_method')

            return VerificationServiceResponse({
                'schemaId':
                'https://schema.originprotocol.com/attestation_1.0.0.json',
                'data': data,
                'signature': signature
            })

        raise PhoneVerificationError(
            'Could not verify code. Please try again shortly.')
    def verify_phone(country_calling_code, phone, code, eth_address):
        """Check a phone verification code against the Twilio Verify API for a
        phone number.

        Args:
            country_calling_code (str): Dialling prefix for the country.
            phone (str): Phone number in national format.
            code (int): Verification code for the country_calling_code and phone
                combination
            eth_address (str): Address of ERC725 identity token for claim

        Returns:
            VerificationServiceResponse

        Raises:
            ValidationError: Verification request failed due to invalid arguments
            PhoneVerificationError: Verification request failed for a reason not
                related to the arguments
        """
        params = {
            'country_code': country_calling_code,
            'phone_number': phone,
            'verification_code': code
        }

        headers = {
            'X-Authy-API-Key': settings.TWILIO_VERIFY_API_KEY
        }

        url = 'https://api.authy.com/protected/json/phones/verification/check'
        response = requests.get(url, params=params, headers=headers)

        try:
            response.raise_for_status()
        except requests.exceptions.HTTPError as exc:
            if response.json()['error_code'] == '60023':
                # This error code could also mean that no phone verification was ever
                # created for that country calling code and phone number
                raise ValidationError('Verification code has expired.',
                                      field_names=['code'])
            elif response.json()['error_code'] == '60022':
                raise ValidationError('Verification code is incorrect.',
                                      field_names=['code'])
            else:
                raise PhoneVerificationError(
                    'Could not verify code. Please try again shortly.'
                )

        # This may be unnecessary because the response has a 200 status code
        # but it a good precaution to handle any inconsistency between the
        # success field and the status code
        if response.json()['success'] is True:
            # TODO: determine what the text should be
            data = 'phone verified'
            # TODO: determine claim type integer code for phone verification
            signature = attestations.generate_signature(
                signing_key, eth_address, CLAIM_TYPES['phone'], data
            )

            attestation = Attestation(
                method=AttestationTypes.PHONE,
                eth_address=eth_address,
                value="{} {}".format(country_calling_code, phone),
                signature=signature
            )
            db.session.add(attestation)
            db.session.commit()

            return VerificationServiceResponse({
                'signature': signature,
                'claim_type': CLAIM_TYPES['phone'],
                'data': data
            })

        raise PhoneVerificationError(
            'Could not verify code. Please try again shortly.'
        )
    def verify_airbnb(eth_address, airbnbUserId):
        ipfs_helper = IPFSHelper()
        validate_airbnb_user_id(airbnbUserId)

        code = get_airbnb_verification_code(eth_address, airbnbUserId)

        try:
            # TODO: determine if this user agent is acceptable.
            # We need to set an user agent otherwise Airbnb returns 403
            response = urlopen(
                Request(
                    url='https://www.airbnb.com/users/show/' + airbnbUserId,
                    headers={'User-Agent': 'Origin Protocol client-0.1.0'}))
        except HTTPError as e:
            if e.code == 404:
                raise AirbnbVerificationError('Airbnb user id: ' +
                                              airbnbUserId + ' not found.')
            else:
                raise AirbnbVerificationError(
                    "Can not fetch user's Airbnb profile.")
        except URLError as e:
            raise AirbnbVerificationError(
                "Can not fetch user's Airbnb profile.")

        if code not in response.read().decode('utf-8'):
            raise AirbnbVerificationError(
                "Origin verification code: " + code +
                " has not been found in user's Airbnb profile.")

        ipfs_hash = ipfs_helper.add_json({
            'schemaId':
            'https://schema.originprotocol.com/airbnb-attestation_1.0.0.json',
            'airbnb_user_id': airbnbUserId
        })
        """ - IPFS hash is a base58 encoded string
            - We store IPFS hashes in solidity claims in bytes32 binary format to minimise
              gas cost.
            - bytes32 is not string serialisable so it can not be transmitted in that form
              from bridge to the DApp
            - bridge needs to transform ipfs hash to bytes32 format (that is how
              it is going to be stored in the contract) before signing the claim, and then
              send IPFS hash to the DApp in base58 string encoding.
            - this way claim has a correct signature if IPFS hash has bytes32 hex encoding
            - the DApp takes signature and other claim info and transforms the base58 encoded
              IPFS hash to base32 hex before submitting the claim to web3.
        """
        signature = attestations.generate_signature(signing_key, eth_address,
                                                    TOPICS['airbnb'],
                                                    base58_to_hex(ipfs_hash))

        attestation = Attestation(method=AttestationTypes.AIRBNB,
                                  eth_address=eth_address,
                                  value=airbnbUserId,
                                  signature=signature,
                                  remote_ip_address=request.remote_addr)
        db.session.add(attestation)
        db.session.commit()

        return VerificationServiceResponse({
            'signature': signature,
            'claim_type': TOPICS['airbnb'],
            'data': ipfs_hash
        })