At Bloom, we’re building a decentralized marketplace for verifying user data. In a nutshell, a company (“Acme”) might announce to the Bloom network that they want to verify their customer Bob’s physical address. Multiple companies raise their hand saying they can do this job, Acme negotiates with each, picks a vendor based on price and past reputation, and then privately shares Bob’s address with the verifier. The verifier performs a proprietary check out of band and then updates Bob’s Bloom ID to reflect whether the address is valid or not.

A few questions you might be thinking:

How does Acme coordinate with the different vendors quickly and privately?

How can the coordination and negotiation be decentralized?

How does Bob control who gets to see his private information?

How can the vendors trust that Acme will pay up?

Is this all happening on the blockchain? Isn’t this slow?

Using Ethereum’s lesser known Whisper protocol, the entire process is quick, decentralized, private, and resilient to cheating. By leveraging cryptographic signatures, we’re able to consolidate the whole process down to one blockchain transaction which updates the user’s Bloom ID, publishes the decision (valid or invalid), and pays the vendor.

In this post, we’ll unpack how we’re using Whisper and signatures to create a great user experience while holding strong to our core commitment to decentralization and privacy.

Bloom ID Attestations

Ethereum is more than a programmable blockchain

Ethereum’s world computer is actually made up of three layers. The Ethereum Virtual Machine is the most well known and provides a compute layer to run smart contracts enabling decentralized applications (dApps). Whisper provides a communication layer to enable secure messaging between dApps, groups or individuals. Swarm provides a storage layer which enables users to pool their resources to deliver content around the world.

Bloom uses smart contracts and Whisper to power a decentralized identity verification marketplace that puts the user at the center of all decisions concerning their personal information. When Acme wants to verify Bob’s information, they follow three simple steps.

Negotiation

Acme broadcasts a message via Whisper. The message contains the type of identity information they need verified and how much they are willing to pay.

Solicitation { messageType: solicitation replyTo: senderPublicKey session negotiationSession sessionSigned: sign(session, senderPrivKey) rewardAsk: 5 BLT }

Verifiers listen for solicitations by filtering Whisper messages for a topic corresponding to the attestation types they can perform. When a verifier receives a solicitation, they respond with a direct message bidding on the job including a message signed with their Ethereum private key. Before bidding, the verifier can confirm Acme has the funds to pay the specified reward by querying the marketplace smart contract.

Bid { messageType: attestationBid rewardBid: 5 BLT replyTo: verifierPublicKey session negotiationSession reSession reSessionSigned: sign(reSession, verifierPrivKey) }

Acme can confirm the identity of the verifier bidding on the job by recovering the address that signed the sessionId and comparing it with a list of whitelisted addresses. If the bid is acceptable, Acme requests a signature from the user to authorize the chosen attester to complete the attestation.

Authorization

Bob receives a notification that Acme has chosen a company to verify his address. If Bob approves, he signs an agreement giving Acme permission to share his data only with the specified company. The reward payment for the attestation can’t be released without a valid signature from Bob. Acme collects Bob’s data, and generates a signature which unlocks the reward payment for the verifier upon completion of the attestation.

JobDetails { messageType: jobDetails reward: 5 BLT subjectData subjectRequestNonce typeIds subjectAddress subjectSignature paymentSignature }

Attestation

The verifier receives the job details from Acme and completes the verification, either confirming or rejecting the information that Bob provided. Now it’s time for them to get paid. They submit their decision to the Attestation Repository smart contract. The smart contract performs a couple essential checks before writing the information to the blockchain.

Did Bob approve the data to be shared with the attester submitting this transaction?

Can Acme afford to pay this reward?

If these checks pass, the decision is written, the reward is paid out from Acme’s account and and event is emitted notifying Acme the job is complete.

Here is what the process looks like from Bob’s perspective when doing a phone attestation. Notice the Metamask popup was only asking for a signature. Bloom enables users to interact with the blockchain and secure their personal information without needing to hold any Ether, pay transaction fees or wait around for transactions to mine.

Next we’ll cover some of the technical details of the attestation process.

Attestation Repository Smart Contract

Attestations are written to a smart contract which stores the result of each attestation as a mapping from Bloom ID to an array of attestation structs.

struct Attestation { bytes32 dataHash; uint256[] typeIds; uint256 requesterId; uint256 attesterId; uint256 completedAt; uint256 certainty; uint256 decision; } mapping(uint256 => Attestation[]) public attestations;

Attestations reference one or more types of identity information. The dataHash stores a hash of the data that was shared with the attester. The plaintext data itself is never stored on the blockchain. The data is formatted as follows:

[ { type: ‘phone’, data: ‘15551112323’, nonce: ‘47b6cc5f-e961–4b9f-a675–6fafed394823’ }, { type: ‘ssn’, data: ‘012–34–5678’, nonce: ‘328493defaf6–576a-f9b4–169e-f5cc6b75’ } ]

Each component of the data is hashed individually, then the resulting array is hashed into the dataHash.

