JS SDK Example
This tutorial shows the steps to run different modules of the Polygon ID JS SDK with exampling code. After the steps for each module, you will find a sample output which is generated when these modules are run.
Steps to Run Example Code
We have created a JS SDK Example repository that you can download and run. Follow these steps:
Download the latest circuits from the following curl command:
curl https://iden3-circuits-bucket.s3.eu-west-1.amazonaws.com/latest.zip --output latest.zip
where s3 is a bucket that has been created for storing the circuits' data in one of the Amazon Simple Storage Service (Amazon S3) for specific regions across the globe.
The
latest.zip
folder is accessed from the s3 bucket and is the output to the locallatest.zip
file.Unzip the
latest.zip
folder downloaded above to thecircuits
folder in the repository:unzip latest.zip -d circuits
To run the scripts, set the values for the following variables:
export WALLET_KEY="...key in hex format with matic balance"
export RPC_URL="...url to polygon amoy network rpc node"
export RHS_URL="..reverse hash service url"
export CONTRACT_ADDRESS="..state v2 contract address in the amoy network"
export CIRCUITS_PATH="..path to the circuits folder"Run the npm command. For this, Node.js (which contains the npm package) must be installed on your system.
npm run start
Identity Creation
Initialize
DataStorage
: To initializeDataStorage
, we need a newCredentialStorage
, which could be as simple as a Memory Data Storage (InMemoryDataSource
). Using Memory Data Storage for new credential storage is not recommended for the production environment. Instead, we could use Browser Data Storage or any other data storage.const dataStorage = {
credential: new CredentialStorage(new InMemoryDataSource<W3CCredential>()),
identity: new IdentityStorage(
new InMemoryDataSource<Identity>(),
new InMemoryDataSource<Profile>()
),
mt: new InMemoryMerkleTreeStorage(40),
states: new EthStateStorage(defaultEthConnectionConfig),
};
The CredentialStorage
accepts the interface of the Data Storage; it does not require Memory or Browser Storage. You can implement your own storage, be it SQL storage or any other type of storage.
Identity
stores identities and profiles.
mt
stores Merkle Trees.
states
stores Ethereum states of identities.
Initialize
CredentialWallet
andIdentityWallet
: To initialize Identity Wallet, we needdataStorage
, andkms
(Key Management System).const memoryKeyStore = new InMemoryPrivateKeyStore();
const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore);
const kms = new KMS();
kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider);
const statusRegistry = new CredentialStatusResolverRegistry();
statusRegistry.register(CredentialStatusType.SparseMerkleTreeProof, new IssuerResolver());
statusRegistry.register(
CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
new RHSResolver(dataStorage.states)
);
const credWallet = new CredentialWallet(dataStorage, statusRegistry);
const wallet = new IdentityWallet(kms, dataStorage, credWallet);For
kms
, we need to define aprovider
that works with keys. For example, we can provide a Baby Jubjub Provider (BJJProvider
), an Ethereum Key Provider, or a Register Key Provider(registerKeyProvider
), to name a few. For each Provider, we need to pass the storage:AbstractPrivateKeyStore
. This storage allows you to create customized encrypted storage. For demo purposes, we have usedmemoryKeyStore
. So in a nutshell, we create storage(memoryKeyStore
), pass it to the Provider(BJJProvider
), and register this Provider in the Key Management System(registerKeyProvider
).After initialization is complete, to create identity, we need to pass some options, which are accepted by the
IdentityCreationOptions
interface. The options are as follows:
export interface IdentityCreationOptions {
method?: DidMethod;
blockchain?: Blockchain;
networkId?: NetworkId;
revocationOpts: {
id: string;
type: CredentialStatusType;
nonce?: number;
};
seed?: Uint8Array;
}
For example:
const seedPhrase: Uint8Array = byteEncoder.encode("seedseedseedseedseedseedseedseed");
const { did, credential } = await wallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
networkId: NetworkId.Amoy,
seed: seedPhrase,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: "https://rhs-staging.polygonid.me",
},
});
If we do not use rhsUrl
within createIdentity() method, we get a credentialStatus
with a URL as id
, which contains an API to fetch the revocation status. Also, in this case, the type
of the credentialStatus
is SparseMerkleTreeProof
.
revocationOpts
should provide info on how to fetch the status of a credential. If we use CredentialStatusType.Iden3ReverseSparseMerkleTreeProof
within createIdentity() method, we get a credentialStatus
with a Reverse Hash Service URL (rhsUrl
) as id
, which, upon running on a browser, shows a response that contains information about the identity state. If we use CredentialStatusType.Iden3SparseMerkleTreeProof
within createIdentity()
method, we get a credentialStatus
with a URL as id
, which contains an API to fetch the revocation status.
Issue Credential
Initialize all storage types
const dataStorage = {
credential: new CredentialStorage(new InMemoryDataSource<W3CCredential>()),
identity: new IdentityStorage(
new InMemoryDataSource<Identity>(),
new InMemoryDataSource<Profile>()
),
mt: new InMemoryMerkleTreeStorage(40),
states: new EthStateStorage(defaultEthConnectionConfig),
};
const memoryKeyStore = new InMemoryPrivateKeyStore();
const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore);
const kms = new KMS();
kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider);
const credWallet = new CredentialWallet(dataStorage);
const statusRegistry = new CredentialStatusResolverRegistry();
statusRegistry.register(CredentialStatusType.SparseMerkleTreeProof, new IssuerResolver());
statusRegistry.register(
CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
new RHSResolver(dataStorage.states)
);
statusRegistry.register(
CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023,
new OnChainResolver([defaultEthConnectionConfig])
);
resolvers.register(CredentialStatusType.Iden3commRevocationStatusV1, new AgentResolver());
const credWallet = new CredentialWallet(dataStorage, statusRegistry);
const wallet = new IdentityWallet(kms, dataStorage, credWallet);Create Issuer's Identity
const seedPhraseIssuer: Uint8Array = byteEncoder.encode("seedseedseedseedseedseedseedseed");
const { did: issuerDID, credential: issuerAuthCredential } = await wallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
networkId: NetworkId.Amoy,
seed: seedPhraseIssuer,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: "https://rhs-staging.polygonid.me",
},
});Create User's Identity
const seedPhraseUser: Uint8Array = byteEncoder.encode("userseedseedseedseedseedseeduser");
const { did: userDID, credential: authBJJCredentialUser } = await identityWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
networkId: NetworkId.Amoy,
seed: seedPhraseUser,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: "https://rhs-staging.polygonid.me",
},
});Create Credential Request (
credentialRequest
) and Issue Credential (issueCredential
):const claimReq: CredentialRequest = {
credentialSchema:
"https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v2.json",
type: "KYCAgeCredential",
credentialSubject: {
id: userDID.toString(),
birthday: 19960424,
documentType: 99,
},
expiration: 12345678888,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: "https://rhs-staging.polygonid.me",
},
};
const issuerCred = await wallet.issueCredential(issuerDID, claimReq);
Output of Run Command
{
"id": "urn:ad777d04-15e9-42ad-bc31-f8acd7191439",
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://schema.iden3.io/core/jsonld/iden3proofs.jsonld",
"https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld"
],
"type": ["VerifiableCredential", "KYCAgeCredential"],
"expirationDate": "2361-03-21T19:14:48.000Z",
"issuanceDate": "2023-04-26T18:54:26.210Z",
"credentialSubject": {
"id": "did:iden3:polygon:amoy:wzwAyDLHL6Nhtj3TnFfUnP7osASXb9hS8BTfa2zeo",
"birthday": 19960424,
"documentType": 99,
"type": "KYCAgeCredential"
},
"issuer": "did:iden3:polygon:amoy:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth",
"credentialSchema": {
"id": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v2.json",
"type": "JsonSchemaValidator2018"
},
"credentialStatus": {
"id": "https://rhs-staging.polygonid.me",
"revocationNonce": 4303,
"type": "Iden3ReverseSparseMerkleTreeProof"
},
"proof": [
{
"type": "BJJSignature2021",
"issuerData": {
"id": "did:iden3:polygon:amoy:wzokvZ6kMoocKJuSbftdZxTD6qvayGpJb3m4FVXth",
"state": {
"rootOfRoots": "0000000000000000000000000000000000000000000000000000000000000000",
"revocationTreeRoot": "0000000000000000000000000000000000000000000000000000000000000000",
"claimsTreeRoot": "6091193ec58a6c020183c2d889a92c32410f31812595f228d67a2bf37e04a729",
"value": "ed17a07e8b78ab979507829fa4d37e663ca5906714d506dec8a174d949c5eb09"
},
"authCoreClaim": "cca3371a6cb1b715004407e325bd993c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd045c3101b2a0bcd60106ff21a680d86af3dbbdec406764f93ab82849410e1c27eb6114eeff7eb030b34d1db28b46d61cb6d7efbec190a0b1c1664664ced80f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"mtp": {
"existence": true,
"siblings": []
},
"credentialStatus": {
"id": "https://rhs-staging.polygonid.me",
"revocationNonce": 0,
"type": "Iden3ReverseSparseMerkleTreeProof"
}
},
"coreClaim": "c9b2370371b7fa8b3dab2a5ba81b68380a00000000000000000000000000000001127b7c7c774b5b5f3bc93a653ccdfbb52d748efb2062f00be85c3117910c0068923001000000000000000000000000000000000000000000000000000000006300000000000000000000000000000000000000000000000000000000000000cf10000000000000281cdcdf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"signature": "ede3a863f234a29a0e2bb80746c37f4f1cfbe2cb0d1c0668d44b6a69ea7b1b110008effb865ffde590ccaf52a16c7188eaa79a52dcf2ab2baa5145e618526b05"
}
]
}
Generate Proof
Initialize all storages including
dataStorage
,identityWallet
,credentialWallet
,circuitStorage
, andstateStorage
.const proofService: IProofService = new ProofService(
idWallet,
credentialWallet,
circuitStorage,
stateStorage,
{ ipfsNodeURL: "https://ipfs.io" }
);IPFS node option is mandatory if you are going to support schemas with
ipfs://
prefix.Create Credential Request (
credentialRequest
):const claimReq: CredentialRequest = {
credentialSchema:
"https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v2.json",
type: "KYCAgeCredential",
credentialSubject: {
id: userDID.toString(),
birthday: 19960424,
documentType: 99,
},
expiration: 1693526400,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: rhsUrl,
},
};Issue Credential (
issueCredential
):const issuerCred = await idWallet.issueCredential(issuerDID, claimReq);
Save Credential(
saveCredential
):await credWallet.save(issuerCred);
Add Credentials to Merkle Tree (
addCredentialsToMerkleTree
) AtomicQueryMTPV2 circuit only :const res = await identityWallet.addCredentialsToMerkleTree([credential], issuerDID);
Publish state to Reverse Hash Service(
publishStateToRHS
) and then to blockchain AtomicQueryMTPV2 circuit only.await identityWallet.publishStateToRHS(issuerDID, rhsUrl);
const ethSigner = new ethers.Wallet(walletKey, (dataStorage.states as EthStateStorage).provider);
Transit State(
transitState
) AtomicQueryMTPV2 circuit only:const txId = await proofService.transitState(
issuerDID,
res.oldTreeState,
true,
dataStorage.states,
ethSigner
);The transaction ID(
txId
) is sent to the network.console.log(txId);
Generate Proof for
ZeroKnowledgeProofRequest
. It uses the AtomicQuerySignV2 as theCircuitId
.
Processe Proof Request:
const proofReq: ZeroKnowledgeProofRequest = {
id: 1,
circuitId: CircuitId.AtomicQueryMTPV2,
optional: false,
query: {
allowedIssuers: ["*"],
type: claimReq.type,
context:
"https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld",
credentialSubject: {
documentType: {
$eq: 99,
},
},
},
};Generate Signature Proof
const { proof, vp } = await proofService.generateProof(proofReq, userDID);
Output of Run Command
{
"id": 1,
"circuitId": "credentialAtomicQueryMTPV2",
"proof": {
"pi_a": [
"16903932334113972519620805189645165148033763067456532043354702748765998282694",
"14387051041597563972727770449725265194885403144389811266303574540189693314411",
"1"
],
"pi_b": [
[
"20868744201672807105014844766210388217012962602503565070866599032913314573906",
"3442846801677856242390734660759775202706413077249960437242009573082897426768"
],
[
"14789189402469891892456734204272864393275089998933018165654790589080248398029",
"18266210435012639289361922267811624205740064923904412505788791153792745549864"
],
["1", "0"]
],
"pi_c": [
"10584826769880435007204063174646630112221834067523356507551322322080614020116",
"15098609038306685045480544545659718185382866226369358624875087099910401452336",
"1"
],
"protocol": "groth16",
"curve": "bn128"
},
"pub_signals": [
"1",
"21568225469889458305914841490175280093555015071329787375641431262509208065",
"1",
"19898531390599208021876718705689344940605246460654065917270282371355906561",
"11294568480342416193654942432097549342373955797199827794411652699521935875647",
"1",
"15316103435703269893947162180693935798669021972402205481551466808302934202991",
"1682538797",
"74977327600848231385663280181476307657",
"0",
"17040667407194471738958340146498954457187839778402591036538781364266841966",
"2",
"1",
"99",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0",
"0"
]
}
Verify Proof
- Once the proof is generated, the Verifier can verify the proof (Signature or MTP).
Verify Proof:
const sigProofOk = await proofService.verifyProof(
proof,
CircuitId.AtomicQuerySigV2 // or CircuitId.AtomicQueryMTPV2
);