🚀 Getting Started with TypeScript/JavaScript
We provide the following npm packages for developers to build with TypeScript or JavaScript:
| Package | Version | Description |
|---|---|---|
@unirep/core | Unirep protocol-related functions | |
@unirep/contracts | Unirep smart contracts, ZKP verifiers and contract-related functions | |
@unirep/circuits | Unirep Circom circuits and circuit-related functions | |
@unirep/utils | Utilities used in Unirep protocol |
💻 Installation
You can simply install @unirep/core to use all of these dependencies.
- Yarn
- Npm
yarn add @unirep/core
npm i @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:
- Typescript
- Javascript
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)
const ethers = require('ethers')
const { deployUnirep } = require('@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:
- Typescript
- Javascript
import { getUnirepContract, Unirep } from '@unirep/contracts'
const address = '0x...'
const provider = 'YOUR/ETH/PROVIDER'
const unirepContract: Unirep = getUnirepContract(address, provider)
const { getUnirepContract } = require('@unirep/contracts')
const address = '0x...'
const provider = 'YOUR/ETH/PROVIDER'
const unirepContract = getUnirepContract(address, provider)
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 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()
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:
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:
- Proving the user owns a Semaphore identity
- Proving the user has initialized data
- 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:
- Typescript
- Javascript
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()
const { UserState } = require('@unirep/core')
const { defaultProver } = require('@unirep/circuits/provers/defaultProver')
const { Identity } = require("@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()
See UserState for more information.
Attester submits sign up proof
The attester will submit this proof by calling the userSignUp function on Unirep.sol.
- Typescript/Javascript
- Solidity
// attester sends the tx
const tx = await unirepContract
.connect(attester)
.userSignUp(publicSignals, proof)
await tx.wait()
function userSignUp(
uint256[] memory publicSignals,
uint256[8] memory proof
) public {
unirep.userSignUp(publicSignals, proof);
}
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
- Typescript
- Javascript
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
)
const { genEpochKey } = require('@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
)
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
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.
- Typescript/Javascript
- Solidity
// 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()
// attester sends the tx
function attest(
uint256 epochKey
) public {
// get epoch from contract
uint48 epoch = unirep.attesterCurrentEpoch(uint160(address(this)));
// the data field that the attester chooses to change
uint fieldIndex = 0;
// the amount of the change
uint change = 5 ;
unirep.attest(
epochKey,
epoch,
fieldIndex,
change
);
}
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()
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.
- Typescript/Javascript
- Solidity
// sends the tx
// it doesn't need to be the attester
const tx = await unirepContract
.connect(relayer)
.userStateTransition(
publicSignals,
proof
)
await tx.wait()
// sends the tx
// it doesn't need to be the attester
function transition(
uint[] memory publicSignals,
uint[8] memory proof
) public {
unirep.userStateTransition(
publicSignals,
proof
);
}
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
// 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()
See reputationProof for more information.
Other users and attesters verify the proof
See ReputationVerifierHelper to learn how to deploy and use the repVerifier.
- Typescript/Javascript
- Solidity
// sends the tx
// it doesn't need to be the attester
const tx = await unirepContract
.connect(relayer)
.verifyReputationProof(
publicSignals,
proof
)
await tx.wait()
// sends the tx
// it doesn't need to be the attester
function verifyProof(
uint[] memory publicSignals,
uint[8] memory proof
) public {
repVerifier.verifyAndCheckCaller(
publicSignals,
proof
);
}
Now, start building your own application with UniRep. 🚀