// Step 1 componentHashes = [hash1, hash2] // Step 2 dataHash = hash(componentHashes)

If a third party requests a Bloom user to reveal the data that was submitted as part of an attestation, the user can choose to reveal none, some or all of the data by sending the 3rd party the hashed or plaintext data.

Attestations have a set list of possible outcomes referred to as decisions. Each attestation is formulated in a way that these possible outcomes can be applied.

0: success: Attestation was completed and information is valid

1: rejection: Attestation can not be completed due to contradicting identity information

2: invalid: Attestation can not be completed due to invalid/unverifiable information.

3: incomplete: Attestation can not be completed at this time due to factors outside of the attester’s control

Phone: “I control this phone number”

Email: “I control this email address”

Sanction Screening: “I am not on any watch lists”

PEP Screening: “I am not a politically exposed person”

Certainty is a subjective assessment by the attester about their confidence in the attestation decision. Certainty is limited to a range of 0 (least certain) to 1000 (most certain). An attester should view assigning a certainty factor as staking their reputation on a decision.

The Complete Attestation Process

The attestation requester deposits BLT into a payment contract and locks them up for a specified amount of time. The attestation requester broadcasts a solicitation via Whisper to find an attester willing to do a job for a specified amount of BLT. The attester receives a solicitation via Whisper and bid on jobs by messaging the requester directly. The attestation requester obtains the user’s permission to share their data with the chosen attester. The attestation requester shares the data and the payment authorization with the attester. The attester completes the attestation and submits their decision to the Attestation Repository smart contract. The act of submitting the attestation triggers a release of tokens to the attester’s balance and notifies the requester via an event that the attestation was completed. The attestation requester reads the result of the attestation from the Attestation Repository smart contract.

Using Whisper In Production

Most Ethereum nodes do not currently have Whisper enabled, including nodes provided by services like Infura. Run geth with Whisper and the WebSocket RPC server enabled in order to send and receive whisper messages via Web3.

geth \ --syncmode "light" \ --shh \ --ws \ --wsaddr="localhost" \

The Web3 Shh package allows you to communicate via whisper.

import * as Shh from 'web3-shh' var shh = new Shh(env.whisper.whisperProvider)

Add filters to listen for broadcast messages by specifying a symmetric key and a topic.

export const newBroadcastSession = async ( newTopic: string, // 4 byte hex string password: string ) => { try { const symkeyId = await shh.generateSymKeyFromPassword(password) const broadcastMessageFilterID = await shh.newMessageFilter({ topics: [newTopic], symKeyID: symkeyId, }) } catch (e) { throw new Error(`Broadcast filter message addition failed: ${e}`) } }

Add filters to listen for direct messages by specifying a key pair.

const newKeyID = await shh.newKeyPair() export const newDirectMessageSession = async ( newTopic: string, keypairId: string ) => { try { const directMessageFilterID = await shh.newMessageFilter({ topics: [newTopic], privateKeyID: keypairId, }) } catch (e) { throw new Error(`Direct message filter addition failed: ${e}`) } }

Poll filters on an interval to retrieve and respond to messages. Watch out for filter timeouts! Whisper filters automatically time out after ~10 minutes. This is not anywhere in the documentation. It is a hard issue to track because the WebSockets connection drops out on a similar interval.

export const fetchAllMessages = async (filterIds: string[]) => { let allMessages: Shh.Message[] = [] for (let filterId of filterIds) { let filterMessages = await shh.getFilterMessages(filterId) for (let message of filterMessages) { allMessages.push(message) } } return allMessages }

Stop listening for messages by removing the filters.

export const endSession = async (filterId: string, keypairId: string) => { try { await shh.deleteMessageFilter(filterId) await shh.deleteKeyPair(keypairId) } catch (e) { throw new Error(`Filter removal failed: ${e}`) } }

Send a broadcast message by specifying a symmetric key and topic.

export const broadcastMessage = async ( message: IBloomWhisperMessage, messageTopic: string, symKeyPassword: string ) => { // Generate symkey from password const symkeyId = await shh.generateSymKeyFromPassword(symKeyPassword) const outcome = await shh.post({ ttl: 7, topic: messageTopic, powTarget: 2.01, powTime: 2, payload: web3.fromAscii(JSON.stringify(message)), symKeyID: symkeyId, }) if (!outcome) { throw new Error('Failed to broadcast message') } }

Send a direct message by specifying a public key for the recipient.

export const directMessage = async ( message: IBloomWhisperMessage, messageTopic: string, recipientPublicKeyString: string ) => { const outcome = await shh.post({ ttl: 7, topic: messageTopic, powTarget: 2.01, powTime: 2, payload: web3.fromAscii(JSON.stringify(message)), pubKey: recipientPublicKeyString, }) if (!outcome) { throw new Error('Failed to send direct message') } }

Read more about Bloom’s phase 2 contracts here: bloom.co/docs.

The contracts are currently being audited and will be released on main-net soon. The entire attestation process is currently running on Rinkeby.

Learn More About Bloom