@gahmen/oa-verify
v0.0.0
Published
[![CircleCI](https://circleci.com/gh/Open-Attestation/oa-verify.svg?style=svg)](https://circleci.com/gh/Open-Attestation/oa-verify)
Downloads
3
Readme
Open Attestation (Verify)
The Open Attestation (Verify) repository is the codebase for the npm module that allows you to verify wrapped document programmatically. This is useful if you are building your own API or web components. Some common use cases where you will need this module:
This module does not provide the following functionality:
- Programmatic wrapping of OA documents (refer to Open Attestation)
- Encryption or decryption of OA documents (refer to Open Attestation (Encryption))
- Programmatic issuance/revocation of document on the Ethereum blockchain
Installation
npm i @govtechsg/oa-verify
Usage
Verifying a document
A verification happens on a wrapped document, and it consists of answering to some questions:
- Has the document been tampered with ?
- Is the issuance state of the document valid ?
- Is the document issuer identity valid ? (see identity proof)
A wrapped document (shown below) created using Open Attestation would be required.
NOTE: The document shown below is valid and has been issued on the goerli network
{
"version": "https://schema.openattestation.com/2.0/schema.json",
"data": {
"billFrom": {},
"billTo": { "company": {} },
"$template": {
"type": "f76f4d39-8d23-455b-96ba-5889e0233641:string:EMBEDDED_RENDERER",
"name": "575f0624-7f43-484c-9285-edd1ae96ebc6:string:INVOICE",
"url": "fb61f072-64e9-4c2f-83bf-ae68fd911414:string:https://generic-templates.tradetrust.io"
},
"issuers": [
{
"name": "ed121e9e-8f70-4a01-a422-4509d837c13f:string:Demo Issuer",
"documentStore": "08948d61-9392-459f-b476-e3c51961f04b:string:0x49b2969bF0E4aa822023a9eA2293b24E4518C1DD",
"identityProof": {
"type": "61c13b84-181a-43aa-85f5-dfe89e4b6963:string:DNS-TXT",
"location": "d7ba5e33-cf5f-4fcc-a4b7-3e6e17324966:string:demo-tradetrust.openattestation.com"
},
"revocation": {
"type": "23d47c6b-4384-4c31-90ca-8284602f6b3e:string:NONE"
}
}
],
"links": {
"self": {
"href": "121c55c0-864d-4e54-a1f0-86bec4b9a050:string:https://action.openattestation.com?q=%7B%22type%22%3A%22DOCUMENT%22%2C%22payload%22%3A%7B%22uri%22%3A%22https%3A%2F%2Ftradetrust-functions.netlify.app%2F.netlify%2Ffunctions%2Fstorage%2Faea9cb1a-816a-4fd7-b3a9-84924dc9a9e9%22%2C%22key%22%3A%22d80b453e53bb26d3b36efe65f18f0482f52d97cffad6f6c9c195d10e165b9a83%22%2C%22permittedActions%22%3A%5B%22STORE%22%5D%2C%22redirect%22%3A%22https%3A%2F%2Fdev.tradetrust.io%2F%22%2C%22chainId%22%3A%225%22%7D%7D"
}
},
"network": {
"chain": "05eb1707-5426-41d8-8fde-bc48ff0f2182:string:ETH",
"chainId": "ae505425-2df7-4597-87d2-037418d7bcbf:string:5"
}
},
"signature": {
"type": "SHA3MerkleProof",
"targetHash": "f292056ed5e5535400cec63b78a84ec384d2d77117e1606a17644e7b97a03cac",
"proof": [],
"merkleRoot": "f292056ed5e5535400cec63b78a84ec384d2d77117e1606a17644e7b97a03cac"
}
}
To perform verification check on the document:
// index.ts
import { isValid, verify } from "@govtechsg/oa-verify";
import * as document from "./document.json";
const fragments = await verify(document as any);
console.log(isValid(fragments)); // output true
Custom verification
By default the provided verify
method performs multiple checks on a document
- for the type
DOCUMENT_STATUS
: it runsOpenAttestationEthereumDocumentStoreStatus
,OpenAttestationEthereumTokenRegistryStatus
andDidSignedDocumentStatus
verifiers - for the type
DOCUMENT_INTEGRITY
: it runsOpenAttestationHash
verifier - for the type
ISSUER_IDENTITY
: it runsOpenAttestationDnsTxt
andDnsDidProof
verifiers
All those verifiers are exported as openAttestationVerifiers
You can build your own verify method or your own verifiers:
// creating your own verify using default exported verifiers
import { verificationBuilder, openAttestationVerifiers } from "@govtechsg/oa-verify";
const verify1 = verificationBuilder(openAttestationVerifiers, { network: "goerli" }); // this verify is equivalent to the one exported by the library
// this verify is equivalent to the one exported by the library
const verify2 = verificationBuilder([openAttestationVerifiers[0], openAttestationVerifiers[1]], {
network: "goerli",
}); // this verify only run 2 verifiers
// creating your own verify using custom verifier
import { verificationBuilder, openAttestationVerifiers, Verifier } from "@govtechsg/oa-verify";
const customVerifier: Verifier<any> = {
skip: () => {
// return a SkippedVerificationFragment if the verifier should be skipped or throw an error if it should always run
},
test: () => {
// return true or false
},
verify: async (document) => {
// perform checks and returns a fragment
},
};
// create your own verify function with all verifiers and your custom one
const verify = verificationBuilder([...openAttestationVerifiers, customVerifier], { network: "goerli" });
Refer to Extending Custom Verification to find out more on how to create your own custom verifier.
Custom validation
Fragments would be produced after verifying a document. Each fragment will help to determine if the individual type mentioned here is valid or not, and would collectively prove the validity of the document.
The isValid
function will execute over fragments and determine if the fragments produced a valid result. By default the function will return true if a document fulfill the following conditions:
- The document has NOT been tampered, AND
- The document has been issued, AND
- The document has NOT been revoked, AND
- The issuer identity is valid.
The function also allows a list of types to check for as a second parameter.
// index.ts
import { isValid, openAttestationVerifiers, verificationBuilder } from "@govtechsg/oa-verify";
import * as document from "./document.json";
const verify = verificationBuilder(openAttestationVerifiers, {
network: "mainnet",
});
const fragments = await verify(document as any);
console.log(isValid(fragments, ["DOCUMENT_INTEGRITY"])); // output true
console.log(isValid(fragments, ["DOCUMENT_STATUS"])); // output false
console.log(isValid(fragments, ["ISSUER_IDENTITY"])); // outpute false
console.log(isValid(fragments)); // output false
isValid(fragments, ["DOCUMENT_INTEGRITY"])
returns true because the integrity of the document is not dependent on the network it has been published to.isValid(fragments, ["DOCUMENT_STATUS"])
returns false because the document has not been published on Ethereum main network.isValid(fragments, ["ISSUER_IDENTITY"])
returns false because there is no DNS-TXT record associated with the Ethereum main network's document store.isValid(fragments)
returns false because at least one of the above returns false.
Listening to individual verification method
The verify
function provides an option to listen to individual verification methods. It might be useful if you want, for instance, to provide individual loaders on your UI.
// index.ts
import { isValid, openAttestationVerifiers, verificationBuilder } from "@govtechsg/oa-verify";
import * as document from "./document.json";
const verify = verificationBuilder(openAttestationVerifiers, {
network: "goerli",
});
const promisesCallback = (verificationMethods: any) => {
for (const verificationMethod of verificationMethods) {
verificationMethod.then((fragment: any) => {
console.log(`${fragment.name} has been resolved with status ${fragment.status}`);
});
}
};
const fragments = await verify(document as any, promisesCallBack);
console.log(isValid(fragments)); // output true
Advanced usage
Extending Custom Verification
Extending from Custom Verification section, we will learn how to write custom verification methods and how you can distribute your own verifier.
Building a custom verification method
We will write a verification method having the following rules:
- it must run only on document having their version equal to
https://schema.openattestation.com/2.0/schema.json
. - it must return a valid fragment, if and only if the document data hold a name property with the value
Certificate of Completion
Document version must be equal to https://schema.openattestation.com/2.0/schema.json
This is where skip
and test
methods come into play. We will use the test
method to return when the verification method run, and the skip
method to explain why it didn't run:
// index.ts
import { verificationBuilder, openAttestationVerifiers, Verifier, isValid } from "@govtechsg/oa-verify";
import { getData } from "@govtechsg/open-attestation";
import * as document from "./document.json";
const customVerifier: Verifier<any> = {
skip: async () => {
return {
status: "SKIPPED",
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
reason: {
code: 0,
codeString: "SKIPPED",
message: `Document doesn't have version equal to 'https://schema.openattestation.com/2.0/schema.json'`,
},
};
},
test: () => document.version === "https://schema.openattestation.com/2.0/schema.json",
};
we use
DOCUMENT_INTEGRITY
type because we check for the content of the document.
Document holds correct name
property
Once we have decided when
the verification method run, it's time to write the logic of the verifier in the verify
method. We will use getData utility to access the data of the document and return the appropriate fragment depending on the content:
// index.ts
import { verificationBuilder, openAttestationVerifiers, Verifier, isValid } from "@govtechsg/oa-verify";
import { getData } from "@govtechsg/open-attestation";
import * as document from "./document.json";
const customVerifier: Verifier<any> = {
skip: async () => {
/* content has been defined in the section above */
},
test: () => /* content has been defined in the section above */,
verify: async (document: any) => {
const documentData = getData(document);
if (documentData.name !== "Certificate of Completion") {
return {
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
data: documentData.name,
reason: {
code: 1,
codeString: "INVALID_NAME",
message: `Document name is ${documentData.name}`,
},
status: "INVALID",
};
}
return {
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
data: documentData.name,
status: "VALID",
};
},
};
Building a custom verify method
The verify
function is built to run a list of verification method. Each verifier will produce a fragment that will help to determine if the document is valid. OpenAttestation comes with its own set of verification methods available in openAttestationVerifiers
.
The verificationBuilder
function helps you to create custom verification method. You can reuse the default one exported by the library.
Extending from what have been mentioned in Custom Verification, let's now build a new verifier using our custom verification method:
// index.ts
import { verificationBuilder, openAttestationVerifiers, Verifier, isValid } from "@govtechsg/oa-verify";
import { getData } from "@govtechsg/open-attestation";
import document from "./document.json";
// our custom verifier will be valid only if the document version is not https://schema.openattestation.com/2.0/schema.json
const customVerifier: Verifier<any> = {
skip: async () => {
return {
status: "SKIPPED",
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
reason: {
code: 0,
codeString: "SKIPPED",
message: `Document doesn't have version equal to 'https://schema.openattestation.com/2.0/schema.json'`,
},
};
},
test: () => document.version === "https://schema.openattestation.com/2.0/schema.json",
verify: async (document: any) => {
const documentData = getData(document);
if (documentData.name !== "Certificate of Completion") {
return {
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
data: documentData.name,
reason: {
code: 1,
codeString: "INVALID_NAME",
message: `Document name is ${documentData.name}`,
},
status: "INVALID",
};
}
return {
type: "DOCUMENT_INTEGRITY",
name: "CustomVerifier",
data: documentData.name,
status: "VALID",
};
},
};
// create your own verify function with all verifiers and your custom one
const verify = verificationBuilder([...openAttestationVerifiers, customVerifier], { network: "goerli" });
const fragments = await verify(document);
console.log(isValid(fragments)); // return false
console.log(fragments.find((fragment: any) => fragment.name === "CustomVerifier")); // display the details on our specific verifier
The document that we created is not valid against our own verifier because the name property does not exist. Try again with the following document:
{
"version": "https://schema.openattestation.com/2.0/schema.json",
"data": {
"name": "66e35a92-9e97-4ffc-b94e-769773dd7535:string:Certificate of Completion",
"issuers": [
{
"documentStore": "375a13f9-ca3d-4a1f-a0c9-1fa92e43a3ec:string:0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3",
"name": "448c7f62-3a93-4792-a157-fabcbf15b91a:string:University of Blockchain",
"identityProof": {
"type": "dcfc17e0-a178-4bb8-b0fb-6a2cfddb8f2f:string:DNS-TXT",
"location": "e3f54dbf-bb51-41bb-9511-e01a5c07ea86:string:example.openattestation.com"
}
}
]
},
"privacy": { "obfuscatedData": [] },
"signature": {
"type": "SHA3MerkleProof",
"targetHash": "975887a864e11fbe27e90f4759c44db90193abc237dede81cd3cd7ca45c46522",
"proof": [],
"merkleRoot": "975887a864e11fbe27e90f4759c44db90193abc237dede81cd3cd7ca45c46522"
}
}
Environment variables
PROVIDER_API_KEY
: let you provide your own PROVIDER API key.PROVIDER_ENDPOINT_URL
: let you provide your preferred JSON-RPC HTTP API URL.PROVIDER_NETWORK
: let you specify the network to use, i.e. "homestead", "mainnet", "goerli".PROVIDER_ENDPOINT_TYPE
: let you specify the provider to use, i.e. "infura", "alchemy", "jsonrpc".
Provider that is supported: Infura, EtherScan, Alchemy, JSON-RPC
Switching network
You may build the verifier to verify against a custom network by either:
- providing your own web3 provider
- specifying the network name (provider will be using the default ones)
To provide your own provider:
const verify = verificationBuilder(openAttestationVerifiers, { provider: customProvider });
To specify network:
const verify = verificationBuilder(openAttestationVerifiers, { network: "goerli" });
Specify resolver
oa-verify
exposes a method, called createResolver
that allows you to easily create custom resolvers, to resolve DIDs:
import { createResolver, verificationBuilder, openAttestationVerifiers } from "@govtechsg/oa-verify";
const resolver = createResolver({
networks: [{ name: "my-network", rpcUrl: "https://my-private-chain/besu", registry: "0xaE5a9b9..." }],
});
const verify = verificationBuilder(openAttestationVerifiers, { resolver });
At the moment, oa-verify supports two did resolvers:
Provider
You may generate a provider using the provider generator, it supports INFURA
, ALCHEMY
, ETHERSCAN
and JsonRPC
provider.
It requires a set of options:
network
: The network may be specified as a string for a common network name, i.e. "homestead", "mainnet", "goerli".provider
: The provider may be specified as a string, i.e. "infura", "alchemy" or "jsonrpc".url
: The url may be specified as a string in which is being used to connect to a JSON-RPC HTTP APIapiKey
: The apiKey may be specified as a string for use together with the provider. If no apiKey is provided, a default shared API key will be used, which may result in reduced performance and throttled requests.
Example
The most basic way to use:
import { utils } from "@govtechsg/oa-verify";
const provider = utils.generateProvider();
// This will generate an infura provider using the default values.
Alternate way 1 (with environment variables):
// environment file
PROVIDER_NETWORK = "goerli";
PROVIDER_ENDPOINT_TYPE = "infura";
PROVIDER_ENDPOINT_URL = "http://jsonrpc.com";
PROVIDER_API_KEY = "ajdh1j23";
// provider file
import { utils } from "@govtechsg/oa-verify";
const provider = utils.generateProvider();
// This will use the environment variables declared in the files automatically.
Alternate way 2 (passing values in as parameters):
import { utils } from "@govtechsg/oa-verify";
const providerOptions = {
network: "goerli",
providerType: "infura",
apiKey: "abdfddsfe23232",
};
const provider = utils.generateProvider(providerOptions);
// This will generate a provider based on the options provided.
// NOTE: by using this way, it will override all environment variables and default values.
Utils and types
Overview
Various utilities and types are available to assert the correctness of fragments. Each verification method exports types for the fragment, and the data associated with the fragment.
- fragment types are available in 4 flavors:
VALID
,INVALID
,SKIPPED
, andERROR
. VALID
andINVALID
fragment data are available in 2 flavors most of the time, one for each version ofOpenAttestation
.
This library provides types and utilities to:
- get a specific fragment from all the fragments returned by the
verify
method - narrow down to a specific type of fragment
- narrow down to a specific fragment data
Let's see how to use it
Example
import { utils } from "@govtechsg/oa-verify";
const fragments = verify(documentValidWithCertificateStore, { network: "goerli" });
// return the correct fragment, correctly typed
const fragment = utils.getOpenAttestationEthereumTokenRegistryStatusFragment(fragments);
if (utils.isValidFragment(fragment)) {
// guard to narrow to the valid fragment type
const { data } = fragment;
if (ValidTokenRegistryDataV2.guard(data)) {
// data is correctly typed here
}
}
Note that in the example above, using utils.isValidFragment
might be unnecessary. It's possible to use directly ValidTokenRegistryDataV2.guard
over the data.
List of utilities
getOpenAttestationHashFragment
getOpenAttestationDidSignedDocumentStatusFragment
getOpenAttestationEthereumDocumentStoreStatusFragment
getOpenAttestationEthereumTokenRegistryStatusFragment
getOpenAttestationDidIdentityProofFragment
getOpenAttestationDnsDidIdentityProofFragment
getOpenAttestationDnsTxtIdentityProofFragment
getDocumentIntegrityFragments
getDocumentStatusFragments
getIssuerIdentityFragments
isValidFragment
: type guard to filter onlyVALID
fragment typeisInvalidFragment
: type guard to filter onlyINVALID
fragment typeisErrorFragment
: type guard to filter onlyERROR
fragment typeisSkippedFragment
: type guard to filter onlySKIPPED
fragment type
Verification method
| Name | Type | Description | Present in default verifier? | | ------------------------------------------ | ------------------ | ---------------------------------------------------------------------------- | ---------------------------- | | OpenAttestationHash | DOCUMENT_INTEGRITY | Verify that merkle root and target hash matches the certificate | Yes | | OpenAttestationDidSignedDocumentStatus | DOCUMENT_STATUS | Verify the validity of the signature of a DID signed certificate | Yes | | OpenAttestationEthereumDocumentStoreStatus | DOCUMENT_STATUS | Verify the certificate has been issued to the document store and not revoked | Yes | | OpenAttestationEthereumTokenRegistryStatus | DOCUMENT_STATUS | Verify the certificate has been issued to the token registry and not revoked | Yes | | OpenAttestationDidIdentityProof | ISSUER_IDENTITY | Verify identity of DID (similar to OpenAttestationDidSignedDocumentStatus) | No | | OpenAttestationDnsDidIdentityProof | ISSUER_IDENTITY | Verify identify of DID certificate using DNS-TXT | Yes | | OpenAttestationDnsTxtIdentityProof | ISSUER_IDENTITY | Verify identify of document store certificate using DNS-TXT | Yes |
Development
To run tests
npm run test
To generate test documents (for v3), you may use the script at scripts/generate.v3.ts
and run the command
npm run generate:v3
License
Additional information
- For Verification SDK implementation follow our Verifier ADR.
- Found a bug ? Having a question ? Want to share an idea ? Reach us out on the Github repository.`