Skip to content

Commit

Permalink
Feat: did resolver storage (#286)
Browse files Browse the repository at this point in the history
* init DidResolverReadonlyStorage
  • Loading branch information
volodymyr-basiuk authored Dec 2, 2024
1 parent 339a4cc commit 335d198
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 44 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@0xpolygonid/js-sdk",
"version": "1.23.1",
"version": "1.24.0",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand Down
1 change: 1 addition & 0 deletions src/credentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './status/sparse-merkle-tree';
export * from './status/resolver';
export * from './status/agent-revocation';
export * from './status/credential-status-publisher';
export * from './status/did-resolver-revocation';
export * from './credential-wallet';
export * from './rhs';
export * from './utils';
Expand Down
24 changes: 24 additions & 0 deletions src/credentials/status/did-resolver-revocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CredentialStatus, RevocationStatus } from '../../verifiable';
import { CredentialStatusResolveOptions, CredentialStatusResolver } from './resolver';

export class DidDocumentCredentialStatusResolver implements CredentialStatusResolver {
constructor(private readonly didResolverUrl: string) {}
async resolve(
credentialStatus: CredentialStatus,
opts?: CredentialStatusResolveOptions | undefined
): Promise<RevocationStatus> {
if (!opts?.issuerDID) {
throw new Error('IssuerDID is not set in options');
}

const url = `${this.didResolverUrl}/1.0/credential-status/${encodeURIComponent(
opts.issuerDID.string()
)}`;
const resp = await fetch(url, {
method: 'POST',
body: JSON.stringify(credentialStatus)
});
const data = await resp.json();
return data;
}
}
90 changes: 90 additions & 0 deletions src/storage/blockchain/did-resolver-readonly-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Hash } from '@iden3/js-merkletree';
import { DIDDocument, VerificationMethod } from '../../iden3comm';
import { resolveDidDocument } from '../../utils';
import { RootInfo, StateInfo, StateProof } from '../entities';
import { IStateStorage } from '../interfaces';
import { DID, Id } from '@iden3/js-iden3-core';
import { JsonRpcProvider } from 'ethers';

