Getting Started
Overview
Platform Contracts
AccountsSigning LogicAttestationsToken Escrow
Internal Contracts
Voting
Attestation Kit
OverviewEnvironment VariablesAttesterRequesterWebhooks
Share Kit
OverviewDemo
Toggle Dark Mode

Attestations

Manage attestations for BloomID users and receive payment


Attestation Logic allows attesters to receive payment for completing work when requested by a third party and only when authorized by the subject. Requesters, attesters and subjects coordinate off-chain to verify identity information. Attesters publish their decision on chain by either 'attesting' the information or 'contesting' it. Either way they receive payment from the requester's escrow account. Users can also revoke attestations through the AttestationLogic contract by emitting a revoke event referencing the revocation links contained in the attestation data tree.

Contract name Network Address
AttestationLogic Mainnet 0xceec7aAA57e3a77c73A9954b9b7D5B32ab688318
AttestationLogic Rinkeby 0x313a0DC2A954F8D196CE72FEf175f4403B7121Ca

Data Structures

Attestation data is stored in an event emitted by AttestationLogic. Event based storage is preferable to EVM state storage for variables not critical to the contract logic in order to save a significant amount of gas. Events are just as permanent as contract storage, they just cannot be referenced from within a transaction.

event TraitAttested(
  address subject,
  address attester,
  address requester,
  bytes32 dataHash
  );

DataHash

Attestations may reference a single type of identity data or a group of data. The dataHash represents the root hash of a Merkle tree containing the attested data. The full spec for how the dataHash is formed is available on the Bloom Specs repo.


AttestationLogic

Public Functions

attest

The attester calls attest to submit an attestation and receive payment from the requester. A simplified view of the attestation process is:

  1. Requester is interested in having an attestation performed for a subject (Bloom user)
  2. Requester finds an attester willing to do the work for a set reward in BLT
  3. Requester obtains a signature from the subject authorizing the attestation
  4. Requester signs a signature authorizing the release of BLT to the attester
  5. Attester attempts to verify data.
  6. If successful, attester writes attestation to the contract and receives payment. If unsuccesful, attester redeems payment signature for the full reward but does not write to the contract.
Interface
/**
 * @notice Function for attester to submit attestation from their own account) 
 * @dev Wrapper for attestForUser using msg.sender
 * @param _subject User this attestation is about
 * @param _requester User requesting and paying for this attestation in BLT
 * @param _reward Payment to attester from requester in BLT
 * @param _paymentNonce Nonce referenced in TokenEscrowMarketplace so payment sig can't be replayed
 * @param _requesterSig Signature authorizing payment from requester to attester
 * @param _dataHash Hash of data being attested and nonce
 * @param _requestNonce Nonce in sig signed by subject so it can't be replayed
 * @param _subjectSig Signed authorization from subject with attestation agreement
 */
function attest(
  address _subject,
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce,
  bytes _requesterSig,
  bytes32 _dataHash,
  bytes32 _requestNonce,
  bytes _subjectSig
) public;
Example
// Web3 Pseudocode
const attestationLogic = AttestationLogic.at("0x132...")

const subjectSig = ethSigUtil.signTypedData(alicePrivkey, {
  data: getFormattedTypedDataAttestationRequest(attestationLogicAddress, 1, combinedDataHash, nonce)
})
const tokenPaymentSig = ethSigUtil.signTypedData(davidPrivkey, {
  data: getFormattedTypedDataPayTokens(
    tokenEscrowMarketplaceAddress,
    1,
    david,
    bob,
    new BigNumber(1e17).toString(10),
    nonce
  )
})

await attestationLogic.attest(
  alice,
  david,
  new BigNumber(1e17),
  paymentNonce,
  tokenPaymentSig,
  dataHash,
  requestNonce,
  subjectSig,
  {from: bob}
)

revokeAttestation

An attestation can be revoked by submitting a revokeAttestation transaction containing the revocationLink string embeeded in the attestation data tree.

Interface
/**
 * @notice Revoke an attestation
 * @dev Link is included in dataHash and cannot be directly connected to a BloomID
 * @param _link bytes string embedded in dataHash to link revocation
 */
function revokeAttestation(
  bytes32 _link
  ) public;
Example
// Web3 Pseudocode
const revokeLink = "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"

await attestationLogic.revokeAttestation(revokeLink, {
  from: bob
})

contest

The attester calls contest to redeem payment for a failed attestation without leaving a permanent negative record associated with a user's BloomID.

Interface
/**
 * @notice Function for attester to reject an attestation and receive payment 
 *  without associating the negative attestation with the subject
 * @param _requester User requesting and paying for this attestation in BLT
 * @param _reward Payment to attester from requester in BLT
 * @param _paymentNonce Nonce referenced in TokenEscrowMarketplace so payment sig can't be replayed
 * @param _requesterSig Signature authorizing payment from requester to attester
 */
function contest(
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce,
  bytes _requesterSig
) public;
Example
// Web3 Pseudocode
const attestationLogic = AttestationLogic.at("0x132...")

const subjectSig = ethSigUtil.signTypedData(alicePrivkey, {
  data: getFormattedTypedDataAttestationRequest(attestationLogicAddress, 1, combinedDataHash, nonce)
})
const tokenPaymentSig = ethSigUtil.signTypedData(davidPrivkey, {
  data: getFormattedTypedDataPayTokens(
    tokenEscrowMarketplaceAddress,
    1,
    david,
    bob,
    new BigNumber(1e17).toString(10),
    paymentNonce
  )
})

