Skip to content

Commit

Permalink
Merge pull request #184 from 0xPolygonID/universal_links
Browse files Browse the repository at this point in the history
Universal links | Verifier changes
  • Loading branch information
0xpulkit authored Sep 18, 2024
2 parents 57f6cca + e231895 commit 2ecddc5
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 199 deletions.
6 changes: 3 additions & 3 deletions docs/verifier/verification-library/verifier-library-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import useBaseUrl from '@docusaurus/useBaseUrl';

At its core, every off-chain interaction between a Verifier and a user's Wallet follows this workflow:

- A web application designs a [request](./request-api-guide.md) for the users. This is delivered to the user within a QR code (or via deep-linking; it is up to the implementer). This can either be a [auth request](./request-api-guide.md#basic-auth-request) or a [query-based request](./request-api-guide.md#query-based-request).
- The user scans the QR code using his/her mobile ID wallet and parses the request.
- A web application designs a [request](./request-api-guide.md) for the users. This is delivered to the user via a [Universal link](../../wallet/universal-links.md), a QR code or via deep-linking (it is up to the implementer). This can either be a [auth request](./request-api-guide.md#basic-auth-request) or a [query-based request](./request-api-guide.md#query-based-request).
- The user clicks the universal link which directs them to the Web Wallet or the mobile wallet or the scans the QR code using the mobile wallet to parse the request.
- The user fetches the revocation status of the requested credential from the Issuer of that credential.
- The user generates a ZK proof on mobile according to the request of the website starting from the credentials held in his/her wallet. This also contains the ZK proof that the credential is not revoked.
- The user sends the ZK proof to the Verifier.
Expand All @@ -33,7 +33,7 @@ At its core, every off-chain interaction between a Verifier and a user's Wallet

Assume that the request is: "Are you over 18 years old?". The Verifier _never gets access to any of the user's credentials_. Instead, the Verifier receives a cryptographic proof which, on verification, provides an answer "yes" or "no" to the previous question.

This section provides all the elements needed to integrate off-chain verification with Polygon ID.
This section provides all the elements needed to integrate off-chain verification with Privado ID.

## Libraries

Expand Down
240 changes: 56 additions & 184 deletions docs/verifier/verification-library/verifier-set-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import useBaseUrl from '@docusaurus/useBaseUrl';

Any application that wants to authenticate users based on their Polygon ID Identity off-chain must set up a Verifier. A Verifier is made of a Server and a Client.
Any application that wants to authenticate users based on their Privado ID Identity off-chain must set up a Verifier. A Verifier is made of a Server and a Client.

The Server generates [the ZK Request](./request-api-guide.md) according to the requirements of the platform. There are two types of authentication:

Expand All @@ -25,11 +25,11 @@ The Server generates [the ZK Request](./request-api-guide.md) according to the r

The second role of the Server is to execute [Verification](./verification-api-guide.md) of the proof sent by the Identity Wallet.

The Verifier Client is the point of interaction with the user. In its simplest form, a client needs to embed a QR code that displays the ZK request generated by the Server. The verification request can also be delivered to users via Deep Linking. After scanning the ZK request, the user will generate a proof based on that request locally on their wallet. This proof is therefore sent back to the Verifier Server that verifies whether the proof is valid.
The Verifier Client is the point of interaction with the user. In its simplest form, a client needs to embed a [Universal Link](../../wallet/universal-links.md) or a QR code that contains the ZK request generated by the Server so that it reaches the user's wallet. The verification request can also be delivered to users via Deep Linking. Once the ZK request reaches the user's wallet, the user will generate a proof based on that request locally on their wallet. This proof is therefore sent back to the Verifier Server that verifies whether the proof is valid.

This tutorial is based on the verification of a Credential of Type `KYCAgeCredential` with an attribute `birthday` based on the following Schema URL: `https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld`.

The prerequisite is that users have the [Polygon ID Wallet app](/docs/wallet/wallet-overview.md) installed and self-issued a Credential of type `KYC Age Credential Merklized` using our [Demo Issuer](https://issuer-demo.polygonid.me/). Further credentials can be issued using the [Issuer Node](/docs/issuer/issuer-overview.md).
The prerequisite is that users have self-issued a Credential of type `KYC Age Credential Merklized` using our [Demo Issuer](https://issuer-demo.polygonid.me/) on their Web Wallet or Privado Wallet App. Further credentials can be issued using the [Issuer Node](/docs/issuer/issuer-overview.md).

In this example, the verifier will set up the query: "Prove that you were born before `2000/01/01`. To set up a different query check out the [ZK Query Language section](./zk-query-language.md).

Expand Down Expand Up @@ -269,12 +269,12 @@ The highlighted lines are to be added only if the authentication needs to design
The request generated in the previous endpoint already contains the `CallBackURL` so that the response generated by the wallet will be automatically forwarded to the server callback function. The callback post endpoint receives the proof generated by the identity wallet. The role of the callback endpoint is to execute the [Verification](verification-api-guide.md) on the proof.
:::info "Testnet / Mainnet"
The code samples on this page are using Polygon's Amoy testnet, including the smart contract address and the RPC endpoint in the `ethURL` variable. If you want to use the Mainnet, you need to add a resolver for it.
The code samples on this page utilize Polygon's Amoy testnet, which is associated with the verifier, and the Privado chain, which is associated with the users' identities. This includes the smart contract addresses and RPC endpoints specified in the `resolvers` object. If you wish to use a different network, you will need to add a resolver for it.
You can find the addresses for the validator smart contracts <ins><a href="../../smart-contracts/#validator-addresses" target="">here</a></ins>. Below is an example for adding a resolver for polygon mainnet:
Mainnet contract address: `0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D`
DID prefix: `polygon:main`
Mainnet contract address: 0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D
DID prefix: polygon:main
```go
const RPC_URL = '<RPC_URL>';
const mainContractAddress = "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D"
Expand All @@ -285,8 +285,6 @@ const mainStateResolver = new resolver.EthStateResolver(
);

const resolvers = {
['polygon:mumbai']: ethStateResolver,
['polygon:amoy']: amoyStateResolver,
['polygon:main']: mainStateResolver,
};
```
Expand Down Expand Up @@ -317,17 +315,11 @@ func Callback(w http.ResponseWriter, r *http.Request) {
log.Println(err)
return
}
// Add Polygon AMOY RPC node endpoint - needed to read on-chain state
ethURL := "https://polygon-amoy.infura.io/v3/<API-KEY>"


// Add IPFS url - needed to load schemas from IPFS
ipfsURL := "https://ipfs.io"

// Add identity state contract address
contractAddress := "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124"

resolverPrefix := "polygon:amoy"

// Locate the directory that contains circuit's verification keys
keyDIR := "../keys"

Expand All @@ -339,9 +331,20 @@ func Callback(w http.ResponseWriter, r *http.Request) {

// load the verifcation key
var verificationKeyLoader = &KeyLoader{Dir: keyDIR}

resolver := state.ETHResolver{
RPCUrl: ethURL,
ContractAddress: common.HexToAddress(contractAddress),
"polygon:amoy": {
RPCUrl: "<AMOY_RPC_URL>",
ContractAddress: common.HexToAddress("0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124"),
},
"privado:main": {
RPCUrl: "https://rpc-mainnet.privado.id",
ContractAddress: common.HexToAddress("0x975556428F077dB5877Ea2474D783D6C69233742"),
},
"privado:test": {
RPCUrl: "https://rpc-testnet.privado.id/",
ContractAddress: common.HexToAddress("0x975556428F077dB5877Ea2474D783D6C69233742"),
},
}

resolvers := map[string]pubsignals.StateResolver{
Expand Down Expand Up @@ -394,15 +397,23 @@ async function callback(req, res) {
const tokenStr = raw.toString().trim();
console.log(tokenStr);

const ethURL = "<AMOY_RPC_URL>";
const contractAddress = "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124";
const keyDIR = "../keys";

const ethStateResolver = new resolver.EthStateResolver(ethURL, contractAddress);


const resolvers = {
["polygon:amoy"]: ethStateResolver,
};
["polygon:amoy"]: new resolver.EthStateResolver(
"<AMOY_RPC_URL>",
"0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124"
),
["privado:main"]: new resolver.EthStateResolver(
"https://rpc-mainnet.privado.id",
"0x975556428F077dB5877Ea2474D783D6C69233742"
),
["privado:test"]: new resolver.EthStateResolver(
"https://rpc-testnet.privado.id/",
"0x975556428F077dB5877Ea2474D783D6C69233742"
)

};

// fetch authRequest from sessionID
const authRequest = requestMap.get(`${sessionId}`);
Expand Down Expand Up @@ -433,9 +444,14 @@ async function callback(req, res) {
## Verifier Client Setup
The Verifier Client must fetch the Auth Request generated by the Server (`/api/sign-in` endpoint) and deliver it to the user via a QR Code.
The Verifier Client must fetch the Auth Request generated by the Server (`/api/sign-in` endpoint) and deliver it to the user via the [Universal Link](../../wallet/universal-links.md) or QR Code.
> To display the QR code inside your frontend, you can use this [Code Sandbox](https://codesandbox.io/s/yp1pmpjo4z?file=/index.js).
> Please Refer [this](../../wallet/universal-links.md/#configuration) to know how to configure Universal Links.
:::note
Universal Links can be used to support both the Web Wallet and the mobile wallet app, while QR codes or deep links are limited to use with mobile devices.
:::
QR Code setup **(only supported on mobile)**:
A Verifier can show a QR code that contains one of the following data structures:
Expand All @@ -447,169 +463,26 @@ A Verifier can show a QR code that contains one of the following data structures
If both params are present `i_m` is prioritized and `request_uri` is ignored.

The same request can also be delivered to users via Deep Linking. The same format for links must be used.
> To display the QR code inside your frontend, you can use this [Code Sandbox](https://codesandbox.io/s/yp1pmpjo4z?file=/index.js).


**_Shortened URL algorithm_**

While it's not strictly restricted how you can perform URL shortage algorithm, it is recommended to follow these instructions:
1. Generate a UUID for a particular request (or use ID of the message itself)
2. Implement an endpoint to fetch messages by UUID.
3. Encode URL to fetch messages to `the request_uri`.
Example of URL shortage logic:
<Tabs>
<TabItem value= "Golang">
```go
package handlers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/gofrs/uuid"
"github.com/patrickmn/go-cache"
)
var cacheStorage = cache.New(60*time.Minute, 60*time.Minute)
func HandleQRData(w http.ResponseWriter, r *http.Request) {
switch r.Method {
// create url for the message
case http.MethodPost:
// get json data from request body
var data interface{}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
defer r.Body.Close()
err = json.Unmarshal(body, &data)
if err != nil {
http.Error(w, "Failed to unmarshal body data", http.StatusInternalServerError)
return
}
// generate random key
uv, err := uuid.NewV4()
if err != nil {
http.Error(w, "Failed to generate uuid", http.StatusInternalServerError)
return
}
// store data in map
cacheStorage.Set(uv.String(), data, 1*time.Hour)
hostURL := os.Getenv("HOST_URL") // e.g. https://verifier.com
// write key to response
fmt.Fprintf(w, "%s%s?id=%s", hostURL, "api/qr-store", uv.String())
return
// get message by identifier
case http.MethodGet:
// get path param
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "Failed to get id", http.StatusNotFound)
return
}
// get data from map
data, ok := cacheStorage.Get(id)
if !ok {
http.Error(w, fmt.Sprintf("Failed to retrieve QR data by %s", id), http.StatusNotFound)
return
}
jsonData, err := json.Marshal(data)
if err != nil {
http.Error(w, "Failed to encode JSON", http.StatusInternalServerError)
return
}
// write data to response
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
w.Write(jsonData)
return
}
}
```
</TabItem>
<TabItem value= "Javascript">
```js
const express = require("express");
const { v4: uuidv4 } = require("uuid");
const Cache = require("cache-manager");
const HttpStatus = require("http-status-codes");
const app = express();
app.use(express.json());
const cPromise = Cache.caching("memory", {
max: 100,
ttl: 10 * 1000 /*milliseconds*/,
});
app.get("/api/qr-store", async (req, res) => {
const id = req.query.id;
const cacheManager = await cPromise;
const data = await cacheManager.get(id);
if (!data) {
return res.status(HttpStatus.NOT_FOUND).json({ error: `item not found ${id}` });
}
return res.status(HttpStatus.OK).json(data);
});
app.post("/api/qr-store", async (req, res) => {
const body = req.body;
const uuid = uuidv4();
const cacheManager = await cPromise;
console.log(cacheManager);
await cacheManager.set(uuid, body, { ttl: 3600 });
const hostUrl = process.env.HOST_URL;
const qrUrl = `${hostUrl}/api/qr-store?id=${uuid}`;
return res.status(HttpStatus.OK).json({ qrUrl });
});

app.listen(3000, () => {
console.log("Express server is running on port 3000");
});
```
**Implement Further Logic**

</TabItem>
</Tabs>
This tutorial showcased a minimalistic application that leverages Privado ID libraries for authentication purposes. Developers can leverage the broad set of existing Credentials held by users to set up any customized Query using our [ZK Query Language](./zk-query-language.md) to unleash the full potential of the framework.

**Implement Further Logic**

This tutorial showcased a minimalistic application that leverages Polygon ID libraries for authentication purposes. Developers can leverage the broad set of existing Credentials held by users to set up any customized Query using our [ZK Query Language](./zk-query-language.md) to unleash the full potential of the framework.
**Step 1: Include the Static Folder**

For example, the concept can be extended to exchanges that require KYC Credentials, DAOs that require proof-of-personhood Credentials, or social media applications that intend to re-use users' aggregated reputation.
Add the <a href="https://github.com/0xPolygonID/tutorial-examples/tree/main/verifier-integration/static" target="_blank">Static Folder</a> to your Verifier repository.

To do so, add the <a href="https://github.com/0xPolygonID/tutorial-examples/tree/main/verifier-integration/js/static" target="_blank">Static Folder</a> to your Verifier repository. This folder contains an HTML static webpage that renders a static webpage with the QR code containing the Auth Request.
This folder contains:

> To display the QR code inside your frontend, you can use the `express.static` built-in middleware function together with this <a href="https://github.com/0xPolygonID/tutorial-examples/tree/main/verifier-integration/js/static" target="_blank">Static Folder</a> or this [Code Sandbox](https://codesandbox.io/s/yp1pmpjo4z?file=/index.js).
- index.html: The main HTML page that renders the QR code and button.
- styles.css: The CSS file for styling.
- script.js: The JavaScript file that fetches the API data and generates the button containing the Universal Link and a QR code.

1. **Add routing to your Express Server**
** Step 2: Serve Static Files Using Express **

To serve static files, we use the <a href="https://expressjs.com/en/starter/static-files.html" target="_blank">express.static built-in middleware function</a>.

Expand All @@ -621,7 +494,7 @@ const getRawBody = require("raw-body");
const app = express();
const port = 8080;

app.use(express.static("static"));
app.use(express.static("../static"));

app.get("/api/sign-in", (req, res) => {
console.log("get Auth Request");
Expand All @@ -641,10 +514,9 @@ app.listen(port, () => {
const requestMap = new Map();
```
2. **Visit http://localhost:8080/**
When visiting the URL, the users will need to scan the QR code with their ID wallets.
** Step 3: Visit Your Application **
Start your server and visit http://localhost:8080/. When visiting the URL, users can click the button containing the Universal Link or scan the QR code with their Privado ID wallet app and continue the verification process.
<div align="center">
<img src={useBaseUrl("img/verifier-static-2.png")} width="600"/>
</div>
Loading

0 comments on commit 2ecddc5

Please sign in to comment.