export class DidResolverStateReadonlyStorage implements IStateStorage {
constructor(private readonly resolverUrl: string) {}
async getLatestStateById(id: bigint): Promise<StateInfo> {
return this.getStateInfo(id);
}
async getStateInfoByIdAndState(id: bigint, state: bigint): Promise<StateInfo> {
return this.getStateInfo(id, state);
}

async getGISTProof(id: bigint): Promise<StateProof> {
const { didDocument } = await resolveDidDocument(
DID.parseFromId(Id.fromBigInt(id)),
this.resolverUrl
);
const { global } = this.getIden3StateInfo2023(didDocument);
if (!global) {
throw new Error('GIST root not found');
}
const { proof } = global;
if (!proof) {
throw new Error('GIST proof not found');
}
return {
root: global.root,
existence: proof.existence,
siblings: proof.siblings?.map((sibling) => BigInt(sibling)),
index: BigInt(0),
value: BigInt(0),
auxExistence: !!proof.node_aux,
auxIndex: proof.node_aux ? BigInt(proof.node_aux.key) : BigInt(0),
auxValue: proof.node_aux ? BigInt(proof.node_aux.value) : BigInt(0)
};
}

async getGISTRootInfo(root: bigint, userId: bigint): Promise<RootInfo> {
const { didDocument } = await resolveDidDocument(
DID.parseFromId(Id.fromBigInt(userId)),
this.resolverUrl,
{
gist: Hash.fromBigInt(root)
}
);
const { global } = this.getIden3StateInfo2023(didDocument);
if (!global) {
throw new Error('GIST root not found');
}
return global;
}

getRpcProvider(): JsonRpcProvider {
return new JsonRpcProvider();
}

publishState(): Promise<string> {
throw new Error('publishState method not implemented.');
}

publishStateGeneric(): Promise<string> {
throw new Error('publishStateGeneric method not implemented.');
}

private async getStateInfo(id: bigint, state?: bigint): Promise<StateInfo> {
const opts = state ? { state: Hash.fromBigInt(state) } : undefined;
const { didDocument } = await resolveDidDocument(
DID.parseFromId(Id.fromBigInt(id)),
this.resolverUrl,
opts
);
const { info } = this.getIden3StateInfo2023(didDocument);
return { ...info };
}

private getIden3StateInfo2023(didDocument: DIDDocument): VerificationMethod {
const vm: VerificationMethod | undefined = didDocument.verificationMethod?.find(
(i: VerificationMethod) => i.type === 'Iden3StateInfo2023'
);
if (!vm) {
throw new Error('Iden3StateInfo2023 verification method not found');
}
return vm;
}
}
1 change: 1 addition & 0 deletions src/storage/blockchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './state';
export * from './onchain-zkp-verifier';
export * from './onchain-revocation';
export * from './did-resolver-readonly-storage';
export * from './erc20-permit-sig';
86 changes: 48 additions & 38 deletions src/storage/blockchain/onchain-zkp-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,12 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
/**
* supported circuits
*/
private readonly _supportedCircuits = [
private static readonly _supportedCircuits = [
CircuitId.AtomicQueryMTPV2OnChain,
CircuitId.AtomicQuerySigV2OnChain,
CircuitId.AtomicQueryV3OnChain
];

/**
* abi coder to encode/decode structures to solidity bytes
*/
private readonly _abiCoder = new ethers.AbiCoder();
/**
*
* Creates an instance of OnChainZKPVerifier.
Expand All @@ -76,10 +72,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
private readonly _opts?: OnChainZKPVerifierOptions
) {}

/**
* {@inheritDoc IOnChainZKPVerifier.prepareTxArgsSubmitV1}
*/
public async prepareTxArgsSubmitV1(
public static async prepareTxArgsSubmitV1(
txData: ContractInvokeTransactionData,
zkProofResponse: ZeroKnowledgeProofResponse
): Promise<JsonDocumentObjectValue[]> {
Expand All @@ -104,6 +97,15 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {

return payload;
}
/**
* {@inheritDoc IOnChainZKPVerifier.prepareTxArgsSubmitV1}
*/
public async prepareTxArgsSubmitV1(
txData: ContractInvokeTransactionData,
zkProofResponse: ZeroKnowledgeProofResponse
): Promise<JsonDocumentObjectValue[]> {
return OnChainZKPVerifier.prepareTxArgsSubmitV1(txData, zkProofResponse);
}

/**
* {@inheritDoc IOnChainZKPVerifier.submitZKPResponse}
Expand Down Expand Up @@ -219,7 +221,8 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
return new Map<string, ZeroKnowledgeProofResponse[]>().set(txnHash, zkProofResponses);
}

public async prepareTxArgsSubmitV2(
public static async prepareTxArgsSubmitV2(
resolverUrl: string,
txData: ContractInvokeTransactionData,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<JsonDocumentObjectValue[]> {
Expand All @@ -228,9 +231,6 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
`submit cross chain doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitZKPResponseV2}' is supported.`
);
}
if (!this._opts?.didResolverUrl) {
throw new Error(`did resolver url required for crosschain verification`);
}
const gistUpdateArr = [];
const stateUpdateArr = [];
const payload = [];
Expand Down Expand Up @@ -276,11 +276,9 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
gistUpdateResolutionsPending.push(JSON.stringify(gist));

gistUpdateResolutions.push(
this.resolveDidDocumentEip712MessageAndSignature(
DID.parseFromId(gist.id),
this._opts.didResolverUrl,
{ gist: gist.root }
)
this.resolveDidDocumentEip712MessageAndSignature(DID.parseFromId(gist.id), resolverUrl, {
gist: gist.root
})
);
}

Expand All @@ -296,13 +294,9 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
stateUpdateResolutionsPending.push(JSON.stringify(state));

stateUpdateResolutions.push(
this.resolveDidDocumentEip712MessageAndSignature(
DID.parseFromId(state.id),
this._opts.didResolverUrl,
{
state: state.state
}
)
this.resolveDidDocumentEip712MessageAndSignature(DID.parseFromId(state.id), resolverUrl, {
state: state.state
})
);
}

Expand Down Expand Up @@ -346,19 +340,32 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
return [payload, crossChainProofs];
}

private packZkpProof(inputs: string[], a: string[], b: string[][], c: string[]): string {
return this._abiCoder.encode(
public async prepareTxArgsSubmitV2(
txData: ContractInvokeTransactionData,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<JsonDocumentObjectValue[]> {
if (!this._opts?.didResolverUrl) {
throw new Error(`did resolver url required for crosschain verification`);
}
return OnChainZKPVerifier.prepareTxArgsSubmitV2(
this._opts.didResolverUrl,
txData,
zkProofResponses
);
}

private static packZkpProof(inputs: string[], a: string[], b: string[][], c: string[]): string {
return new ethers.AbiCoder().encode(
['uint256[] inputs', 'uint256[2]', 'uint256[2][2]', 'uint256[2]'],
[inputs, a, b, c]
);
}

private packCrossChainProofs(
private static packCrossChainProofs(
gistUpdateArr: GlobalStateUpdate[],
stateUpdateArr: IdentityStateUpdate[]
) {
const proofs = [];

for (const globalStateUpdate of gistUpdateArr) {
proofs.push({
proofType: 'globalStateProof',
Expand All @@ -371,14 +378,14 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
proof: this.packIdentityStateMsg(stateUpdate)
});
}
return this._abiCoder.encode(
return new ethers.AbiCoder().encode(
['tuple(' + 'string proofType,' + 'bytes proof' + ')[]'],
[proofs]
);
}

private packGlobalStateMsg(msg: GlobalStateUpdate): string {
return this._abiCoder.encode(
public static packGlobalStateMsg(msg: GlobalStateUpdate): string {
return new ethers.AbiCoder().encode(
[
'tuple(' +
'tuple(' +
Expand All @@ -394,8 +401,8 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
);
}

private packIdentityStateMsg(msg: IdentityStateUpdate): string {
return this._abiCoder.encode(
private static packIdentityStateMsg(msg: IdentityStateUpdate): string {
return new ethers.AbiCoder().encode(
[
'tuple(' +
'tuple(' +
Expand All @@ -411,16 +418,19 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
);
}

private packMetadatas(
private static packMetadatas(
metas: {
key: string;
value: Uint8Array;
}[]
): string {
return this._abiCoder.encode(['tuple(' + 'string key,' + 'bytes value' + ')[]'], [metas]);
return new ethers.AbiCoder().encode(
['tuple(' + 'string key,' + 'bytes value' + ')[]'],
[metas]
);
}

private getOnChainGistRootStatePubSignals(
private static getOnChainGistRootStatePubSignals(
onChainCircuitId:
| CircuitId.AtomicQueryMTPV2OnChain
| CircuitId.AtomicQuerySigV2OnChain
Expand All @@ -444,7 +454,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
return atomicQueryPubSignals.getStatesInfo();
}

private async resolveDidDocumentEip712MessageAndSignature(
private static async resolveDidDocumentEip712MessageAndSignature(
did: DID,
resolverUrl: string,
opts?: {
Expand Down
2 changes: 2 additions & 0 deletions src/storage/entities/state.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ProofJSON } from '@iden3/js-merkletree';
/**
* state information of identity from chain.
*
Expand Down Expand Up @@ -44,6 +45,7 @@ export interface RootInfo {
replacedAtTimestamp: bigint;
createdAtBlock: bigint;
replacedAtBlock: bigint;
proof?: ProofJSON;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/utils/did-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const resolveDIDDocumentAuth = async (
resolveURL: string,
state?: Hash
): Promise<VerificationMethod | undefined> => {
let url = `${resolveURL}/${did.string().replace(/:/g, '%3A')}`;
let url = `${resolveURL}/${encodeURIComponent(did.string())}`;
if (state) {
url += `?state=${state.hex()}`;
}
Expand Down Expand Up @@ -115,11 +115,11 @@ export const resolveDidDocument = async (
signature?: DIDDocumentSignature;
}
): Promise<DIDResolutionMetadata> => {
let didString = did.string().replace(/:/g, '%3A');
let didString = encodeURIComponent(did.string());
// for gist resolve we have to `hide` user did (look into resolver implementation)
const isGistRequest = opts?.gist && !opts.state;
if (isGistRequest) {
didString = emptyStateDID(did).string();
didString = encodeURIComponent(emptyStateDID(did).string());
}
let url = `${resolverUrl}/1.0/identifiers/${didString}`;

Expand Down
Loading

0 comments on commit 335d198

Please sign in to comment.