@0xpolygonid/js-sdk
Version:
SDK to work with Polygon ID
474 lines (473 loc) • 22.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.packEthIdentityProof = exports.OnChainZKPVerifier = exports.toTxDataArgs = exports.FunctionSignatures = void 0;
const ethers_1 = require("ethers");
const iden3comm_1 = require("../../iden3comm");
const ZkpVerifier_json_1 = __importDefault(require("./abi/ZkpVerifier.json"));
//import { UniversalVerifier } from '@iden3/universal-verifier-v2-abi';
const universal_verifier_v2_abi_1 = require("@iden3/universal-verifier-v2-abi");
const blockchain_1 = require("../../blockchain");
const js_iden3_core_1 = require("@iden3/js-iden3-core");
const circuits_1 = require("../../circuits");
const utils_1 = require("../../utils");
const js_merkletree_1 = require("@iden3/js-merkletree");
const common_1 = require("./common");
const maxGasLimit = 10000000n;
/**
* Supported function signature for SubmitZKPResponse
*/
var FunctionSignatures;
(function (FunctionSignatures) {
/**
* solidity identifier for function signature:
* function submitZKPResponse(uint64 requestId, uint256[] calldata inputs,
* uint256[2] calldata a, uint256[2][2] calldata b, uint256[2] calldata c) public
*/
FunctionSignatures["SubmitZKPResponseV1"] = "b68967e2";
//function submitZKPResponseV2(tuple[](uint64 requestId,bytes zkProof,bytes data),bytes crossChainProof)
FunctionSignatures["SubmitZKPResponseV2"] = "ade09fcd";
//function submitResponse(tuple(string authMethod,bytes proof),tuple(uint256 requestId,bytes proof,bytes metadata)[],bytes crossChainProof)
FunctionSignatures["SubmitResponse"] = "06c86a91";
})(FunctionSignatures = exports.FunctionSignatures || (exports.FunctionSignatures = {}));
const toTxDataArgs = function (res) {
return [
{
authMethod: res.authProof.raw.authMethod,
proof: res.authProof.encoded
},
res.proofs.map((p) => {
return {
requestId: p.requestId,
proof: p.encoded,
metadata: p.metadata
};
}),
res.crossChainProof.encoded
];
};
exports.toTxDataArgs = toTxDataArgs;
/**
* OnChainZKPVerifier is a class that allows to interact with the OnChainZKPVerifier contract
* and submitZKPResponse.
*
* @beta
* @class OnChainZKPVerifier
*/
class OnChainZKPVerifier {
/**
*
* Creates an instance of OnChainZKPVerifier.
* @beta
* @param {EthConnectionConfig[]} - array of ETH configs
*/
constructor(_configs, _opts) {
this._configs = _configs;
this._opts = _opts;
}
static async prepareTxArgsSubmitV1(txData, zkProofResponse) {
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitZKPResponseV1) {
throw new Error(`prepareTxArgsSubmitV1 function doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitZKPResponseV1}' is supported.`);
}
const requestID = zkProofResponse.id;
const inputs = zkProofResponse.pub_signals;
const preparedZkpProof = (0, common_1.prepareZkpProof)(zkProofResponse.proof);
const payload = [requestID, inputs, preparedZkpProof.a, preparedZkpProof.b, preparedZkpProof.c];
return payload;
}
/**
* {@inheritDoc IOnChainZKPVerifier.prepareTxArgsSubmitV1}
*/
async prepareTxArgsSubmitV1(txData, zkProofResponse) {
return OnChainZKPVerifier.prepareTxArgsSubmitV1(txData, zkProofResponse);
}
/**
* {@inheritDoc IOnChainZKPVerifier.submitZKPResponse}
*/
async submitZKPResponse(ethSigner, txData, zkProofResponses) {
const chainConfig = this._configs.find((i) => i.chainId == txData.chain_id);
if (!chainConfig) {
throw new Error(`config for chain id ${txData.chain_id} was not found`);
}
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitZKPResponseV1) {
throw new Error(`submitZKPResponse function doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitZKPResponseV1}' is supported.`);
}
const provider = new ethers_1.JsonRpcProvider(chainConfig.url, chainConfig.chainId);
ethSigner = ethSigner.connect(provider);
const response = new Map();
const feeData = await provider.getFeeData();
const maxFeePerGas = chainConfig.maxFeePerGas
? BigInt(chainConfig.maxFeePerGas)
: feeData.maxFeePerGas;
const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas
? BigInt(chainConfig.maxPriorityFeePerGas)
: feeData.maxPriorityFeePerGas;
const verifierContract = new ethers_1.Contract(txData.contract_address, ZkpVerifier_json_1.default);
for (const zkProofResponse of zkProofResponses) {
const txArgs = await this.prepareTxArgsSubmitV1(txData, zkProofResponse);
const payload = await verifierContract.submitZKPResponse.populateTransaction(...txArgs);
const request = {
to: txData.contract_address,
data: payload.data,
maxFeePerGas,
maxPriorityFeePerGas
};
let gasLimit;
try {
gasLimit = await ethSigner.estimateGas(request);
}
catch (e) {
gasLimit = maxGasLimit;
}
request.gasLimit = gasLimit;
const transactionService = new blockchain_1.TransactionService(provider);
const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request);
response.set(txnHash, zkProofResponse);
}
return response;
}
/**
* {@inheritDoc IOnChainZKPVerifier.submitZKPResponseV2}
*/
async submitZKPResponseV2(ethSigner, txData, zkProofResponses) {
const chainConfig = this._configs.find((i) => i.chainId == txData.chain_id);
if (!chainConfig) {
throw new Error(`config for chain id ${txData.chain_id} was not found`);
}
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitZKPResponseV2) {
throw new Error(`submitZKPResponseV2 function 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 provider = new ethers_1.JsonRpcProvider(chainConfig.url, chainConfig.chainId);
ethSigner = ethSigner.connect(provider);
const txDataArgs = await this.prepareTxArgsSubmitV2(txData, zkProofResponses);
const feeData = await provider.getFeeData();
const maxFeePerGas = chainConfig.maxFeePerGas
? BigInt(chainConfig.maxFeePerGas)
: feeData.maxFeePerGas;
const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas
? BigInt(chainConfig.maxPriorityFeePerGas)
: feeData.maxPriorityFeePerGas;
const verifierContract = new ethers_1.Contract(txData.contract_address, ZkpVerifier_json_1.default);
const txRequestData = await verifierContract.submitZKPResponseV2.populateTransaction(...txDataArgs);
const request = {
to: txData.contract_address,
data: txRequestData.data,
maxFeePerGas,
maxPriorityFeePerGas
};
let gasLimit;
try {
gasLimit = await ethSigner.estimateGas(request);
}
catch (e) {
gasLimit = maxGasLimit;
}
request.gasLimit = gasLimit;
const transactionService = new blockchain_1.TransactionService(provider);
const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request);
return new Map().set(txnHash, zkProofResponses);
}
/**
* {@inheritDoc IOnChainVerifierMultiQuery.submitResponse}
*/
async submitResponse(ethSigner, txData, responses, authProof) {
const chainConfig = this._configs.find((i) => i.chainId == txData.chain_id);
if (!chainConfig) {
throw new Error(`config for chain id ${txData.chain_id} was not found`);
}
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitResponse) {
throw new Error(`submitResponse function doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitResponse}' is supported.`);
}
if (!this._opts?.didResolverUrl) {
throw new Error(`did resolver url required for crosschain verification`);
}
const provider = new ethers_1.JsonRpcProvider(chainConfig.url, chainConfig.chainId);
ethSigner = ethSigner.connect(provider);
const txPreparationResult = await this.prepareTxArgsSubmit(txData, responses, authProof);
const feeData = await provider.getFeeData();
const maxFeePerGas = chainConfig.maxFeePerGas
? BigInt(chainConfig.maxFeePerGas)
: feeData.maxFeePerGas;
const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas
? BigInt(chainConfig.maxPriorityFeePerGas)
: feeData.maxPriorityFeePerGas;
const verifierContract = new ethers_1.Contract(txData.contract_address, universal_verifier_v2_abi_1.IVerifierABI);
const txRequestData = await verifierContract.submitResponse.populateTransaction(...txPreparationResult.txDataArgs);
const request = {
to: txData.contract_address,
data: txRequestData.data,
maxFeePerGas,
maxPriorityFeePerGas
};
let gasLimit;
try {
gasLimit = await ethSigner.estimateGas(request);
}
catch (e) {
gasLimit = maxGasLimit;
}
request.gasLimit = gasLimit;
const transactionService = new blockchain_1.TransactionService(provider);
const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request);
// return multiple responses for all the responses (single and grouped)
return new Map().set(txnHash, {
authProof: txPreparationResult.result.authProof.raw,
crossChainProof: txPreparationResult.result.crossChainProof.raw,
responses: txPreparationResult.result.proofs.map((m) => m.proof)
});
}
static async prepareTxArgsSubmit(resolverUrl, txData, responses, authProof) {
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitResponse) {
throw new Error(`submit cross chain doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitResponse}' is supported.`);
}
const gistUpdatesArr = [];
const stateUpdatesArr = [];
const payloadResponses = [];
const emptyBytes = '0x';
let encodedAuthProof = '';
switch (authProof.authMethod) {
case iden3comm_1.AuthMethod.AUTHV2: {
const preparedZkpProof = (0, common_1.prepareZkpProof)(authProof.zkp.proof);
encodedAuthProof = (0, common_1.packZkpProof)(authProof.zkp.pub_signals, preparedZkpProof.a, preparedZkpProof.b, preparedZkpProof.c);
break;
}
case iden3comm_1.AuthMethod.ETH_IDENTITY: {
encodedAuthProof = (0, exports.packEthIdentityProof)(authProof.userDid);
break;
}
default:
throw new Error('auth proof must use method AuthV2 or ethIdentity');
}
// Process all the responses
for (const zkProof of responses) {
this.checkSupportedCircuit(zkProof.circuitId);
const { requestId, zkProofEncoded, metadata } = (0, iden3comm_1.processProofResponse)(zkProof);
payloadResponses.push({
proof: zkProof,
requestId: requestId,
encoded: zkProofEncoded,
metadata: metadata
});
}
// Process all zkProofs and prepare cross chain proofs
const allZkProofs = responses.map((zkProof) => ({
circuitId: zkProof.circuitId,
pub_signals: zkProof.pub_signals
}));
if (authProof.authMethod == iden3comm_1.AuthMethod.AUTHV2) {
allZkProofs.push({
circuitId: authProof.zkp.circuitId,
pub_signals: authProof.zkp.pub_signals
});
}
for (const zkProof of allZkProofs) {
const { gistUpdateResolutions, stateUpdateResolutions } = this.getUpdateResolutions(resolverUrl, txData.chain_id, zkProof.circuitId, zkProof.pub_signals);
if (gistUpdateResolutions.length > 0) {
gistUpdatesArr.push(...(await Promise.all(gistUpdateResolutions)));
}
if (stateUpdateResolutions.length > 0) {
stateUpdatesArr.push(...(await Promise.all(stateUpdateResolutions)));
}
}
const encodedCrossChainProof = gistUpdatesArr.length || stateUpdatesArr.length
? this.packCrossChainProofs(gistUpdatesArr, stateUpdatesArr)
: emptyBytes;
const preparationResult = {
authProof: { raw: authProof, encoded: encodedAuthProof },
proofs: payloadResponses,
crossChainProof: {
raw: {
globalStateProofs: gistUpdatesArr || [],
identityStateProofs: stateUpdatesArr || []
},
encoded: encodedCrossChainProof
}
};
return { result: preparationResult, txDataArgs: (0, exports.toTxDataArgs)(preparationResult) };
}
async prepareTxArgsSubmit(txData, responses, authProof) {
if (!this._opts?.didResolverUrl) {
throw new Error(`did resolver url required for crosschain verification`);
}
return OnChainZKPVerifier.prepareTxArgsSubmit(this._opts.didResolverUrl, txData, responses, authProof);
}
static checkSupportedCircuit(circuitId) {
if (!this._supportedCircuits.includes(circuitId)) {
throw new Error(`Circuit ${circuitId} not supported by OnChainZKPVerifier`);
}
}
static getCrossChainResolvers(source, txDataChainId, type, didResolverUrl) {
return [
...new Set(source.map((info) => JSON.stringify({
id: info.id.string(),
[type]: type === 'gist' ? info.root?.string() : info.state?.string()
})))
].reduce((acc, s) => {
const info = JSON.parse(s);
const id = js_iden3_core_1.Id.fromString(info.id);
const chainId = (0, js_iden3_core_1.chainIDfromDID)(js_iden3_core_1.DID.parseFromId(id));
if (txDataChainId === chainId) {
return acc;
}
const promise = this.resolveDidDocumentEip712MessageAndSignature(js_iden3_core_1.DID.parseFromId(js_iden3_core_1.Id.fromString(info.id)), didResolverUrl, {
[type]: js_merkletree_1.Hash.fromString(info[type])
});
return [...acc, promise];
}, []);
}
static async prepareTxArgsSubmitV2(resolverUrl, txData, zkProofResponses) {
if (txData.method_id.replace('0x', '') !== FunctionSignatures.SubmitZKPResponseV2) {
throw new Error(`submit cross chain doesn't implement requested method id. Only '0x${FunctionSignatures.SubmitZKPResponseV2}' is supported.`);
}
const gistUpdatesArr = [];
const stateUpdatesArr = [];
const payloadResponses = [];
const emptyBytes = '0x';
for (const zkProof of zkProofResponses) {
this.checkSupportedCircuit(zkProof.circuitId);
const { requestId, zkProofEncoded, metadata } = (0, iden3comm_1.processProofResponse)(zkProof);
payloadResponses.push({
requestId: requestId,
zkProof: zkProofEncoded,
data: metadata
});
const { gistUpdateResolutions, stateUpdateResolutions } = this.getUpdateResolutions(resolverUrl, txData.chain_id, zkProof.circuitId, zkProof.pub_signals);
if (gistUpdateResolutions.length > 0) {
gistUpdatesArr.push(...(await Promise.all(gistUpdateResolutions)));
}
if (stateUpdateResolutions.length > 0) {
stateUpdatesArr.push(...(await Promise.all(stateUpdateResolutions)));
}
}
const crossChainProofEncoded = gistUpdatesArr.length || stateUpdatesArr.length
? this.packCrossChainProofs(gistUpdatesArr, stateUpdatesArr)
: emptyBytes;
return [payloadResponses, crossChainProofEncoded];
}
async prepareTxArgsSubmitV2(txData, zkProofResponses) {
if (!this._opts?.didResolverUrl) {
throw new Error(`did resolver url required for crosschain verification`);
}
return OnChainZKPVerifier.prepareTxArgsSubmitV2(this._opts.didResolverUrl, txData, zkProofResponses);
}
static getUpdateResolutions(resolverUrl, chainId, proofCircuitId, inputs) {
const stateInfo = this.getOnChainGistRootStatePubSignals(proofCircuitId, inputs);
const gistUpdateResolutions = this.getCrossChainResolvers(stateInfo.gists, chainId, 'gist', resolverUrl);
const stateUpdateResolutions = this.getCrossChainResolvers(stateInfo.states, chainId, 'state', resolverUrl);
return { gistUpdateResolutions, stateUpdateResolutions };
}
static packCrossChainProofs(gistUpdateArr, stateUpdateArr) {
const proofs = [];
for (const globalStateUpdate of gistUpdateArr) {
proofs.push({
proofType: 'globalStateProof',
proof: this.packGlobalStateMsg(globalStateUpdate)
});
}
for (const stateUpdate of stateUpdateArr) {
proofs.push({
proofType: 'stateProof',
proof: this.packIdentityStateMsg(stateUpdate)
});
}
return new ethers_1.ethers.AbiCoder().encode(['tuple(' + 'string proofType,' + 'bytes proof' + ')[]'], [proofs]);
}
static packGlobalStateMsg(msg) {
return new ethers_1.ethers.AbiCoder().encode([
'tuple(' +
'tuple(' +
'uint256 timestamp,' +
'bytes2 idType,' +
'uint256 root,' +
'uint256 replacedAtTimestamp' +
') globalStateMsg,' +
'bytes signature,' +
')'
], [msg]);
}
static packIdentityStateMsg(msg) {
return new ethers_1.ethers.AbiCoder().encode([
'tuple(' +
'tuple(' +
'uint256 timestamp,' +
'uint256 id,' +
'uint256 state,' +
'uint256 replacedAtTimestamp' +
') idStateMsg,' +
'bytes signature,' +
')'
], [msg]);
}
static getOnChainGistRootStatePubSignals(onChainCircuitId, inputs) {
const PubSignals = this._supportedCircuitsPubSignalsMap[onChainCircuitId];
if (!PubSignals) {
throw new Error(`Circuit ${onChainCircuitId} not supported by OnChainZKPVerifier`);
}
const atomicQueryPubSignals = new PubSignals();
const encodedInputs = utils_1.byteEncoder.encode(JSON.stringify(inputs));
atomicQueryPubSignals.pubSignalsUnmarshal(encodedInputs);
return atomicQueryPubSignals.getStatesInfo();
}
static async resolveDidDocumentEip712MessageAndSignature(did, resolverUrl, opts) {
const didDoc = await (0, utils_1.resolveDidDocument)(did, resolverUrl, {
...opts,
signature: utils_1.DIDDocumentSignature.EthereumEip712Signature2021
});
if (!didDoc.didResolutionMetadata.proof?.length) {
throw new Error('No proof found in resolved DID document');
}
const message = didDoc.didResolutionMetadata.proof[0].eip712.message;
const signature = didDoc.didResolutionMetadata.proof[0].proofValue;
const isGistRequest = opts?.gist && !opts.state;
if (isGistRequest) {
return {
globalStateMsg: {
timestamp: message.timestamp,
idType: message.idType,
root: message.root,
replacedAtTimestamp: message.replacedAtTimestamp
},
signature
};
}
return {
idStateMsg: {
timestamp: message.timestamp,
id: message.id,
state: message.state,
replacedAtTimestamp: message.replacedAtTimestamp
},
signature
};
}
}
exports.OnChainZKPVerifier = OnChainZKPVerifier;
/**
* supported circuits
*/
OnChainZKPVerifier._supportedCircuits = [
circuits_1.CircuitId.AuthV2,
circuits_1.CircuitId.AtomicQueryMTPV2OnChain,
circuits_1.CircuitId.AtomicQuerySigV2OnChain,
circuits_1.CircuitId.AtomicQueryV3OnChain
];
OnChainZKPVerifier._supportedCircuitsPubSignalsMap = {
[circuits_1.CircuitId.AtomicQueryMTPV2OnChain]: circuits_1.AtomicQueryMTPV2OnChainPubSignals,
[circuits_1.CircuitId.AtomicQuerySigV2OnChain]: circuits_1.AtomicQuerySigV2OnChainPubSignals,
[circuits_1.CircuitId.AtomicQueryV3OnChain]: circuits_1.AtomicQueryV3OnChainPubSignals,
[circuits_1.CircuitId.AuthV2]: circuits_1.AuthV2PubSignals
};
/**
* Packs an Ethereum identity proof from a Decentralized Identifier (DID).
* @param did - Decentralized Identifier (DID) to pack.
* @returns A hexadecimal string representing the packed DID identity proof.
*/
const packEthIdentityProof = (did) => {
return `0x${(0, utils_1.bytesToHex)(js_iden3_core_1.BytesHelper.intToBytes(js_iden3_core_1.DID.idFromDID(did).bigInt()))}`;
};
exports.packEthIdentityProof = packEthIdentityProof;