Skip to content

Commit

Permalink
feat: didSignedDocumentStatus (#114)
Browse files Browse the repository at this point in the history
* chore: scaffold for new verifier & refactor verifiers

* wip

* feat: didSignedDocumentStatus

* fix: remove data

* chore: added tests

* Chore/did signed adam comment (#116)

* chore: added changes from Adam's comment

* fix: remove signer block

Co-authored-by: Raymond Yeh <[email protected]>

* fix: errors to return UNEXPECTED_ERROR instead of INVALID

* feat: didIdentityProof (#120)

* feat: didIdentityProof

* chore: removed dns-did support in didIdentityProof

* feat: dnsDidProof

* chore: skipping issuers for dns did

* chore: skipping test for issuers not using did in didIdentityProof

* chore: throwing for v3

* fix: existing integration tests

* chore: integration test for documents issued with DID

* fix: comments to fix dnsproof

* fix: rm TODO list

* fix: lint

* fix: comments

Co-authored-by: Raymond Yeh <[email protected]>

* feat: node cache for did resolution

* fix: rm commitlint

* feat: npm audit fix

* feat: using getDnsDidRecords from dnsprove dep

* feat: default verifier without top level DID

* feat: updated to latest OA supporting DID signing (#124)

Co-authored-by: Raymond Yeh <[email protected]>

* fix: rm cloudflare test

* fix: undo temp fix for dnsprove

* fix: types for signed document

* fix: using isSignedWrappedV2Document

* fix: typed reasons and removed any

* feat: better error message coding

Co-authored-by: Raymond Yeh <[email protected]>

BREAKING CHANGE: Removal of previous implementation of signed document
  • Loading branch information
yehjxraymond authored Oct 2, 2020
1 parent b755c10 commit 424f4b1
Show file tree
Hide file tree
Showing 46 changed files with 3,439 additions and 1,479 deletions.
3 changes: 0 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ jobs:
- run:
name: install
command: npm install
- run:
name: commitlint
command: npx --no-install commitlint-circle
- run:
name: lint
command: npm run lint
Expand Down
1,126 changes: 688 additions & 438 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@govtechsg/dnsprove": "^2.0.8",
"@govtechsg/dnsprove": "^2.0.9",
"@govtechsg/document-store": "^2.0.0",
"@govtechsg/open-attestation": "^3.12.0",
"@govtechsg/open-attestation": "^4.0.0",
"@govtechsg/token-registry": "^2.0.0",
"axios": "^0.20.0",
"debug": "^4.1.1",
"did-resolver": "^2.1.1",
"ethers": "^5.0.4",
"snyk": "^1.364.2"
"ethr-did-resolver": "^3.0.0",
"node-cache": "^5.1.2",
"snyk": "^1.364.2",
"web-did-resolver": "^1.3.3"
},
"devDependencies": {
"@commitlint/cli": "^9.0.1",
Expand All @@ -40,7 +45,7 @@
"@types/jest": "^26.0.3",
"@typescript-eslint/eslint-plugin": "^3.5.0",
"@typescript-eslint/parser": "^3.5.0",
"commitizen": "^4.1.2",
"commitizen": "^4.2.1",
"eslint": "^7.4.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-prettier": "^6.11.0",
Expand Down
11 changes: 11 additions & 0 deletions src/common/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class CodedError extends Error {
code: number;

codeString: string;

constructor(message: string, code: number, codeString: string) {
super(message);
this.code = code;
this.codeString = codeString;
}
}
2 changes: 1 addition & 1 deletion src/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { INFURA_API_KEY } from "../config";
export const getProvider = (options: { network: string }): ethers.providers.Provider =>
process.env.ETHEREUM_PROVIDER === "cloudflare"
? new ethers.providers.CloudflareProvider()
: new ethers.providers.InfuraProvider(options.network, process.env.INFURA_API_KEY || INFURA_API_KEY);
: new ethers.providers.InfuraProvider(options.network, INFURA_API_KEY);

export const getIssuersDocumentStore = (
document: WrappedDocument<v2.OpenAttestationDocument> | WrappedDocument<v3.OpenAttestationDocument>
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const INFURA_API_KEY = "bb46da3f80e040e8ab73c0a9ff365d18";
export const INFURA_API_KEY = process.env.INFURA_API_KEY || "bb46da3f80e040e8ab73c0a9ff365d18";
29 changes: 29 additions & 0 deletions src/did/resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Resolver, DIDDocument } from "did-resolver";
import { getResolver as ethrGetResolver } from "ethr-did-resolver";
import { getResolver as webGetResolver } from "web-did-resolver";
import NodeCache from "node-cache";
import { INFURA_API_KEY } from "../config";

const providerConfig = {
networks: [{ name: "mainnet", rpcUrl: `https://mainnet.infura.io/v3/${INFURA_API_KEY}` }],
};

const didResolutionCache = new NodeCache({ stdTTL: 5 * 60 }); // 5 min

export const resolver = new Resolver({
...ethrGetResolver(providerConfig),
...webGetResolver(),
});

export const resolve = async (didUrl: string): Promise<DIDDocument> => {
const cachedResult = didResolutionCache.get<DIDDocument>(didUrl);
if (cachedResult) return cachedResult;
const did = await resolver.resolve(didUrl);
didResolutionCache.set(didUrl, did);
return did;
};

export const getPublicKey = async (did: string, key: string) => {
const { publicKey } = await resolve(did);
return publicKey.find((k) => k.id.toLowerCase() === key.toLowerCase());
};
97 changes: 97 additions & 0 deletions src/did/verifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { PublicKey } from "did-resolver";
import { utils } from "ethers";
import { Proof, v2 } from "@govtechsg/open-attestation";
import { getPublicKey } from "./resolver";
import { Reason, OpenAttestationSignatureCode } from "../types/error";
import { CodedError } from "../common/error";

export interface DidVerificationStatus {
verified: boolean;
did: string;
reason?: Reason;
}

interface VerifySignature {
did: string;
signature: string;
merkleRoot: string;
publicKey: PublicKey;
}

export const verifySecp256k1VerificationKey2018 = ({
did,
publicKey,
merkleRoot,
signature,
}: VerifySignature): DidVerificationStatus => {
const messageBytes = utils.arrayify(merkleRoot);
const { ethereumAddress } = publicKey;
if (!ethereumAddress) {
return {
did,
verified: false,
reason: {
code: OpenAttestationSignatureCode.KEY_MISSING,
codeString: OpenAttestationSignatureCode[OpenAttestationSignatureCode.KEY_MISSING],
message: `ethereumAddress not found on public key ${JSON.stringify(publicKey)}`,
},
};
}

return {
did,
verified: utils.verifyMessage(messageBytes, signature).toLowerCase() === ethereumAddress.toLowerCase(),
};
};

export const verifySignature = async ({
merkleRoot,
identityProof,
proof,
did,
}: {
merkleRoot: string;
identityProof?: v2.IdentityProof;
proof: Proof[];
did?: string;
}): Promise<DidVerificationStatus> => {
if (!identityProof?.key)
throw new CodedError(
"Key is not present",
OpenAttestationSignatureCode.MALFORMED_IDENTITY_PROOF,
"MALFORMED_IDENTITY_PROOF"
);
if (!did) throw new CodedError("DID is not present", OpenAttestationSignatureCode.DID_MISSING, "DID_MISSING");
const { key } = identityProof;
const publicKey = await getPublicKey(did, key);
if (!publicKey)
throw new CodedError(
`No public key found on DID document for the DID ${did} and key ${key}`,
OpenAttestationSignatureCode.KEY_NOT_IN_DID,
"KEY_NOT_IN_DID"
);

const correspondingProof = proof.find((p) => p.verificationMethod.toLowerCase() === key.toLowerCase());
if (!correspondingProof)
throw new CodedError(
`Proof not found for ${key}`,
OpenAttestationSignatureCode.CORRESPONDING_PROOF_MISSING,
"CORRESPONDING_PROOF_MISSING"
);

switch (publicKey.type) {
case "Secp256k1VerificationKey2018":
return verifySecp256k1VerificationKey2018({
did,
publicKey,
merkleRoot,
signature: correspondingProof.signature,
});
default:
throw new CodedError(
`Signature type ${publicKey.type} is currently not support`,
OpenAttestationSignatureCode.UNSUPPORTED_KEY_TYPE,
"UNSUPPORTED_KEY_TYPE"
);
}
};
19 changes: 12 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { SignedWrappedDocument, v2, v3, WrappedDocument } from "@govtechsg/open-attestation";
import { verificationBuilder } from "./verifiers/verificationBuilder";
import { Verifier, Verifiers } from "./types/core";
import { openAttestationHash } from "./verifiers/hash/openAttestationHash";
import { Identity, openAttestationDnsTxt } from "./verifiers/dnsText/openAttestationDnsTxt";
import { openAttestationSignedProof } from "./verifiers/signedProof/openAttestationSignedProof";
import { openAttestationHash } from "./verifiers/documentIntegrity/hash/openAttestationHash";
import { Identity, openAttestationDnsTxt } from "./verifiers/issuerIdentity/dnsText/openAttestationDnsTxt";
import { isValid } from "./validator";
import { openAttestationEthereumTokenRegistryStatus } from "./verifiers/tokenRegistryStatus/openAttestationEthereumTokenRegistryStatus";
import { openAttestationEthereumDocumentStoreStatus } from "./verifiers/documentStoreStatus/openAttestationEthereumDocumentStoreStatus";
import { openAttestationEthereumTokenRegistryStatus } from "./verifiers/documentStatus/tokenRegistryStatus/openAttestationEthereumTokenRegistryStatus";
import { openAttestationEthereumDocumentStoreStatus } from "./verifiers/documentStatus/documentStoreStatus/openAttestationEthereumDocumentStoreStatus";
import { OpenAttestationDidSignedDocumentStatus } from "./verifiers/documentStatus/didSignedDocumentStatus";
import { OpenAttestationDidSignedDidIdentityProof } from "./verifiers/issuerIdentity/didIdentityProof";
import { OpenAttestationDnsDid } from "./verifiers/issuerIdentity/dnsDidProof";

const openAttestationVerifiers: Verifiers[] = [
openAttestationHash,
openAttestationSignedProof,
openAttestationEthereumTokenRegistryStatus,
openAttestationEthereumDocumentStoreStatus,
openAttestationDnsTxt,
OpenAttestationDnsDid,
OpenAttestationDidSignedDocumentStatus,
];

const verify = verificationBuilder<
Expand All @@ -32,8 +35,10 @@ export {
Verifier,
Identity,
openAttestationHash,
openAttestationSignedProof,
openAttestationDnsTxt,
openAttestationEthereumDocumentStoreStatus,
openAttestationEthereumTokenRegistryStatus,
OpenAttestationDnsDid,
OpenAttestationDidSignedDocumentStatus,
OpenAttestationDidSignedDidIdentityProof,
};
24 changes: 24 additions & 0 deletions src/types/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ export enum OpenAttestationHashCode {
DOCUMENT_TAMPERED = 0,
SKIPPED = 2,
}
export enum OpenAttestationDidSignedDocumentStatusCode {
SKIPPED = 0,
UNEXPECTED_ERROR = 1,
MISSING_REVOCATION = 2,
UNSIGNED = 3,
}
export enum OpenAttestationDidSignedDidIdentityProofCode {
SKIPPED = 0,
UNEXPECTED_ERROR = 1,
}
export enum OpenAttestationDnsDidCode {
SKIPPED = 0,
UNEXPECTED_ERROR = 1,
MALFORMED_IDENTITY_PROOF = 2,
}
export enum OpenAttestationSignatureCode {
UNEXPECTED_ERROR = 0,
KEY_MISSING = 1,
MALFORMED_IDENTITY_PROOF = 2,
DID_MISSING = 3,
KEY_NOT_IN_DID = 4,
CORRESPONDING_PROOF_MISSING = 5,
UNSUPPORTED_KEY_TYPE = 6,
}

export interface EthersError extends Error {
reason?: string | string[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { openAttestationHash } from "./openAttestationHash";
import { tamperedDocumentWithCertificateStore } from "../../../test/fixtures/v2/tamperedDocument";
import { document } from "../../../test/fixtures/v2/document";
import { verificationBuilder } from "../verificationBuilder";
import { tamperedDocumentWithCertificateStore } from "../../../../test/fixtures/v2/tamperedDocument";
import { document } from "../../../../test/fixtures/v2/document";
import { verificationBuilder } from "../../verificationBuilder";

const verify = verificationBuilder([openAttestationHash]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { verifySignature } from "@govtechsg/open-attestation";
import { VerificationFragmentType, Verifier } from "../../types/core";
import { OpenAttestationHashCode } from "../../types/error";
import { VerificationFragmentType, Verifier } from "../../../types/core";
import { OpenAttestationHashCode } from "../../../types/error";

const name = "OpenAttestationHash";
const type: VerificationFragmentType = "DOCUMENT_INTEGRITY";
Expand Down
Loading

0 comments on commit 424f4b1

Please sign in to comment.