Convey service

The Convey service has been built to act as a content relayer in front of IPFS. Let’s imagine that Bob wants to share a JWT with Alice through a QR code. If the content is too large, it will not fit in a QR code, so there is where the Convey service acts. It allows to transport and caché files that can be accessed via HTTPS, and provides a tiny URL that can fit in any QR code. The service exposes two main endpoints: POST and GET files. Please refer to the protocol description here for more details about it.

Main flows

Authentication flow

  1. Bob requests to login to the Convey service by sending it’s DID
  2. The Convey service responds with a challenge
  3. Bob signs a VC with that challenge and send it to the Convey service to authenticate
  4. Convey service verifies the received challenge and validates that the signer is the same that Bob provided in step 1. If it is ok, it responds with an authentication token
  5. Bob saves that authentication token to be used on each interaction with the Convey service

See the DID Authentication protocol for more details.

Example

import axios from 'axios'
import { createVerifiableCredentialJwt } from 'did-jwt-vc'
import RSKEthrDID from '@rsksmart/ethr-did'

const identity = new RSKEthrDID({ 
    address: '0xDe9D2B98E1c23E2765c06C5057723a6c1c453147',
    privateKey: '2f86e57652ee906707d4415105228b4fda7b1b900cfd0871cd5d17277ad084b8'
})

axios.post(`${serviceUrl}/request-auth`, { did: identity.did })
  .then(res => res.status === 200 && !!res.data && res.data.challenge)
  .then(challenge => createVerifiableCredentialJwt({
      vc: {
        '@context': ['https://www.w3.org/2018/credentials/v1'],
        type: ['VerifiableCredential'],
        credentialSubject: {
          claims: [
            { claimType: 'challenge', claimValue: challenge }
          ]
        }
      }
    }, identity))
  .then(jwt => axios.post(`${serviceUrl}/auth`, { jwt }))
  .then(res => res.status === 200 && !!res.data && res.data.token)

Post and share content

  1. Bob does the authentication flow
  2. Bob posts some content (ideally encrypted) to the Convey service
  3. Convey service puts it in IPFS and store its CID (associated to the original file) in memory. It responds with the CID of the saved content and the convey formatted uri (see the protocol)
  4. Bob builds a QR code with the received convey formatted uri (and the encryption key in case the content needs to be decrypted) with the format convey://{CID}#{key}

Example

import axios from 'axios'

const content = 'myLargeContent'
const { encrypted, encriptionKey } = encrypt(content)

login()
  .then(token => axios.post(`${serviceUrl}/file`, { file: encrypted }, { headers: { 'Authorization': token }))
  .then(res => res.status === 200 && !!res.data && res.data)
  .then({ cid, url } => generateQr(`${url}#${encriptionKey}`))

Receive shared content

  1. Alice does the authentication flow
  2. Alice scans the QR and receive the convey formatted uri
  3. Alice gets the CID and encryption key (if provided) from the received uri and builds and HTTP url as the following: http://{conveyService}/file/{CID}
  4. Alice tries to get the content from the Convey service
  5. If found, the Convey service will return a JSON with the file. If not found,
  6. the Convey service will return an HTTP 404.
  7. Alice will get the file from the IPFS Gateway (https://{ipfsGateway}/ipfs/{CID})
  8. The content is encrypted, Alice decrypts it with the key and gets the original file

Example

import axios from 'axios'

const qrContent = `convey://${cid}#${ecKey}`

const index = 'convey://'.length
const identifier = uri.substring(index)
const [cid, encryptionKey] = identifier.split('#')

login()
  .then(token => axios.get(`${serviceUrl}/file/${cid}`, { headers: { 'Authorization': token }))
  .then(res => res.status === 200 && !!res.data && res.data.file)
  .then(file => decrypt(file, encryptionKey))
  .then(content => console.log(`Decrypted content: ${content}`))

API

Authentication

This service uses a simplified version of DID Auth protocol

POST /request-auth

Generates a random 64 bytes challenge that is associated to the received DID. It is part of the implementation of the DID Authentication protocol

Parameter

  • did - string DID that will be associated with the generated challenge.

Returns

{ challenge: generatedChallenge }

Example with Axios

import axios from 'axios'

const did = 'did:ethr:rsk:testnet:0x....1234'

axios.post(`${serviceUrl}/request-auth`, { did })
    .then(res => res.status === 200 && !!res.data && res.data.challenge)
    .then(challenge => console.log(`The challenge is: ${challenge}`))
POST /auth

Verifies challenge and did of the received VC, if it is ok, it emits another VC and responds with the JWT representation of it. It will act as the authentication token. It is part of the implementation of the DID Authentication protocol.

Parameter

  • jwt - jwt representation of a VC signed by the client with the received challenge. That VC should be signed by the DID sent before and follow this format:
    vc: {
      '@context': ['https://www.w3.org/2018/credentials/v1'],
      type: ['VerifiableCredential'],
      credentialSubject: {
        claims: [
          { claimType: 'challenge', claimValue: RECEIVED_CHALLENGE }
        ]
      }
    }
    

Returns

{ token: generatedToken }

Example with Axios and did-jwt-vc

import axios from 'axios'
import { createVerifiableCredentialJwt } from 'did-jwt-vc'

const jwt = await createVerifiableCredentialJwt({
  vc: {
    '@context': ['https://www.w3.org/2018/credentials/v1'],
    type: ['VerifiableCredential'],
    credentialSubject: {
      claims: [
        { claimType: 'challenge', claimValue: theReceivedChallenge }
      ]
    }
  }
}, identity) // this identity should be associated to the did sent in /request_auth

axios.post(`${serviceUrl}/auth`, { jwt }))
    .then(res => res.status === 200 && !!res.data && res.data.token)
    .then(token => console.log(`The authentication token is: ${token}`))

Convey files

Use this API to send and receive files using the convey

POST /file

It stores the file in IPFS and then saves in the service memory the original content associated to its IPFS hash (cid)

Parameter

  • file - string content to be saved

Returns

{
  url: `convey://${cid}`,
  cid: generatedCid
}

Example with Axios

import axios from 'axios'

const content = 'myLargeContent'
const { encrypted, encriptionKey } = encrypt(content)

const token = thePreviouslyReceivedToken

axios.post(`${serviceUrl}/file`, { file: encrypted }, { headers: { 'Authorization': token }))
    .then(res => res.status === 200 && !!res.data && res.data)
    .then({ cid, url } => console.log(`The Convey uri: ${url}. The CID: ${cid}`))
GET /file

It looks for the given CID in the service memory and responds with it in case it is found. If not, returns an HTTP 404.

Parameter

  • cid - CID of the desired content

Returns

{
  file: retrievedFileFromMemory
}

Example with Axios

import axios from 'axios'

const token = thePreviouslyReceivedToken
const cid = theOneGottenFromTheQr

axios.get(`${serviceUrl}/file/${cid}`, { headers: { 'Authorization': token }))
    .then(res => res.status === 200 && !!res.data && res.data.file)
    .then(file => console.log(`The retrieved file is: ${file}.`))

Possible extensions

  • Limit the amount of time the content is saved in memory
  • Limit file size

Run for development

The service source code is hosted in Github, so please refer directly to the README and check there the detailed guide to install and test the service locally or with a Docker container.