Skip to main content
Version: next

🚀 Getting Started with TypeScript/JavaScript

We provide the following npm packages for developers to build with TypeScript or JavaScript:

PackageVersionDescription
@unirep/coreNPM versionUnirep protocol-related functions
@unirep/contractsNPM versionUnirep smart contracts, ZKP verifiers and contract-related functions
@unirep/circuitsNPM versionUnirep Circom circuits and circuit-related functions
@unirep/utilsNPM versionUtilities used in Unirep protocol

💻 Installation

You can simply install @unirep/core to use all of these dependencies.

yarn add @unirep/core

🛠️ Deploy or connect to a Unirep smart contract

Deploy a Unirep.sol

If there is no Unirep.sol deployed in a testnet or a local blockchain environment (e.g. Hardhat node), you can run the following scripts:

import { ethers } from 'ethers'
import { deployUnirep } from '@unirep/contracts/deploy'

// connect to a wallet
const privateKey = 'YOUR/PRIVATE/KEY'
const provider = 'YOUR/ETH/PROVIDER'
const deployer = new ethers.Wallet(privateKey, provider)

// deploy unirep contract
const unirepContract = await deployUnirep(deployer)

Connect to a deployed Unirep.sol

If a Unirep.sol is deployed, you can connect the smart contract with the function:

import { getUnirepContract, Unirep } from '@unirep/contracts'

const address = '0x...'
const provider = 'YOUR/ETH/PROVIDER'

const unirepContract: Unirep = getUnirepContract(address, provider)
info

See Testnet Deployment to see the currently deployed Unirep.sol contract.

The following actions are initiated by either an attester or a user.
Read the section users and attesters to learn more.

🤖 Attester sign up

The app builder must sign up with Unirep.sol as an attester. There are two ways to sign up:

  1. Attester signs up with a wallet
  2. Attester signs up with a smart contract

1. Attester sign up with a wallet 👛

Connect a wallet with a private key and a provider, and then call attesterSignUp in Unirep.sol.

// deploy or connect to a unirep smart contract
let unirepContract = getUnirepContract(address, provider)
// attester wallet
const attester = new ethers.Wallet(privateKey, provider)
// connect unirep contract with attester wallet
unirepContract = await unirepContract.connect(attester)
// define epoch length
const epochLength = 300 // 300 seconds
// send transaction
const tx = await unirepContract.attesterSignUp(epochLength)
await tx.wait()
info

See Define epoch length for more information.

The msg.sender will be recorded as an attester ID in Unirep.sol. The attester should connect to this wallet to help users sign up and send attestations to users.

2. Attester sign up with a smart contract 📄

The app builder can also use a smart contract as an attester. For example:

App.sol
pragma solidity ^0.8.0;
import {Unirep} from '@unirep/contracts/Unirep.sol';

contract App {
Unirep public unirep;
constructor(
Unirep _unirep,
uint48 _epochLength
) {
// set UniRep address
unirep = _unirep;
// sign up as an attester
unirep.attesterSignUp(_epochLength);
}
}

Unirep.sol will record the msg.sender, which is the address of App.sol, as the attester ID. Now the attester can define the user signup and attestation logic in App.sol.

👤 User sign up

The attester can now start signing up users. Users of this application should provide a signup proof which includes:

  1. Proving the user owns a Semaphore identity
  2. Proving the user has initialized data
  3. Proving the user wants to sign up to this attester (proving attester ID)

User generates sign up proof

The user will generate the signup proof on the client side:

import { UserState } from '@unirep/core'
import { defaultProver } from '@unirep/circuits/provers/defaultProver'
import { Identity } from "@semaphore-protocol/identity"

// Semaphore Identity
const id = new Identity()
// generate user state
const userState = new UserState({
prover: defaultProver, // a circuit prover
unirepAddress: unirepContract.address,
provider, // an ethers.js provider
id,
})

// start and sync
await userState.start()
await userState.waitForSync()

// generate signup proof
const { proof, publicSignals } = await userState.genUserSignUpProof()
info