await attestationLogic.contest(
  david,
  new BigNumber(1e17),
  paymentNonce,
  tokenPaymentSig,
  {from: attesterAddress}
)

AttestationLogic

Delegated Functions

attestFor

Anyone can collect valid attestation signatures and submit the transaction on behalf of the attester in order to pay transaction fees.

Interface
function attestFor(
  address _subject,
  address _attester,
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce,
  bytes _requesterSig,
  bytes32 _dataHash,
  bytes32 _requestNonce,
  bytes _subjectSig, // Sig of subject with dataHash and requestNonce
  bytes _delegationSig
) public;
SigningLogic
bytes32 constant ATTEST_FOR_TYPEHASH = keccak256(
  "AttestFor(address subject,address requester,uint256 reward,bytes32 paymentNonce,bytes32 dataHash,bytes32 requestNonce)"
);
struct AttestFor {
    address subject;
    address requester;
    uint256 reward;
    bytes32 paymentNonce;
    bytes32 dataHash;
    bytes32 requestNonce;
}

function hash(AttestFor request) internal pure returns (bytes32) {
  return keccak256(abi.encode(
    ATTEST_FOR_TYPEHASH,
    request.subject,
    request.requester,
    request.reward,
    request.paymentNonce,
    request.dataHash,
    request.requestNonce
  ));
}
function generateAttestForDelegationSchemaHash(
  address _subject,
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce,
  bytes32 _dataHash,
  bytes32 _requestNonce
) external view returns (bytes32) {
  return keccak256(
    abi.encodePacked(
      "",
      DOMAIN_SEPARATOR,
      hash(AttestFor(
        _subject,
        _requester,
        _reward,
        _paymentNonce,
        _dataHash,
        _requestNonce
      ))
    )
    );
}

contestFor

Interface
function contestFor(
  address _attester,
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce,
  bytes _requesterSig,
  bytes _delegationSig
) public;
SigningLogic
bytes32 constant CONTEST_FOR_TYPEHASH = keccak256(
  "ContestFor(address requester,uint256 reward,bytes32 paymentNonce)"
);
struct ContestFor {
    address requester;
    uint256 reward;
    bytes32 paymentNonce;
}

function hash(ContestFor request) internal pure returns (bytes32) {
  return keccak256(abi.encode(
    CONTEST_FOR_TYPEHASH,
    request.requester,
    request.reward,
    request.paymentNonce
  ));
}
function generateContestForDelegationSchemaHash(
  address _requester,
  uint256 _reward,
  bytes32 _paymentNonce
) external view returns (bytes32) {
  return keccak256(
    abi.encodePacked(
      "",
      DOMAIN_SEPARATOR,
      hash(ContestFor(
        _requester,
        _reward,
        _paymentNonce
      ))
    )
    );
}

revokeAttestationFor

Interface
function revokeAttestationFor(
  address _sender,
  bytes32 _link,
  bytes32 _nonce,
  bytes _delegationSig
  ) public;
SigningLogic
bytes32 constant REVOKE_ATTESTATION_FOR_TYPEHASH = keccak256(
  "RevokeAttestationFor(bytes32 link,bytes32 nonce)"
);

struct RevokeAttestationFor {
    bytes32 link;
    bytes32 nonce;
}

function hash(RevokeAttestationFor request) internal pure returns (bytes32) {
  return keccak256(abi.encode(
    REVOKE_ATTESTATION_FOR_TYPEHASH,
    request.link,
    request.nonce
  ));
}

function generateRevokeAttestationForDelegationSchemaHash(
  bytes32 _link,
  bytes32 _nonce
) internal view returns (bytes32) {
  return keccak256(
    abi.encodePacked(
      "",
      DOMAIN_SEPARATOR,
      hash(RevokeAttestationFor(
        _link,
        _nonce
      ))
    )
    );
}

AttestationLogic

Initialization

 

When the contract is first deployed it is in an initializable state. The deployer specifies an address that has privileges to migrate data from a previous version of Attestation Logic. Once initialization is complete the initializer relinquishes this privilege.

migrateAttestation

Publish an attestation that was completed in a previous version of this contract. This is the only trusted component of this contract. Once initialization is over this function is disabled.

Interface
/**
 * @notice Submit attestation completed prior to deployment of this contract
 * @dev Gives initializer privileges to write attestations during the initialization period
 * @param _requester user requesting this attestation be completed 
 * @param _attester user completing the attestation
 * @param _subject user this attestation is about
 * @param _dataHash hash of data being attested
 */
function migrateAttestation(
  address _requester,
  address _attester,
  address _subject,
  bytes32 _dataHash
) public onlyDuringInitialization;
Example
const attestationLogic = AttestationLogic.at("[attestation logic address]")
attestationLogic.migrateAttestation(
  requesterAddress,
  attesterAddress,
  subjectAddress,
  dataHash,
  {from: initializer}
)

Events

 

Attestation Logic emits events when attestations are completed, rejected or revoked.

event TraitAttested(
  address subject,
  address attester,
  address requester,
  bytes32 dataHash
  );
event AttestationRejected(address indexed attester, address indexed requester);
event AttestationRevoked(bytes32 link, address attester);