Verifiable Credentials issued by DIDs provide powerful verifiability on the claims made. You can trustlessly verify that a specific identity made a specific claim. But, by default, you cannot be sure *when* a claim was made.
Although credentials include a `validFrom` (in the VC 2.0 spec) property indicating when the credential was issued, this is simply a claim being made by the issuer. If the issuer is a known and trusted entity, this might be alright. But in more complex systems with many parties, you might not want to rely on this claimed timestamp to make decisions.
Verifiable Credentials allow us to build Web3 applications at Web2 scale, but without verifiable timestamping, the possibilities are limited.
Usually this is when a developer will say “well I’ll just put a hash of the credential on-chain, verifiable timestamping is one of the fundamental use-cases for blockchains”, but doing so requires an on-chain transaction. Regular application users won’t accept having to pay fees just to interact with your application, and if you use meta-txs and pay the gas fees on behalf of your users, the costs would be overwhelming.
This is where Witness Protocol comes in. Witness lets you take advantage of blockchain timestamping without making a transaction. If you submit a hash to their API, they will aggregate it along with other submitted hashes into a variation of a Merkle Tree, the root of which is put on chain. Witness then provides you with a proof that can be used to trustlessly verify that a piece of content was witnessed at a certain blocktime, even if the Witness service shuts down one day.

There are a ton of really cool Web3 applications we can build with “witnessed credentials”. I decided to take a few hours to see what kind of prototype I could build for a real Web3 application using Verifiable Credentials, the Witness Protocol, and DIDComm. Using the Witness API and Veramo’s Agent Explorer, I created a simple peer-to-peer Poll application that allows users to create and send each other polls that can be trustlessly verified without the friction of on-chain transactions.

Everything required for this application is included in an Agent Explorer Plugin that you can see here: https://github.com/nickreynolds/agent-explorer-plugin-witness . And you can even import this plugin to your own instance of the Agent Explorer or on https://explore.veramo.io/ . There is no special back-end infrastructure that needs be setup, all of the credential creation and verification happens locally in the browser and the communication happens over DIDComm.
For this blog post, I’ll just cover some of the basics, rather than walking through the entire plugin, which involves a fair amount of React, but if you’re interested in how the rest works, you can check out the plugin’s source code.
First, we’ll create a file to hold some helper functions for working with the Witness API:
import { WitnessClient } from "@witnessco/client";
// put your API key here or just don't include one, you might just get rate-limited
export const API_KEY = ''
export const createWitnessHash = (content: string): `0x${string}`=> {
const witness = new WitnessClient(API_KEY)
return witness.hash(content)
}
export const getTimestamp = async (leafHash: `0x${string}`): Promise<Date|undefined> => {
const witness = new WitnessClient(API)
try {
const timestamp = await witness.getTimestampForLeafHash(leafHash);
// console.log("timestamp: ", timestamp)
return timestamp
} catch (ex) {
console.log("no timestamp found")
await postLeaf(leafHash)
return undefined
}
};
export const postLeaf = async (leafHash: `0x${string}`): Promise<any> => {
const witness = new WitnessClient(API_KEY)
const result = await witness.postLeaf(leafHash);
console.log("result: ", result)
return result
};
Our application will just use 2 Verifiable Credential types that we’ll define: WitnessPollAnnouncement and WitnessPollResponse.
Inside a React component (a form for creating polls), we’ll create a credential, then send the hash to witness so it can get batched and eventually given an on-chain checkpoint. Veramo has its own system for hashing credentials canonically which uses IPFS CIDs, so just to be safe, we’ll hash the Veramo hash with Witness’s hash function which uses Keccak-256 hashes compatible with Ethereum:
const identifier = await issuerAgent?.didManagerGet({ did })
const usableProofs = await issuerAgent.listUsableProofFormats(identifier)
const proofFormat = usableProofs.includes('jwt') ? 'jwt' : usableProofs[0]
const credential = await issuerAgent.createVerifiableCredential({
save: true,
proofFormat: (proofFormat as ProofFormat),
credential: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
type: ['VerifiableCredential', 'WitnessPollAnnouncement'],
issuer: { id: did },
issuanceDate: new Date().toISOString(),
credentialSubject: {
title,
pollOptions: pollOptions.map(({option}) => option)
},
},
})
const credhash = await agent?.dataStoreSaveVerifiableCredential({verifiableCredential: credential})
const witnessHash = createWitnessHash(credhash!)
await postLeaf(witnessHash)
We can send this credential to any DID that has DIDComm enabled in order to inform them of the poll. There are a number of ways to do this in the Agent Explorer, so we won’t detail them now.
Next, we’ll create a CredentialHeaderComponent to display the Witness state. This is a special component that any Agent Explorer Plugin can define. Whenever the Agent Explorer renders a credential, it will add any of these plugin components to the credential’s header, allowing you to render additional custom information.
import React from 'react'
import { Typography, Space } from 'antd'
import { CheckOutlined } from '@ant-design/icons'
import { useQuery } from 'react-query'
import { UniqueVerifiableCredential } from '@veramo/core'
import { getTimestamp, createWitnessHash } from '../api'
interface WitnessedCredentialHeaderProps {
credential: UniqueVerifiableCredential
}
export const WitnessedCredentialHeader: React.FC<WitnessedCredentialHeaderProps> = ({
credential,
}) => {
const { data: witnessData, isLoading: isLoadingWitnessData } = useQuery(
['witness-data', credential.hash],
() => {
return getTimestamp(createWitnessHash(credential.hash))
}
)
if (!witnessData) {
console.log("credential not yet witnessed")
}
const date = React.useMemo(() => {
if (!witnessData) return undefined
return witnessData
}, [witnessData])
return (
<>
{date && <Space direction='horizontal' align='baseline' >
<CheckOutlined />
<div>
<Typography.Text>{`Witnessed: ${date.toDateString()}`}</Typography.Text>
</div>
</Space>}
</>
)
}
Now, whenever the Agent Explorer is rendering a credential, this component will check if that credential has been witnessed, and if so, display the date it was first timestamped on-chain.

This is essentially all we need to do to start building interesting applications that can rely on credential issuance times without needing to do (or pay for) any on chain transactions ourselves. In this application, WitnessPollResponses are sent via DIDComm back to the original poll creator. When rendering poll results, we can check if (and when) a response was made and decide whether or not to include it in the results or weight it differently than unwitnessed votes.
There are some other components that go into creating the entire application flow, so if you’re interested you can head over to the plugin repo to check out the entire project: https://github.com/nickreynolds/agent-explorer-plugin-witness
If you have any questions about this implementation, feel free to pop over to the Veramo Discord: https://discord.gg/FRRBdjemHV or join us on Thursdays for the Veramo User Group hosted by DIF (meetings are announced and zoom links provided in both the Veramo Discord and DIF’s Discord)