See UserState for more information.

Attester submits sign up proof

The attester will submit this proof by calling the userSignUp function on Unirep.sol.

userSignUp.ts/userSignUp.js
// attester sends the tx
const tx = await unirepContract
.connect(attester)
.userSignUp(publicSignals, proof)
await tx.wait()
tip

A user can check if they have signed up successfully with userState:

// let userState synchronize through the latest block
await userState.waitForSync()
console.log(await userState.hasSignedUp()) // true

See waitForSync for more information.

📮 Attestation

Users must provide epoch keys to receive data from attesters, similar to how Ethereum users provide address to receive ETH. An attestation is the data an attester gives to a specified epoch key.

User generates epoch keys

epochKey.ts
import { genEpochKey } from '@unirep/utils'
// get epoch from contract
const epoch = await unirepContract.attesterCurrentEpoch(attester.address)
// define nonce
const nonce = 0 // it could be 0 to (NUM_EPOCH_KEY_NONCE - 1) per user
// generate an epoch key
const epochKey = genEpochKey(
identity.secret,
BigInt(attester.address),
epoch,
nonce
)
info

See genEpochKey for more information.

Attester submits the transaction

The attester will submit attestations by calling the attest function on Unirep.sol.
To add to a user's data:

data[0] += 5

the attester will define:

const fieldIndex = 0
const change = 5
caution

There are addition data fields and replacement data fields. Please make sure the index of the data is correct.

For example, if SUM_FIELD_COUNT = 4 then the data[4] will be replaced by the change but not added together.

See Data for more information.

attest.ts/attest.js
// attester sends the tx
// the data field that the attester chooses to change
const fieldIndex = 0
// the amount of the change
const change = 5
const tx = await unirepContract
.connect(attester)
.attest(
epochKey,
epoch,
fieldIndex,
change
)
await tx.wait()
caution

To verify an epoch key on-chain, see Verify epoch key on-chain.

⏱️ User state transition

After an epoch ends, the user will perform user state transition to finalize the state in the previous epoch, and use a new state to receive more data in a new epoch.

The user state transition proof must be built by the user because only the user holds the Semaphore identity secret key.

User generates user state transition proof

// call to make sure the state is updated
await userState.waitForSync()
// generate the user state transition proof
const { proof, publicSignals } = await userState.genUserStateTransitionProof()
info

See genUserStateTransitionProof for more information.

A transition proof can be relayed

The user state transition proof should be submitted to Unirep.sol to update the user state on-chain but it does not have to be the attester that sends the transaction.

transition.ts/transition.js
// sends the tx
// it doesn't need to be the attester
const tx = await unirepContract
.connect(relayer)
.userStateTransition(
publicSignals,
proof
)
await tx.wait()
info

See userStateTransition for more information.

🔐 Prove data

After a user state transition, a user can prove the data they received in previous epochs.

User generates data proof

proveData.ts/proveData.js
// call to make sure the state is updated
await userState.waitForSync()
// the data that the user wants to prove
// If the user has 5, they can choose to prove they have more than 3
const repProof = await userState.genProveReputationProof({
minRep: 3
})
// check if proof is valid
console.log(await repProof.verify()) // true

// we will use { publicSignals, proof} later
const { publicSignals, proof } = repProof

In this example, we define data[0] as positive reputation and data[1] as negative reputation.
Proving minRep = 3 is a claim that (data[0] - data[1]) >= 3.

In the above attestation, the user's data[0] increased by 5 and data[1] was not changed.

Therefore in this case data[0] - data[1] = 5.

Use getProvableData to know the data that a user can prove.

const data = await userState.getProvableData()
info

See reputationProof for more information.

Other users and attesters verify the proof

info

See ReputationVerifierHelper to learn how to deploy and use the repVerifier.

transition.ts/transition.js
// sends the tx
// it doesn't need to be the attester
const tx = await unirepContract
.connect(relayer)
.verifyReputationProof(
publicSignals,
proof
)
await tx.wait()

Now, start building your own application with UniRep. 🚀