📖
Metapass API
  • Welcome!
  • Quick Start
    • Graph Node Requests
    • Smart Contract Requests
    • Miscellaneous
  • Reference
    • Graph Node API
      • TKETSFactory
      • Event
      • Ticket
      • TicketToken
    • Smart Contract Specifications
      • EventFactory
        • ABI
      • Ticket
        • ABI
Powered by GitBook
On this page
  • Signature verification
  • The idea
  • Example implementation
  • Getting a smart contract instance with ethers
  1. Quick Start

Miscellaneous

Signature verification

The purpose of signature verification is to ensure that the person submitting a HTTP request to your backend actually has ownership of the wallet address that he says he owns. This is done using Elliptic Curve Digital Signature Algorithm (ECDSA) to recover the address that could have signed a particular signature, and then checking if the address matches that was given in a HTTP request.

The idea

Here, we outline an example implementation of how this can be done. The idea is that we ask the user to provide his wallet address, and the timestamp of the HTTP request. You may ask for other information if you desire. Then, we concatenate these two fields together, and generate a hash for it. We then ask the user to sign this hash, and produce a signature signed using his wallet address.

Then, in the HTTP request, we attach the plaintext address and timestamp, as well as the signature, into the header of the request. On the backend, we then re-generate the hash that was signed by the user. Then, we use a special Web3 function (akin to the ecrecover function in Solidity) to recover the address that signed the signature given, by supplying the re-generated hash. If everything matches, and the timestamp has not expired, then we allow the request to go through.

Doing the verification this way avoids the need for a centralised, traditional OAuth API to authenticate users, as it commonly is for Web3 technologies. Everything is secured via cryptography, just like in the blockchain itself.

Example implementation

Frontend (React TSX)

import { useWeb3React } from '@web3-react/core'
import {
  safeAccess,
  getRawSignature,
  solidityKeccak,
  toUnixTimestamp
} from '../utils'

const { library } = useWeb3React()

const BACKEND_ADDRESS = "..."

const getHeaderFields = async (account, library) => {
    const currentTimestamp = toUnixTimestamp(new Date())
    
    const msg = solidityKeccak( ['address', 'uint256'], [account, currentTimestamp] )
    const signature = await getRawSignature(msg, library, account)
    
    return { address, signature, timestamp }
}

export async function getProtectedEndpoint(account, data) {
  const headers = await getHeaderFields(account, library)
  return axios.post(`${BACKEND_ADDRESS}/protected/`, data, {
    headers: {
      'Content-Type': 'application/json',
      'x-address': headers.address,
      'x-timestamp': headers.timestamp!.toString(),
      'x-signature': headers.signature
    }
  }).then((r) => {
    if (r.status === 200) {
      return r.data
    }

    return undefined
  }).catch((e: Error) => {
    console.log(e)
    return undefined
  })
}

Backend (Python)

from web3 import Web3
from eth_account.messages import encode_defunct

REQUEST_TIMEOUT = 60 * 60 # 1 hour timeout

def verify_request(request):
    # Write permissions are only allowed to the owner of the wallet address
    try:
        address = request.META.get('HTTP_X_ADDRESS', None)
        timestamp = request.META.get('HTTP_X_TIMESTAMP', None)
        signature = request.META.get('HTTP_X_SIGNATURE', None)

        if (not (address and timestamp and signature)): # Missing fields
            return False

        if (Web3.toInt(hexstr=address) == 0): # Input address is 0
            return False

        # Use solidity keccak to hash the information
        msg = Web3.solidityKeccak(['address', 'uint256'], [address, int(timestamp)])
        
        # reconstruct signed hash using encode_defunct
        hash_ = encode_defunct(primitive = msg)

        # use ecrecover to recover the address that signed this signature
        recovered_address = w3.eth.account.recover_message(hash_, signature=signature)

        # get current time
        curr_time = int(time.time())

        # check if recovered address matches the address given, and that the timeout has not passed
        return recovered_address == address and curr_time < (int(timestamp) + REQUEST_TIMEOUT)
    except:
        return False

Getting a smart contract instance with ethers

// account is optional
export function getContract(address: string, ABI: any, library: any, account: any) {
  if (!isAddress(address) || address === ethers.constants.AddressZero) {
    throw Error(`Invalid 'address' parameter '${address}'.`)
  }

  return new ethers.Contract(address, ABI, getProviderOrSigner(library, account))
}
PreviousSmart Contract RequestsNextGraph Node API

Last updated 3 years ago