Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: did resolver storage #286

Merged
merged 14 commits into from
Dec 2, 2024
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
23 changes: 23 additions & 0 deletions src/credentials/status/did-resolver-revocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 didString = opts?.issuerDID.string().replace(/:/g, '%3A');
const url = `${this.didResolverUrl}/1.0/credential-status/${didString}`;
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
const resp = await fetch(url, {
method: 'POST',
body: JSON.stringify(credentialStatus)
});
const data = await resp.json();
return data;
}
}
4 changes: 2 additions & 2 deletions src/iden3comm/types/protocol/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
DIDDocument as DidResolverDidDocument,
VerificationMethod as DidResolverVerificationMethod
} from 'did-resolver';
import { RootInfo, StateInfo } from '../../../storage';
import { RootInfoWithProof, StateInfo } from '../../../storage';

/** AuthorizationResponseMessage is struct the represents iden3message authorization response */
export type AuthorizationResponseMessage = BasicMessage & {
Expand Down Expand Up @@ -89,5 +89,5 @@ export type DIDDocument = DidResolverDidDocument & {
export type VerificationMethod = DidResolverVerificationMethod & {
published?: boolean;
info?: StateInfo;
global?: RootInfo;
global?: RootInfoWithProof;
vmidyllic marked this conversation as resolved.
Show resolved Hide resolved
};
93 changes: 93 additions & 0 deletions src/storage/blockchain/did-resolver-readonly-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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 vm = (didDocument as DIDDocument).verificationMethod?.find(
(i) => i.type === 'Iden3StateInfo2023'
);
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
if (!vm) {
throw new Error('Iden3StateInfo2023 verification method not found');
}
const { global } = vm as VerificationMethod;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { global } = vm as VerificationMethod;
const { global } = vm;

if (!global) {
throw new Error('GIST root not found');
}
const { proof } = global;
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 vm = (didDocument as DIDDocument).verificationMethod?.find(
(i) => i.type === 'Iden3StateInfo2023'
);
if (!vm) {
throw new Error('Iden3StateInfo2023 verification method not found');
}
const { global } = vm as VerificationMethod;
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
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> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add space between functions

throw new Error('publishStateGeneric method not implemented.');
}

private async getStateInfo(id: bigint, state?: bigint): Promise<StateInfo> {
const opts = state ? { state: Hash.fromBigInt(state) } : undefined;
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
const { didDocument } = await resolveDidDocument(
DID.parseFromId(Id.fromBigInt(id)),
this.resolverUrl,
opts
);
const vm = (didDocument as DIDDocument).verificationMethod?.find(
(i) => i.type === 'Iden3StateInfo2023'
);
if (!vm) {
throw new Error('Iden3StateInfo2023 verification method not found');
}
const { info } = vm as VerificationMethod;
return { ...info };
}
}
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,
vmidyllic marked this conversation as resolved.
Show resolved Hide resolved
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(
private static packGlobalStateMsg(msg: GlobalStateUpdate): string {
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
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
11 changes: 11 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 @@ -46,6 +47,16 @@ export interface RootInfo {
replacedAtBlock: bigint;
}

/**
* global identity state root info from DID resolver document
*
* @public
* @interface RootInfoWithProof
*/
export interface RootInfoWithProof extends RootInfo {
proof: ProofJSON;
}

/**
* identity state message
*
Expand Down
2 changes: 1 addition & 1 deletion src/utils/did-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const resolveDidDocument = async (
// 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 = emptyStateDID(did).string().replace(/:/g, '%3A');
Kolezhniuk marked this conversation as resolved.
Show resolved Hide resolved
}
let url = `${resolverUrl}/1.0/identifiers/${didString}`;

Expand Down
Loading