Only this pageAll pages
Powered by GitBook
1 of 35

Human ID

Introduction

Loading...

Loading...

Loading...

For Users

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

For Developers

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

For Node Operators

Loading...

Architecture

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

How it Works

Loading...

Loading...

Loading...

Loading...

The Issue of Regulatory Compliance & Non-Consensual Data Sharing

While Zero Knowledge Proofs are helpful, they aren't enough...

Zero-knowledge KYC prevents data honeypots and massive surveillance. However, on its own, it isn't sufficient for cases where regulators or law enforcement need to investigate users suspected of criminal activity.

As a result, maintaining honeypots has become a necessary compliance practice: organizations store sensitive data that is frequently breached, just to make sure it remains available for investigations.

Solving The Dilemma: Proof of Clean Hands With Human Network

While ZK proofs (ZKP) aren't trivial, ZKPs with compliance are even more complex. Holonym has built Human Network, an AVS on EigenLayer that allows provable threshold encryption, to unlock ZKPs with programmable privacy for selective and consensual disclosure. The network allows developers to hard-code policies that require pre-existing conditions to occur before a third party can decrypt user data. Users can verify and encrypt their data to the Network's key to create ZKPs which can be "decrypted" by pre-approved parties only when a smart contract is triggered.

While this function can be used to gate any sort of data behind programmable policies, it is especially useful for provable encryption of identity.

Human ID packages this functionality into a very easy to use solution called that serves as a general-purpose tool for meeting compliance requirements.

Once a user has a proof-of-clean-hands SBT (soul bound token), we know the data is encrypted to an observer that can only decrypt a small amount of data per day, making surveillance or honeypots impossible while enabling compliance with law enforcement and regulators.

For more information, see the architecture:

Or usage:

Need Support?

If you need support with using the Human ID application, please follow the instructions on this page

If you need support when verifying your identity using Human ID, please follow the steps below:

  1. Review or search the contents of this site, especially our .

  2. Click on the support icon in the bottom right corner of this page — or in the Human ID app — to get in contact with a support agent.

Thank you!

Proof of Clean Hands
Clean Hands Architecture
Clean Hands Attestations
Frequently Asked Questions (FAQs)
Please click the intercom button to access our support team

Verifying Identity with Human ID

Using Human ID to privately prove your identity

The Human ID verification process consists of two key stages:

  1. Issuance of Credentials – Obtaining credentials through identity verification.

  2. Proving Facts About Credentials – Demonstrating specific details without revealing unnecessary personal information.

Issuance

On the , users scan a QR code to begin the verification process by photographing the required identity documents.

Proving

In the proof section, users select a wallet address to link their verified proof. Once the user has a Human ID, they can generate proofs of unique personhood, with more proof types planned for the future. To do so, visit , where they will receive a soulbound token confirming whether their address belongs to a unique person.

Convenience vs. Privacy If users trust the KYC provider not to track their wallet, they can immediately prove facts about their credentials. However, for those seeking a cryptographic guarantee that the KYC provider cannot track them, waiting—potentially for days or even weeks—enhances privacy. This follows the same principle as Tornado Cash or ZCash, where longer wait times improve anonymity.

Verifying ePassport

You need to have NFC-enabled passport to complete this verification

You can verify your ePassport using Holonym's Private Passport Verifier to increase Sybil resistance score. Verification is free.

To perform the ePassport verification using your NFC passport, follow the steps below:

  1. Navigate to https://silksecure.net/holonym/diff-wallet.

  2. Click the "ePassport" card.

  3. Follow the steps to download the Private Passport Verifier mobile app.

  4. Open the Private Passport Verifier app. Follow the steps to enter your information. Then scan your NFC passport.

  5. After scanning, you will be redirected to .

  6. At this page, a zero-knowledge proof is generated. This zero knowledge proof conceals your private information while proving that you are a unique person.

  7. Enter your wallet address. Click submit. When you click submit, your address will receive a soul-bound token (SBT) attesting to your uniqueness.

Note that you can only receive one SBT per NFC passport.

Private Identity Verification

Human ID is a privacy-preserving identity protocol developed by Holonym Foundation for the framework. Human ID uses zero knowledge proofs to allow for proving facts about oneself without revealing the whole identity, enabling organizations and smart contracts to verify their users without storing any sensitive information.

Motivation

Identity verification is often required for legal or practical reasons such as KYC, fraud prevention, proof-of-age, and Sybil resistance. However, verifying identity while also preserving privacy has historically been difficult. This problem is even more prominent in web3, where data is stored on a public ledger. Not only is obtaining privacy on a blockcchain difficult, but the adverse affects of losing privacy are greater: "doxxing" a user's wallet address once reveals all of the address' past and future activity.

issuance page
https://silksecure.net/holonym/diff-wallet
https://silksecure.net/holonym/nfc-handoff
Human ID is a private credential system part of the human.tech tool-set that makes privacy-preserving identity possible and simple for both on- and off-chain use cases.

Privacy-preserving proofs

With a Human ID, user can prove a variety of statements without revealing their identity. These include facts needed for meeting legal requirements, Sybil resistance to prevent bot attacks, and account or key recovery. Example statements are:

"I have US residency"

"I have non-US residency"

"I have never received this airdrop from any other crypto address"

"I am an accredited investor"

"I am over 18"

"I am the same person who created this wallet" if the wallet needs recovery

"I have never voted in this DAO's governance"

Many of these are ready to use out-of-the-box, and others can be enabled by contacting us and/or contributing to our GitHub

Note: Proof generation currently takes about 30ms-4s on standard consumer devices, depending on the proof type and the device's specifications.

To start using Human ID in your (d)App, continue to

Otherwise, take a peak into the architecture of human.tech

Continue on to learn about programmable privacy for meeting legal requirements across compliance regimes,

human.tech
Cover

Explore the human.tech framework

Integrating Human ID
Overview

Overview

How Holonym works

Actors and actions in Holonym

At a high level, the Holonym system can be understood in terms of the following actors and actions.

Actors

  • User. Someone who receives credentials and prove facts about themselves, often for the purpose of fulfilling requirements set by an organization.

  • Issuer. A party that issues credentials to users. In the process of issuing credentials, an issuer may use 3rd party identity providers to verify users' identities.

  • Consumer. A party that modifies its users' access or actions based on the facts its users have proven about themselves using. For example, an organization may require its users to reside in a certain country in order to vote on certain proposals.

Actions

  1. Issuer issues a credential to a user. In the user flow, this step is called "issuance".

  2. User proves facts about themselves using their credentials. In the user flow, this step is called "proving".

  3. Consumer allows a user to access something or to perform an action, depending on what the user has proven.

Using Human ID with Stellar

In order to use Human ID with Stellar, follow the steps below:

  1. Visit https://silksecure.net/holonym/diff-wallet with an already-funded wallet you would like to use to pay fees on Aurora, Ethereum, Optimism, or Avalanche.

  2. Follow the steps to get the proof of unique government ID or phone.

  3. When you prove, enter your Stellar address that you want the uniqueness proof to be publicly linked to.

Now you're done! You've obtained the uniqueness proof for the Stellar SBT.

For higher privacy, you can connect and pay with a wallet address different than the one you want the proof-of-uniqueness to be linked to. You may also delay between paying and proving to prevent timing information from linking your ID to your wallet address. If you do so, even in case of a malicious KYC provider your address with the uniqueness proof can't be linked to your identity.

Using Human ID with NEAR

In order to use Human ID with NEAR Protocol, follow the steps below:

  1. Visit https://silksecure.net/holonym/diff-wallet if you have an already-funded wallet you would like to use to pay fees on Aurora, Ethereum, Optimism, Avalanche, or Fantom.

  2. Visit https://silksecure.net/holonym/silk if you do not already have a funded wallet on these chains or want to use the Human Wallet for other reasons.

  3. Follow the steps to get the proof of unique government ID or phone.

  4. When you prove, enter your NEAR account ID that you want the uniqueness proof to be publicly linked to.

Now you're done! You've obtained the uniqueness proof for the NEAR SBT. You can use it with sites like to prove that you are a unique person!

For higher privacy, you can connect and pay with a wallet address different than the one you want the proof-of-uniqueness to be linked to. You may also delay between paying and proving to prevent timing information from linking your ID to your wallet address. If you do so, even in case of a malicious KYC provider your address with the uniqueness proof can't be linked to your identity.

Getting Refunded

If you verification was unsuccessful, please follow the steps below to begin the refund process:

Step 1: Watch the Video Guide (Optional)

To make the process easier, you can watch this helpful video guide that walks you through the refund steps: Watch the Video Guide

Step 2: Visit the Refund Link

Click on the following link to initiate the refund process:

Step 3: Find the "Eligible for Refund" Label

Once you're on the page, locate the "Eligible for Refund" label next to your cards.

Step 4: Click on the Refund Label

Click on the "Eligible for Refund" label to access the refund page.

Step 5: Complete the Refund Process

Follow the on-screen instructions to finalize your refund.

We hope this guide, along with the video, helps you complete your refund process smoothly. Let us know if you have any questions!

https://www.nada.bot/
Refund Link

Where Data Is(n't) Stored

Credential storage

Credentials are stored by the Human Wallet self-custodial wallet so that other than the user who owns them, nobody can access them.

When the user scans government IDs, the data is not stored in Holonym servers. For modern NFC-based ID and Aadhaar verification, a ZKP is created without Holonym ever seeing the data. For older types of government ID verification that do not involve ePassport NFC reading, the user data briefly passes through Holonym servers so that Holonym can sign the credentials, attesting to their authenticity, to return to the user. Data is immediately deleted from Holonym servers and from the KYC provider's servers after the signature is generated. Even if the user does legacy credential verification and does not trust a KYC provider, the protocol gives additional privacy guaranteed: Each user's ID is anonymized in what we call the Privacy Pool, i.e. an anonymity set. The Privacy Pool exists to break the link between a user's identity and a user's crypto address. This creates a level of privacy where even if Holonym, an issuer, or anyone tries to surreptitiously collect user data, they still cannot identify user wallets.

Proof storage

Proofs can be submitted on-chain or off-chain. Proofs, such as "I am a unique person" should not contain sensitive information. The Holonym website and app only permit proofs that do not give sensitive data, regardless of whether they're on- or off-chain.

Issuer

Anybody can make an issuer, as this protocol is decentralized at the base layer. Of course, issuers of credentials should be trusted, despite the fact that anybody can create an issuer. Furthermore, Holonym Foundation operates the frontend and can integrate the issuer into the frontend, so we recommend reaching out in the discord to talk to us and the community before making a custom issuer.

Modularity of the Stack

Holonym creates modular infrastructure for privacy, flexibility, and security.

Holonym applies modularity to the identity verification stack. This fixes a trilemma of security, flexibility, and privacy in identity verification. It has historically been difficult to combine the benefits of centralization (e.g., rigor and ease of use) with decentralization (e.g., flexibility, privacy, and composability). Holonym introduces four layers of identity verification to enable "best of both worlds" approaches.

  1. Issuance layer

  2. Privacy layer

  3. Audit layer

  4. ReLayer

Issuance layer

Unlike blockchains which benefit from being decentralized, some use cases of identity benefit from being centralized. The most common form of rigorous identification is government ID, as centralized as possible. Governments have successfully disbursed over $1 trillion in annual benefits with a relatively low amount of benefit fraud through identity theft. They have been able to conduct large-scale elections using identity to prevent fraudulent votes. These have been possible due to extensive personal information governments are privy to.

Privacy layer

While there is clearly utility (e.g., social security, secure voting processes) in having an institution like a government privy to personal information, the other risks of centralization should be mitigated. Thus, we introduce a privacy layer to separate the issuer from all other aspects of the protocol. This way, an issuer's power is only limited to verifying once; it cannot track subsequent actions of a user.

The privacy is obtained by a global Merkle tree of all credentials. For more information, see

or

Audit Layer

Regulators often need to investigate activities when something goes wrong. Not having identity data available for regulators is illegal in most countries and can have penalties exceeding $100M. Currently, companies record all user activity to have an audit record for regulators, as there has been no other way to comply. In current systems, absent of a way to selectively enforce data privacy, every user is monitored regardless of whether or not they are being investigated.

However, in Holonym, the user can be required to encrypt their own audit trail to the data audit layer. The audit layer should have two functions: data availability and permissioned access, which can in turn be two separate layers. Permissioned access involves a smart contract that ensures that nobody can unscrupulously spy on user activity without proper consent or a court order. It enforces that only certain actors under certain conditions can decrypt the data, allowing for provable privacy for honest users while retaining regulatory oversight for bad actors. These conditions are enforced via a smart contract rather than trusting a third party to maintain privacy.

ReLayer

The relayer layer is the way in which zero-knowledge proofs are sent. Sending and receiving reveal IP addresses and/or wallet addresses if gas is needed. Currently, Holonym uses a relayer to submit transactions and hide the address they came from. It is important that this layer remains flexible so that a relayer node can be replaced by a relayer network, and mixnets can be employed to hide IP addresses.

On-Chain Proofs

To put the proofs on-chain, a Verifier and Relayer are used. The Verifier attests to the proof being complete (since the proof size is too large to cheaply be put on-chain ) and the Relayer posts the attestations on-chain to the Hub contract. We plan to progressively decentralize this Verifier by achieving consensus amongst a permissionless set of verifiers.

Hub Contract

Before a user can submit a proof on-chain, the Hub ensures

  1. An allowed verifier, either a server, network, or smart contract, has verified a VOLE-based ZKP

  2. This verifier has posted any necessary data off-chain so others can check its steps in verifying the proof

  3. Any nullifier revealed by the ZKP has not been spent

Once a proof is on-chain, the Hub sends a non-transferable ERC721 representing a SBT. The Hub also allows reading the relevant metadata pertaining to the SBT, i.e. the public values of the corresponding proof, via the method getSBT.

Attention The Hub does not check any custom logic on the public values of the proof beyond the following two standard public input values: nullifier and expiry. While it checks the nullifier has not been used before and the expiry has, it cannot check custom logic. For example, some proofs require that you check the credential's issuer given in the public outputs is actually a trusted issuer of credentials. To do so, you must call getSBTand check the public value corresponding to the issuer address is valid.

For more information, you may look at the

Flow of Data

Architecture

The Human ID protocol consists of the following components:

  • Client (website or mobile app)

  • Issuers (either using the Human ID credential format or custom formats such as ICAO9303 for NFC passports issued by the government)

  • Hub smart contracts (1 per chain)

  • Relayer

The flow of data is outlined in the action sequence described in the section. The following describes the flow of data in more detail. The diagram illustrates the issuance+storage (designated by 1.*) and proving (designated by 2.*) steps. An organization can grant access to users based on the results stored in the proof contract.

1.1. User verifies themself to an issuer

1.2. The issuer signs credentials

1.3. The user generates a proof that credentials were sigend with particular attributes

1.5. User encrypts their credentials client-side. The encryption key is generated from the user's signature.

1.6. User stores their encrypted credentials in the Human Wallet

2.1. User generates a proof and (e.g., a proof that they are a US resident) and submits it to the verifier

2.2. The verifier attests to it and returns it to either the user or the relayer, depending on how the attestation is to be consumed

2.3. The attestation is given to the recipient: either the Hub smart contract or an offchain consumer

Dry Runs

(Coming soon)

Human ID has dry run flows. These allow developers to (a) complete verification flows and (b) get mock SBTs on testnet while they are integrating Human ID into their dapp.

Get mock SBT

Option A: With Human Wallet SDK

...

Option B: With Redirect

...

Read mock SBT

Use the Holonym API to query the . For the network option, specify "base-sepolia" instead of "optimism".

Run an Observer

If you want to run an observer, please reach out to the Human ID team.

Overview

The observer is a component of the Clean Hands Stack for programmable privacy, used for GDPR-compliant storage of encrypted user data with the DecryptBabyJubJub method for KYC.

The observer is a primary component in the Clean Hands stack with . To interact with the observer, a user generates a ZKP they have passed sanctions checks, and this ZKP outputs the ciphertext of the user's personal identifiable information (PII) and the user's associated blockchain address. The Observer's role in this system is to verify ZKPs, issue attestations to users with valid ZKPs, and to store the public outputs of these ZKPs so that the ciphertext can be decrypted if Human Network permits.

Custom Sybil Resistance

Political, social, and economic systems often need Sybil resistance. Sybil resistance is anything of the form:

1 person => 1 x

e.g.,

Hub

The contract where everything happenson-chain

Before a user can submit a proof on-chain, the Hub ensures

  1. An allowed verifier, either a server or smart contract, has verified a VOLE-based ZKP

  2. This verifier has posted any necessary data off-chain so others can check its steps in verifying the proof

  3. Any nullifier revealed by the ZKP has not been spent

1 person => social security check

  • Standard Sybil Resistance

    To just see whether a unique person owns a wallet address, you may visit Integrating Human ID

    More Advanced Sybil Resistance

    Sometimes you want to prove Sybil resistance for a specific action. E.g., if a user wants to vote in multiple elections from different addresses as to not reveal their voting history. This can be done by giving each election a unique actionID.

    Importantly, this also means bribery would be easier. Imagine an action somebody doesn't care about. That person can provide a new address that actionID. One can imagine a DAO vote where a bad actor is passionate about the outcome, and can create 1000 new address, then bribe 1000 random people to register one of those addresses for that specific actionID. Since the random people don't care about the vote outcome, they don't mind giving away their uniqueness to the new address for that particular actionID.

    For the default actionID, it's far harder to launch a large-scale bribery attack, as giving up a vote to a briber is giving up everything to a briber: all future votes, universal basic income, and airdrops will go to the briber. While some people would gladly give up these rewards for the right price, doing so would require the presentation of government ID in order to participate in a black market. This risk far outweighs any rewards, so we assume such bribery on the default actionID will be negligible.

    How to set an actionID

    To prove uniqueness with respect to a specific action, you must create an actionId. Please use a large random number less than 21888242871839275222246405745257275088548364400416034343698204186575808495617 (the bn254 scalar field order). Let's say your actionId is 11223344556677, and after your users are done proving uniqueness, you want to them to be sent back to yourwebsite.com/yourcallback.

    Then, they may visit https://holonym.io/prove/uniqueness/11223344556677/yourwebsite%2Ecom%2Fyourcallback

    You can then view their verification status with respect to the custom actionID by the standard steps in Integrating Human ID. Simply replace the actionID in the code examples with your custom actionID.

    Once a proof is on-chain, the Hub sends a non-transferable ERC721 representing a SBT. The Hub also allows reading the relevant metadata pertaining to the SBT, i.e. the public values of the corresponding proof, via the method getSBT.

    Attention The Hub does not check any custom logic on the public values of the proof beyond the following two standard public input values: nullifier and expiry. While it checks the nullifier has not been used before and the expiry has, it cannot check custom logic. For example, some proofs require that you check the credential's issuer given in the public outputs is actually a trusted issuer of credentials. To do so, you must call getSBTand check the public value corresponding to the issuer address is valid.

    For more information, you may look at the source code

    Hub
    Credentials
    source code
    sybil-resistance endpoint
    Previous V2 architecture. In V3 storage is done in the Human Wallet, there is no Roots smart contract, and the Proof smart contract is called the Hub.
    Actions
    Endpoints

    POST /observations

    This endpoint does the following.

    • Verify the Clean Hands ZKP. Uses this circuit to verify a proof which should have been generated using this package.

    • Make sure the encryption key output by the circuit is Human Network’s public key.

    • Make sure the issuer address output by the circuit is the configured clean hands issuer.

    • Make sure the conditions contract signed by the user is on our whitelist.

    • Verify the user’s signature of the conditions contract.

    • Store the ZKP’s public values, user's address, user's signature, and signed access contract in the observations collection.

    • Issue an attestation on Sign Protocol.

    POST /observations/sui

    Identical to POST /observations but mints an SBT on Sui instead of issuing an attestation on Sign Protocol.

    GET /observations?user_address=<address>

    This endpoint queries the database for an observation for the provided user address and returns the result.

    Schemas

    Environment variables

    Create a .env file with the following variables. All are necessary.

    MONGODB_URI - URI for MongoDB. The observer stores ZKP outputs, the user's blockchain address, the user's signature, and the address of the access conditions contract in a collection titled "observations".

    CLEAN_HANDS_ISSUER_ADDRESS - The address that issued the credentials used as inputs to the ZKP. This is used to validate the issuer address output by the ZKP.

    ATTESTOR_PRIVATE_KEY - The private key of the account used to issue attestations. This private key is used to create transactions on Optimism. It's account remain funded; otherwise attestations will not be issued.

    OP_RPC_URL - URL for Optimism RPC node.

    SUI_PRIVATE_KEY - The output of the sui keytool export command.

    SUI_PRIVATE_KEY_SCHEME - ed25519 | secp256k1 | secp256r1

    Run

    Human ID
    pub struct ObservationSchema {
        /// Distinct from _id. This is a hash of the fields of the observation. Allows for 
        /// more efficient lookups to make sure we don't store the same observation twice.
        pub id: String,
        pub user_address: String,
        pub signature: String,
        pub access_contract: String,
        pub zkp_public_values: Vec<String>,
    }
    # You might want to modify the following
    MONGODB_URI=mongodb://localhost:27017
    CLEAN_HANDS_ISSUER_ADDRESS=3953516660401541564649985379958697237340496801951929947163239598560489169274
    
    # The following variables MUST be changed
    ATTESTOR_PRIVATE_KEY=123 
    OP_RPC_URL=abc
    SUI_PRIVATE_KEY=123
    SUI_PRIVATE_KEY_SCHEME=ed25519
    docker pull holonym/observer
    docker run --env-file .env holonym/observer

    Clean Hands Architecture

    Human ID's Proof of Clean Hands establishes that a user

    1. has completed KYC,

    2. has verified that they are not on any sanctions or PEP lists,

    3. has encrypted their identifying data to Human Network, and

    4. has selected a smart contract that determines the conditions under which their data can be decrypted.

    Components

    The Clean Hands system is made possible by the following:

    • Credential issuer.

    • Zero knowledge circuit.

    • Observer node.

    • Smart contract(s).

    The novelty of the Clean Hands system is in its encryption and decryption. Each user encrypts their data to Human Network and proves encryption. When they encrypt, they select a smart contract that determines who is allowed to decrypt. The user's ciphertext is sent to an "observer" node. An entity granted access to the user's ciphertext by the observer node and granted decryption rights by the smart contract can decrypt the user's ciphertext.

    Flow

    There are five steps in the Clean Hands flow.

    1. Credential issuance

    The user verifies their identity and that they are not on any sanctions or PEP lists. Human ID's credential issuer issues a signature to the user attesting to the veracity of the user's identity data and sanctions list status.

    2. Proof generation

    The user generates a zero knowledge proof. This proof uses the credentials and signature issued by the credential issuer. The proof establishes that the user has completed KYC and sanctions list checks and that they have encrypted their name and date of birth to Human Network. At this step, the user also signs, using the ephemeral private key used in encryption, the address of a smart contract. This smart contract determines who can decrypt the user's data.

    3. Attestation issuance

    The user sends the proof to an observer node. The observer verifies the proof, stores the proof's public outputs so that the user's ciphertext can be retrieved at a later date by authorized entities, and issues an on-chain attestation to the user. This attestation attests to the fact that the user has completed all of these first 3 steps.

    4. On-chain activity

    With the attestation, the user is now able to interact with smart contracts that require proof of clean hands, for example, which allows verified users to transaction privately.

    5. Decryption

    If necessary, the ciphertext from the user’s zero knowledge proof is decrypted. The entity that wants to decrypt must request the user's ciphertext from the observer. They must also be granted decryption rights from the smart contract associated with the ciphertext. With the ciphertext and decryption rights, the decrypter requests decryption from Human Network.

    Lists checked for Proof of Clean Hands

    • US / OFAC Capta List (CAP)

    • US / OFAC Non-SDN Chinese Military-Industrial Complex Companies List (CMIC)

    • US / BIS Denied Persons List (DPL)

    • US / DOS ITAR Debarred (DTC)

    VOLE-based ZK

    What it enables

    We implemented an () VOLE-based prover to enable fast identity proofs that otherwise aren't practical to do with privacy. Privacy requires all proofs to be done on consumer hardware without expensive servers. Yet most interesting proofs such as NFC-enabled passports and email proofs require expensive RSA, SHA256, or Keccak256 operations. The prover can take the existing circuits in circom, where these primitives have already been implemented and audited. We have utilized this to allow mobile browsers to perform proof of passport in seconds without running out of memory.

    Human Network.

    US / BIS Entity List (EL)

  • INT / Financial Action Task Force

  • US / FBI Most Wanted

  • US / FINCEN Financial Crimes Enforcement Network - 311

  • US / OFAC Foreign Sanctions Evaders (FSE)

  • INT / Interpol Red Notices

  • US / DOS Nonproliferation Sanctions (ISN)

  • US / BIS Military End User (MEU) List

  • US / OFAC Consolidated Sanctions List (Non-SDN Lists)

  • US / OFAC Non-SDN Menu-Based Sanctions List (NS-MBS List)

  • US / HRJ US Department of the Treasury Sanctioned Countries

  • US / HRJ OFAC Sanctioned Countries (Military)

  • US / HRJ OFAC Sanctioned Countries

  • INT / Politically Exposed Persons

  • US / OFAC Palestinian Legislative Council List (PLC)

  • US / OFAC Specially Designated Nationals (SDN)

  • US / OFAC Sectoral Sanctions Identifications List (SSI)

  • US / DOS Cuba Restricted List

  • the Ethereum-Aztec bridge
    How it works

    We use a variant of VOLE-based ZK called VOLE in the head, where we modified the linear code to be the repeat-multiple-accumulate (RMA) code. VOLE-based ZK is orders of magnitude more efficient than popular proving systems, but it is not used for rollups because it is not succinct and difficult to make noninteractive. Yet for identity, especially when offchain, VOLE-based ZK is ideal. VOLE in the head renders VOLE-based ZK noninteractive with roughly a 2x performance cost. We replace the repetition code with the RMA code to have faster performance.

    VOLE-based ZK consists of two phases:

    1. Commitment / Preprocessing:

    VOLE is performed, giving a two-party "commitment": the verifier receives functions describing many lines with the same slope, and the prover receives random points on each of these lines. The parties do not learn the other parties' values. Notice that by showing a verifier one of his points, a prover can open one of his commitments.

    A derandomized step is performed so instead of commitment to random values, the parties commit to the witness.

    2. Proving:

    To see the committed output of any addition gates, the verifier just adds her commitments. In fact, any linear operation is trivial because the VOLE commitment scheme is linearly homomorphic.

    For multiplication gates, a bit more effort is required: the prover must send the commited values and prove they are correct. We use quicksilver for this.

    Finally, after all of the circuit's gates' outputs have been learned by the verifier, she can ask the prover to open any of the public outputs.

    Making It Noninteractive

    This involves a special type of VOLE based off of SoftSpokenOT, in which the outputs that the prover recieves are independent of the verifier's choices of randomness. This allows the verifier's randomness to be created by the Fiat-Shamir heuristic. However, with this variant of VOLE, the verifier's choices of random values are limited to a very small set, making it too easy for the prover to guess the verifier's choices -- in our case, the verifier has only two options, so this is highly insecure.

    As a result, linear codes are used to enforce that the prover must guess at least d secrets the verifier chose, where d is the minimum distance of the linear code. For more information see the VOLE in the head paper.

    open source

    Stellar

    How to read Human ID SBTs on Stellar

    Users can opt to receive SBTs on Stellar. See Using Human ID with Stellar for user-facing instructions.

    Off-chain

    Use the Stellar SDK to simulate a read transaction to get the user's SBT.

    On-chain (Soroban)

    1. Fetch the wasm for the Human ID SBT contract.

    2. Incorporate into your Soroban contract.

    This example contract has a get_sbt function that simply wraps the function from the SBT contract.

    Check out the full .

    For the list of circuit IDs, see .

    Flow of Data: KYC Proof of Personhood

    Human ID's Proof of Personhood via KYC consists of the following components:

    • User agent (UI)

    • Human ID server

    • ID verification provider

    • Verifier

    The flow of data is outlined in the following sequence diagram. Please refer to notes for detailed explanations for relevant parts.

    Issuance and Proving

    Sections 1 and 2 in the sequence diagram constitute issuance. This is where the user's private credentials are issued.

    Section 3 is proving, where the user proves facts about their issued credentials.

    Notes on handling of user data by IDV Providers

    Following data are requested by IDV providers as photo or/and video stream during the verification process.

    • Selfie (photo, video stream)

    • One of the following documents

      • Passport

      • Driver License

    Currently, following IDV providers are supported.

    Veriff has clearly outlined in its

    • a list of compliances (i.e: GDPR)

    • regarding data collection, retention and deletion

      • a list of

    Onfido has its

    • a list of

    • regarding data

    Facetec has two privacy policies ( and )

    SDK privacy policy seems more relevant for usage for ID verification. Its documentation on privacy is sparse compared to the other 2 providers.

    • In article #2, it mentions that any data sent to its server is encrypted, siloed and is never stored with any additional personally identifiable information (PII).

    • In article #6, it provides detailed info on its compliance to GDPR for EU residents.

    Notes on client-side encryption of IDV session result

    IDV provider returns the session result to user.

    With Human Wallet:

    The result is encrypted on client-side using a derivative of the PRF.

    With other wallets:

    The result is encrypted with key derived with hash(userSignature(aConstantMessage)) to generate ciphertext.

    Notes on ciphertext and storage of userCredentials

    Only the encrypted ciphertext which is non PII is stored in Human ID database as below.

    View to see the data included in user credentials.

    Notes on verifier and SBT issuance

    The user submits a zero knowledge proof of uniqueness () to the verifier server. The verifier verifies the ZKP, and upon verification, issues a soulbound token to the user. The circuit ID, issuer address, expiry, and actionNullifier, the ZK proof are embedded in the Soul-bound token.

    Sign Protocol Attestations

    How to read Holonym attestations on Sign Protocol

    Holonym issues an Sign Protocol attestation for every SBT it sends. The following is an example of how to query and validate Holonym attestations.

    Query

    See the for how to construct queries.

    This query gets the first page of attestations issued by Holonym.

    Query parameters used:

    Clean Hands Attestations

    How to read Clean Hands attestations

    Human ID issues its Clean Hands attestation to users who prove that they are not on any sanctions lists (see all the lists here: ).

    Off-chain with Sign Protocol

    Use Sign Protocol's scan API to see whether a user has a valid Clean Hands attestation.

    import {
      Horizon,
      rpc,
      TransactionBuilder,
      Networks,
      Contract,
      scValToNative,
      nativeToScVal,
    } from '@stellar/stellar-sdk'
    
    /// Types ///
    type StellarSbt = {
      action_nullifier: bigint,
      circuit_id: bigint,
      expiry: bigint,
      id: bigint,
      minter: string, // Stellar address
      public_values: Array<bigint>,
      recipient: string, // Stellar address
      revoked: boolean
    }
    type StellarSbtStatus = 'valid' | 'expired' | 'revoked' | 'none'
    type GetStellarSBTRetVal = {
      sbt?: StellarSbt
      status: StellarSbtStatus
    }
    
    /// Constants ///
    const horizonServerUrl = 'https://horizon.stellar.org'
    const sorobanRpcUrl = 'https://mainnet.sorobanrpc.com'
    const stellarSBTContractAddress = 'CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5'
    
    /// Get SBT ///
    async function getStellarSBTByAddress(
      address: string,
      circuitId: string
    ): Promise<GetStellarSBTRetVal | null> {
      const sorobanServer = new rpc.Server(sorobanRpcUrl)
      const userAccount = await sorobanServer.getAccount(address)
      const contract = new Contract(stellarSBTContractAddress)
    
      const operation = contract.call(
        'get_sbt',
        nativeToScVal(address, { type: 'address' }),
        nativeToScVal(circuitId, { type: 'u256' })
      )
    
      const transaction = new TransactionBuilder(userAccount, {
        Networks.PUBLIC,
        fee: '100',
      })
        .addOperation(operation)
        .setTimeout(60)
        .build()
    
      const simulationResponse = await sorobanServer.simulateTransaction(transaction)
      const parsed = rpc.parseRawSimulation(simulationResponse)
    
      // Happy path. User has the desired SBT.
      if (rpc.Api.isSimulationSuccess(simulationResponse)) {
        const _parsed = parsed as rpc.Api.SimulateTransactionSuccessResponse
    
        if (!_parsed.result) {
          throw new Error('Unexpected: Could not get "result" field from parsed Stellar transaction simulation for SBT query')
        }
    
        const sbt = scValToNative(_parsed.result?.retval)
    
        return {
          sbt,
          status: 'valid'
        }
      }
      // Error cases. User does not have the SBT.
      else if (rpc.Api.isSimulationError(simulationResponse)) {
        const _parsed = parsed as rpc.Api.SimulateTransactionErrorResponse
    
        // Error code 1 is "SBT not found"
        if (_parsed.error.includes('HostError: Error(Contract, #1)')) {
          return { status: 'none' }
        }
    
        // Error code 5 is "SBT revoked"
        if (_parsed.error.includes('HostError: Error(Contract, #5)')) {
          return { status: 'revoked' }
        }
    
        // Error code 6 is "SBT expired"
        if (_parsed.error.includes('HostError: Error(Contract, #6)')) {
          return { status: 'expired' }
        }
    
        throw new Error(`Stellar transaction simulation for SBT query failed: ${_parsed.error}`)
      } else {
        throw new Error('Unexpected: Stellar transaction simulation for SBT query returned an unexpected response')
      }
    }
    
    soroban contract fetch --id CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5 -o ./human_id_sbt_contract.wasm --network mainnet --network-passphrase "Public Global Stellar Network ; September 2015" --rpc-url https://mainnet.sorobanrpc.com
    Human ID SBT contract here
    here

    schemaId - Filters for the HolonymV3 Sign Protocol schema.

  • attestor - Filters for Holonym's attestor address, 0xB1f50c6C34C72346b1229e5C80587D0D659556Fd.

  • The response looks like this...

    If you are querying for a specific recipient, filter by the recipient.

    Validate

    For a Holonym V3 SBT, we always want to verify the circuit ID as well as the action ID and issuer used to generate the ZKP. We want the circuit ID to match whatever circuit we are filtering for. We want the action ID to be the default action ID used for Holonym Sybil resistance proofs. We want the issuer to match whatever Holonym issuer we are filtering for.

    See here for the circuit IDs, action ID, and issuers.

    Sign Protocol docs
    curl 'https://mainnet-rpc.sign.global/api/index/attestations?schemaId=onchain_evm_10_0x1&attester=0xB1f50c6C34C72346b1229e5C80587D0D659556Fd'
    On Optimism with Sign Protocol

    Tutorial coming soon...

    On-chain (not Optimism)

    Use our off-chain attester, and verify its signature on-chain. Our attester returns a signature of the circuit ID, action ID, and user address, if the address has a clean hands attestation.

    Our attester address is 0xa74772264f896843c6346ceA9B13e0128A1d3b5D.

    Query for signature

    Verify signature in Solidity

    Warning: this Solidity code is untested.

    Verify signature in JavaScript

    Sui SBTs

    TypeScript

    We also allow users to mint SBTs on Sui. The following code verifies that the address possesses a Clean Hands SBT on Sui.

    Move

    See the example SBT verifier package for how to verify the SBT on chain.

    Decrypt

    See Decryption of Provably Encrypted Data.

    Lists checked for Proof of Clean Hands
    #![no_std]
    use soroban_sdk::{contract, contractimpl, Env, Address, U256};
    
    mod human_id_sbt_contract {
        soroban_sdk::contractimport!(
            file = "../../human_id_sbt_contract.wasm"
        );
    }
    
    #[contract]
    pub struct MyContract;
    
    #[contractimpl]
    impl MyContract {
        /// * `recipient` - The address of the SBT recipient.
        /// * `circuit_id` - This determines the type of SBT to look up. For example, KYC, ePassport, and phone number
        ///   SBTs each have a different circuit ID.
        pub fn get_sbt(env: Env, recipient: Address, circuit_id: U256) -> human_id_sbt_contract::SBT {
            let human_id_sbt_contract_addr = Address::from_str(&env, "CCNTHEVSWNDOQAMXXHFOLQIXWUINUPTJIM6AXFSKODNVXWA4N7XV3AI5");
            let human_id_sbt_client = human_id_sbt_contract::Client::new(&env, &human_id_sbt_contract_addr);
    
            human_id_sbt_client.get_sbt(&recipient, &circuit_id)
        }
    }
    {
       "data" : {
          "page" : 1,
          "rows" : [
             {
                "attestTimestamp" : "1714819085000",
                "attestationId" : "0x65",
                "attester" : "0xB1f50c6C34C72346b1229e5C80587D0D659556Fd",
                "chainId" : "10",
                "chainType" : "evm",
                "data" : "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004230783732396436363065316330326534653431393734356536313764363433663839376135333836373363636631303531653039336262666135386230613132306200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000068080e7f000000000000000000000000ededf460a77928f59c27f37f73d4853fd8a0798400000000000000000000000000000000000000000000000000000000075bcd151cb8413a579d6138d257450bb5209579bde35c885f7a4405e3767a5a5d2ea6df03fae82f38bf01d9799d57fdda64fad4ac44e4c2c2f16c5bf8e1873d0a3e1993",
                "dataLocation" : "onchain",
                "extra" : {},
                "from" : "0xB1f50c6C34C72346b1229e5C80587D0D659556Fd",
                "fullSchemaId" : "onchain_evm_10_0x1",
                "id" : "onchain_evm_10_0x65",
                "indexingValue" : "0x1cb8413a579d6138d257450bb5209579bde35c885f7a4405e3767a5a5d2ea6df",
                "lastSyncAt" : null,
                "linkedAttestation" : "",
                "mode" : "onchain",
                "recipients" : [
                   "0xEdedf460A77928f59c27f37F73D4853FD8a07984"
                ],
                "revokeReason" : null,
                "revokeTimestamp" : null,
                "revokeTransactionHash" : "",
                "revoked" : false,
                "schema" : {
                   "chainId" : "10",
                   "chainType" : "evm",
                   "data" : [
                      {
                         "name" : "circuitId",
                         "type" : "string"
                      },
                      {
                         "name" : "publicValues",
                         "type" : "uint256[]"
                      }
                   ],
                   "dataLocation" : "onchain",
                   "description" : "Holonym V3 SBT",
                   "extra" : {
                      "data" : "{\"name\":\"HolonymV3\",\"description\":\"Holonym V3 SBT\",\"data\":[{\"name\":\"circuitId\",\"type\":\"string\"},{\"name\":\"publicValues\",\"type\":\"uint256[]\"}]}"
                   },
                   "id" : "onchain_evm_10_0x1",
                   "maxValidFor" : "0",
                   "mode" : "onchain",
                   "name" : "HolonymV3",
                   "originalData" : "{\"name\":\"HolonymV3\",\"description\":\"Holonym V3 SBT\",\"data\":[{\"name\":\"circuitId\",\"type\":\"string\"},{\"name\":\"publicValues\",\"type\":\"uint256[]\"}]}",
                   "registerTimestamp" : "1714776243000",
                   "registrant" : "0xcaFe2eF59688187EE312C8aca10CEB798338f7e3",
                   "resolver" : "0x0000000000000000000000000000000000000000",
                   "revocable" : true,
                   "schemaId" : "0x1",
                   "syncAt" : "1714776266473",
                   "transactionHash" : "0xfd378e1a6758a5fbafad18f21da353485c8106013dccf501268b6cf126a0eef0"
                },
                "schemaId" : "0x1",
                "syncAt" : "1714819103980",
                "transactionHash" : "0x6216e5793cb20d350680942e97f33688bb075be6c2bbbc960b71d254a2e7bc96",
                "validUntil" : "0"
             },
             ...
          ],
          "size": 100,
          "total": 195
       },
       "message" : "ok",
       "statusCode" : 200,
       "success" : true
    }
    // We are using Ethers v5
    const { ethers } = require("ethers");
    
    // Extract attestation data. 
    // This is data.rows[0].data in the API response above
    const data = "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004230783732396436363065316330326534653431393734356536313764363433663839376135333836373363636631303531653039336262666135386230613132306200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000068080e7f000000000000000000000000ededf460a77928f59c27f37f73d4853fd8a0798400000000000000000000000000000000000000000000000000000000075bcd151cb8413a579d6138d257450bb5209579bde35c885f7a4405e3767a5a5d2ea6df03fae82f38bf01d9799d57fdda64fad4ac44e4c2c2f16c5bf8e1873d0a3e1993";
    
    const decoded = ethers.utils.defaultAbiCoder.decode(["string", "uint256[]"], data);
    
    const circuitId = decoded[0];
    
    // Public values of the ZKP
    const publicValues = decoded[1];
    
    const actionId = publicValues[2];
    const issuer = publicValues[4];
    
    // Make sure circuitId matches KYC circuit ID
    if (circuitId != "0x729d660e1c02e4e419745e617d643f897a538673ccf1051e093bbfa58b0a120b") {
        throw new Error("Invalid circuit ID");
    }
    
    // Validate action ID
    if (actionId.toString() != "123456789") {
        throw new Error("Invalid action ID");
    }
    
    // Make sure issuer is the KYC Holonym issuer
    if (issuer.toHexString() != "0x03fae82f38bf01d9799d57fdda64fad4ac44e4c2c2f16c5bf8e1873d0a3e1993") {
        throw new Error("Invalid issuer")
    }
    // Set user address
    const address = '0x123'
    
    const resp = await fetch(`https://mainnet-rpc.sign.global/api/scan/addresses/${address}/attestations`)
    const data = await resp.json()
    const cleanHandsAttestations = data.data.rows.filter((att) => (
      att.fullSchemaId == 'onchain_evm_10_0x8' &&
      att.attester == '0xB1f50c6C34C72346b1229e5C80587D0D659556Fd' &&
      att.isReceiver == true && 
      !att.revoked &&
      att.validUntil > (new Date().getTime() / 1000)
    ))
    const hasValidAttestation = cleanHandsAttestations.length > 0
    // Set user address
    const userAddress = '0x123'
    
    const actionId = 123456789
    const resp = await fetch(`https://api.holonym.io/attestation/sbts/clean-hands?action-id=${actionId}&address=${userAddress}`)
    const { isUnique, signature, circuitId } = await resp.json();
    pragma solidity ^0.8.4;
    
    import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
    
    contract SignatureVerification {
        using ECDSA for bytes32;
    
        address public humanIdAttester = 0xa74772264f896843c6346ceA9B13e0128A1d3b5D;
    
        function verifySignature(
            uint256 circuitId,
            uint256 actionId,
            address userAddress,
            bytes memory signature
        ) public pure returns (bool) {
            bytes32 digest = keccak256(abi.encodePacked(
                circuitId,
                actionId,
                userAddress
            ));
    
            bytes32 personalSignPreimage = keccak256(abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                digest
            ));
    
            (
                address recovered,
                ECDSA.RecoverError err,
                bytes32 _sig
            ) = ECDSA.tryRecover(personalSignPreimage, publicSignalsSig);
    
            return recovered == humanIdAttester;
        }
    }
    // Verify using ethers v5
    const digest = ethers.utils.solidityKeccak256(
      ["uint256", "uint256", "address"],
      [circuitId, actionId, userAddress]
    );
    const personalSignPreimage = ethers.utils.solidityKeccak256(
      ["string", "bytes32"],
      ["\x19Ethereum Signed Message:\n32", digest]
    );
    const recovered = ethers.utils.recoverAddress(personalSignPreimage, signature)
    console.log(recovered === '0xa74772264f896843c6346ceA9B13e0128A1d3b5D')
    import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';
    const PACKAGE_ID = '0x53ddebd997f0e57dc899d598f12001930e228dddadf268a41d4c9a7c1df47a97';
    const SBT_TYPE = `${PACKAGE_ID}::sbt::SoulBoundToken`;
    const client = new SuiClient({
        url: getFullnodeUrl('mainnet'),
    });
    async function hasSBT(address: string): Promise<boolean> {
        try {
            // Query all objects owned by the address
            const objects = await client.getOwnedObjects({
                owner: address,
                options: { showType: true }, // Include object type in the response
            });
            // Check if any object matches the SBT type
            const hasSbt = objects.data.some((obj) => {
                const type = obj.data?.type;
                return type === SBT_TYPE;
            });
            return hasSbt;
        } catch (error) {
            console.error('Error querying SBT ownership:', error);
            return false;
        }
    }

    Identity Card

    security

  • storage

  • ControlCase has issued compliance certificate for ISO 27001

  • Veriff
    Onfido
    Facetec
    trust center
    controls
    subprocessors
    privacy policy
    compliances
    collection
    processing
    site
    sdk
    see the circuit here

    FAQs

    You got questions? We got answers!

    What is human.tech?

    human.tech is a technology platform that enhances the security, transparency, and compliance of digital interactions through advanced cryptographic techniques and decentralized networks.

    How does human.tech's technology work?

    human.tech leverages blockchain technology to create secure, immutable records of transactions and digital identities to ensure data integrity and privacy.

    What are human.tech's main products?
    • Human ID: A private digital identity solution that verifies users without storing or seeing sensitive information. Data stays on user devices, and only proofs of identity and compliance are submitted.

    • Human Network: Provides decentralized primitives for a better web. It enables privacy-preserving KYC with AML, and supports account login and recovery based on passwords and privacy-preserving biometrics.

    How does Human ID ensure data security?

    Human ID uses advanced cryptographic techniques to protect data from unauthorized access and tampering, ensuring only authorized parties can access sensitive information.

    What measures does Human ID take to protect user privacy?

    Human ID technology keeps data on user devices, submitting only proofs of compliance and identity. This approach ensures that sensitive information is never exposed or stored centrally.

    What is ZK KYC? How is it different from KYC?

    When most people hear KYC, they think “loss of privacy”, “centralization”, and “getting doxxed”. Holonym uses “Zero Knowledge” KYC that prevents any identity authority or third party (including us) from linking your credentials with your wallet address.

    This means that even if a third party KYC provider snoops on your data, there is no way to know which person you are on-chain.\

    How does Human ID help with regulatory compliance?

    Human ID automates compliance processes through real-time transaction monitoring and smart contracts, reducing administrative burdens and ensuring adherence to regulatory standards.

    What specific regulatory standards does Human ID support?

    Human ID supports various regulatory standards by providing privacy-preserving KYC and AML capabilities, facilitating easier compliance with financial regulations.

    What should I do if my wallet was hacked?

    We’re sorry to hear about your situation. Unfortunately, we cannot change your wallet address, as we do not support proof revocation. You will need to wait until your verification expires in one year before you can verify again. You may proceed with a refund then.

    Where can I find the proof contracts?

    You can find a complete list of ZK-SBT contracts supported by Holonym here: Just go to and search the contract address to see more information.

    I minted my ZK SBT to the wrong address! Can I transfer it?

    It is not possible to transfer SBTs at this time because it breaks Sybil Defense with the current implementation.

    How does Human ID guarantee privacy for me?

    Human ID is designed to minimize any chance of data leakage. For any form of document-based verification, some agent has to assess a document and check it against a large centralized database. Almost all KYC providers in Web3 keep a centralized database that can be linked to an individual wallet address (see FTX disclosure). Holonym allows users to prove on-chain facts about themselves while making it highly difficult, near impossible, to link a real world document to an on-chain identity. Not even we can link your data to your wallet.

    How is this possible?

    1. During verification, all data is sent over an encrypted channel to a third party credential issuer.

    How many points do I earn for verifying my identity?

    You’ll earn 16 points for KYC verification and 1.5 points for verifying your phone number.

    Is there a specific age limit for completing ID verification with Human ID?

    Yes, you need to be at least 18 years old.

    My ID verification always fails. Can I submit my ID through a ticket and have it verified there?

    Unfortunately, manual review of IDs isn't supported; the process relies on automation.

    Can I obtain a Human Passport stamp by verifying my phone number?

    Yes, phone verification is now supported.

    What type of documents can I use to verify my identity for proof of uniqueness?

    You can verify with any of the following standard un-expired and valid physical documents that match your identity:

    • passport

    • driver's license

    Are there any countries that aren't supported for verification?

    Yes, the unsupported countries are North Korea, Iran, Syria, and Cuba.

    Verification complete! What’s next?

    Continue to the minting page:

    1. Connect your wallet: .

    2. Select Phone Verification or KYC/Government ID Verification.

    Are there any verification guidelines I can use to avoid common issues?

    To ensure a smooth verification process, please follow these steps:

    • Clear Photos – Avoid blurriness by holding your phone still while capturing your ID and selfie. • Scan Both Sides – If using a driver's license, upload both front and back (no duplicates). • Unaltered Documents – Russian internal passports must have a visible, unedited machine-readable area. • Valid ID – Ensure your document is not expired. • Selfie Match – Your selfie must clearly match the photo on your ID. • Use a Physical Document – Do not take a picture of an ID displayed on a screen. • Age Requirement – You must be 18+ years old to verify.

    ⚠️ Unsupported Countries: North Korea, Iran, Syria, and Cuba. ⚠️ Unsupported IDs: Nigeria's NIN and Bangladesh's resident permit.

    Can I verify again but with a different wallet?

    No, each user can verify only one wallet per identity. If the system detects multiple verification attempts with different wallets, they will all fail. If you encountered an 'already registered' error, do not attempt verification again. For now, your only option is to request a refund and wait until the first verification expires after a year before trying again.

    How can I proceed with verification after making a payment?

    If your payment was successful, you can go ahead and verify directly on the website.

    Note: do not make another payment if you were not redirected to the verification page the first time. If you're having trouble and can't continue, open a support ticket for assistance.

    What to do if my verification was unsuccessful?

    If verification was unsuccessful, you can request a refund by following the refund steps outlined in our FAQs.

    Can I retry verification if my first attempt failed?

    After receiving your refund, you can try again, but we recommend using a different ID than the one you previously used.

    How can I verify my identity?

    You can verify your identity using standard unexpired and valid physical documents such as a passport, driver's license, national ID, or residence card. Unsupported documents include student IDs, Nigeria's NINS, and Bangladesh's resident permit IDs.

    You can review the supported countries and documents with the following link: •

    Is verification a one-time fee, or will I need to re-verify later?

    Verification is valid for one year. After that, you’ll need to pay again to verify your identity.

    Do I need to verify and mint SBT again if I’ve already minted it on the old app?

    No, you don’t need to verify again as long as your previous verification is still valid.

    What are the SBT Contract addresses?

    Here are the SBT contract addresses:

    • For V3: 0x2AA822e264F8cc31A2b9C22f39e5551241e94DfB • For Legacy ID: 0x7A81f2f88b0eE30eE0927c9F672487689C6dD7Ce • For Legacy Phone: 0xe337aD5aA1Cb84e12a7Aab85aEd1Ab6cb44C4a8e

    I minted my ZK SBT to the wrong address! Can I transfer it?

    SBTs (Soulbound Tokens) are intentionally non-transferable, making them a key part of our Sybil Defense system. To verify a different wallet, you must wait for the current verification proof to expire, which happens one year after minting.

    Can I try verifying again with a different ID?

    Each paid verification allows for one attempt. If it fails, you can request a refund and try again, but you'll need to submit a new payment and restart the process.

    Do I get a full refund if my verification failed?

    If your verification fails, you can request a full refund.

    How do I request a refund?

    Follow these steps to request a refund:

    Step 1 (Optional): Watch a step-by-step refund walkthrough on YouTube:

    Step 2: Go to the Refund Page:

    Step 3: Look for the "Eligible for Refund" tag next to your card.

    Step 4: Click the "Eligible for Refund" label and follow the instructions to complete the process.

    What to do if I received the "Failed" message?

    This means your verification attempt was unsuccessful. This is not a system bug, as the process is managed by the provider and subject to their approval. Verification failures can happen for various reasons, and unfortunately, we do not have the exact reason for each unsuccessful attempt. If you wish to try again, please use a different ID than the one you previously used.

    Should I add my country code when entering phone number for verification?

    Do NOT include the country code when entering your phone number. Select your country from the provided list instead.

    I have troubles receiving OTP, what should I do?

    Follow this OTP Troubleshooting Guide:

    1. Do NOT include the country code when entering your phone number. Select your country from the provided list instead.

    2. Restart your phone to refresh the connection with your network provider.

    I received "Loading Credentials" error, what should I do?

    If you received a "Loading Credentials" error, follow these steps:

    1. Allow up to 30 minutes for the provider to complete your ID verification.

    2. DO NOT close, refresh, or exit the page during this process.

    What to do if I received the "Failed to Fetch or V3SybilResistance.r1cs" error while minting SBT?

    If you receive a "Failed to Fetch or V3SybilResistance.r1cs" error during the minting process, try the following solutions:

    • Use a Different Browser or Device Copy the minting page link and open it in another browser or device.

    • Uninstall Internet Download Manager (IDM) IDM can interfere with the minting process, so removing it may resolve the issue.

    If the issue persists, try clearing your cache or using incognito mode.

    Why am I getting an "Already Registered" error?

    If you receive an "Already Registered" message, it means you have already completed verification with the same wallet or with different wallet.

    Each user can verify only once and submit a valid proof one single time. Any further attempts will result in errors. This rule ensures Holonym's sybil resistance.

    What should I do if I'm facing issues with the payment page?

    If you're having trouble with the payment page, try clearing your browser's cache and restarting it, then attempt again. You can also try using a different browser.

    What should I do if verification still fails after two attempts?

    • If you've failed twice, we don’t recommend trying again unless you have a different ID and are willing to restart the entire process.

    • Even if you try again with a different ID, successful verification is not guaranteed, as it is still subject to the provider's approval.

    How to unlock more Discord channels and roles?

    💡 Roles and Permissions: Some channels require specific roles to access. Make sure you have the right roles to unlock more content.

    1. For Token-Gated Roles: Go to the ✅│verify-holo channel and connect your wallet to Collab.Land.

    Integrating Human ID

    What you can do with Human ID and how to start

    While Holonym can be used off-chain it is by far most commonly used on-chain. We use the term Soul-Bound Token (SBT) to refer to an on-chain record linked to a user's address. To use Holonym, you simply must direct the user to get their SBT, then you can read the SBT from from our API in one line of code, or from the blockchain itself.

    1. Send a user to Holonym to get an SBT

    Option A: Via Link or Redirect

    You may send the user to

    credentialType is one of:

    • gov-id (for government ID credentials).

    • clean-hands (for sanctions checks).

    • phone (for phone number credentials).

    ePassport

    Users can also verify for free using an NFC-enabled government ID document. Please see for user instructions.

    Note that the ePassport SBT is distinct from the gov-id SBT. A user can get both an ePassport SBT and a gov-id (KYC) SBT.

    Option B: Via Human ID SDK

    To directly embed Human ID into your website, you can import the and call

    where credentialType is either 'kyc' (for government ID credentials), 'clean-hands', 'phone' (for phone number credentials) or 'biometrics'. This will prompt the user to complete the SBT minting flow.

    2. Read a user's SBT

    Option A: Holonym API

    To check whether a user has a certain SBT, you can query the Holonym API. The JavaScript example shows how to call the Holonym API (which calls the SBT smart contract) to get whether the user is unique for the given action ID. (Use the default action ID of 123456789.)

    Option B (DEPRECATED): On chain

    You can call the SBT contract directly. The Solidity example gives anyone 1 gwei who has proven they're from the US. (See more examples in the .)

    You may have noticed one person can claim the gwei many times in the Solidity example! This an example; please do not implement a US stimulus program from it.

    Note: by default, proofs can only be done once-per-ID to prevent Sybil attacks & bribery. You don't want somebody to sell their US residency proof to others. This means a user can only verify one of their addresses as being a US resident.

    Example: Sybil Resistance

    1. Send users to Holonym to get government ID uniqueness SBTs

    Send users to the following URL.

    Users will complete verification and get an SBT at the address of their choosing.

    2. Read users' uniqueness SBTs

    The below JS example checks whether an address belongs to a unique person.

    The smart contract in Solidity gives a privacy-preserving airdrop once-per-person to only unique people.

    Note: we highly recommend using the default actionID of 123456789. If you would like to explore custom actionIDs, please see . Otherwise, feel free to ignore the concept of an actionID and just use 123456789as shown in the example.

    // userCredentialsv2
    {
      "_id": {
        "$oid": "676d..."
      },
      "holoUserId": "f111...",
      "encryptedGovIdCreds": {
        "ciphertext": "0x...",
        "iv": "0x...",
        "_id": {
          "$oid": "676d..."
        }
      },
      "__v": {
        "$numberInt": "0"
      }
    }
    

    Human Wallet: A secure, noncustodial onboarding solution that resembles a wallet as a service but is self-custodial, free, and addresses security issues inherent in embedded wallets. It uses the Human Network for secure onboarding and recovery.

  • Human Passport: An identity verification application and Sybil resistance protocol with more than 2M users. It enables users to collect verifiable credentials, or Stamps, that prove their identity and trustworthiness without exposing personally identifying information. To date, Human Passport has protected over $225M in airdrop and grant funds.

  • We request deletion of the data by the third party as soon as it is successfully verified and handed back to the user. In the event document is not successfully verified, the record is logged and kept for up to 90 days to help users debug why their verification failed. It is deleted after the 90 day period.

  • We use a nullifier scheme to make it nearly impossible for the third party issuer to track the user after they verify them. This means that the user encrypts the cryptographically signed credential with a secret only they know after receiving it from the identity issuer.

  • We use a relayer to add a user’s leaf to the Merkle Tree so the verified credentials (VCs) can’t be linked to an on-chain address. The Merkle Tree is an identity mixer, or anonymity set, that the user can prove set membership and knowledge of specific attributes about from a pseudonymous ethereum address.

  • The user’s identity data is stored on the user’s local device. All ZK proofs on identity are computed on the client side, not our server.

  • We keep an encrypted backup of user data in the event that the user clears their cookies, local storage, or switched devices. The data is encrypted with the user’s ethereum key and can be requested to be deleted at any time by contacting the team.

  • Users can increase their privacy by using:

    -- a VPN network (be careful to use the same country when verifying)

    -- a privacy preserving browser like Tor -- waiting some period of time after adding their leaf to the merkle tree before minting their SBT

  • national ID

  • residence card

  • voter's ID

  • We also have expanded support for less common identity cards to meet the communities needs such as:

    • Provisional driver's licenses

    • India PAN cards

    • India Local ID cards

    • Russian internal passport

    • Nigeria voter cards

    • USA passport card

    • Swedish Identitetskort

    • Spain ID

    We do not support docs that have high likelihood of forgery such as:

    • "Federal Limits Apply" US IDs

    • Italian Refugee Cards

    • Italian Paper ID cards

    • Student ID cards

    • Nigerian National Identification Numbers

    Click "Mint SBT" to finalize the process.
  • Pay on your preferred Network. Confirm the payment on your wallet.

  • Get SBT on Optimism Network if you are verifying for Human Passport.

  • Confirm that the wallet address is correct, as you can only receive one SBT of this type and cannot transfer it to another wallet.

  • You're all set!

    Following these guidelines will help prevent verification issues.

    Check WhatsApp and Viber if SMS isn't received.

    Important Notes:

    • Burner phones and virtual numbers are NOT accepted due to security reasons, as they weaken Sybil resistance.

    • Do NOT keep retrying without following these steps—doing so may lock you out after reaching the maximum attempts, preventing verification.

    • You have a maximum of three attempts. If you fail all three times, your only option is to request a refund. Please follow the refund process.

    Once verification is complete, check your profile— the "Verify" label on your picture will change to "Verified".

  • After verification, click "Prove" to proceed with minting your SBT.

  • Discover More Channels & Roles
    : • Click “Channels & Roles” in the left sidebar. • Select “Customize” to answer a few questions and gain access to additional channels and roles. • Use “Browse Channels” to filter and join channels that match your interests.
  • View Hidden Channels: If some channels appear collapsed, click on the category name to expand and see all available channels.

  • https://github.com/holonym-foundation/holonym-relayer/blob/main/constants/contract-addresses.json
    https://optimistic.etherscan.io/
    https://silksecure.net/holonym/diff-wallet
    https://onfido.com/supported-documents
    https://www.youtube.com/watch?v=QkGi0VHvmuA
    https://silksecure.net/holonym/diff-wallet
    biometrics (for non-personally identifying face uniqueness and liveness check).
    Verifying ePassport
    Human ID SDK
    examples repo
    Custom Sybil Resistance
    https://id.human.tech/<credentialType>
    humanID.requestSBT(credentialType)
    // `action-id` is the action ID. The only action ID currently 
    // in use is 123456789.
    // `user` is the address to be checked.
    const resp = await fetch('https://api.holonym.io/sybil-resistance/gov-id/optimism?action-id=123456789&user=0x0000000000000000000000000000000000000000');
    const { result: isUnique } = await resp.json();
    // NOTE: This example is deprecated with Holonym V3.
    
    pragma solidity ^0.8.0;
    import "../interfaces/IResidencyStore.sol";
    
    // US Residents have had it hard this year! let's send 1 gwei to anyone who can prove they're from the US
    contract USResidency {
        IResidencyStore resStore;
        constructor() {
            resStore = IResidencyStore(0x7497636F5E657e1E7Ea2e851cDc8649487dF3aab); //ResidencyStore address can be found on https://github.com/holonym-foundation/app.holonym.id-frontend/blob/main/src/constants/proofContractAddresses.json
        }
    
        // NOTE: there are better ways to send ETH. Please don't copy this code
        function sendStimmy() public {
            require(resStore.usResidency(msg.sender), "You have not proven you are from the US");
            payable(msg.sender).send(1);
        }
    }
    https://id.human.tech/gov-id/kyc/issuance/prereqs
    //`user` is the address that should be checked for uniqueness
    // `action-id` should be 123456789, unless you specified a custom action ID
    const resp = await fetch('https://api.holonym.io/sybil-resistance/gov-id/optimism?user=0x0000000000000000000000000000000000000000&action-id=123456789');
    const { result: isUnique } = await resp.json();
    // NOTE: This example is deprecated with Holonym V3
    
    pragma solidity ^0.8.0;
    import "../interfaces/IAntiSybilStore.sol";
    
    /* NOTE: you can replace airdrop with any other action that needs Sybil resistance. 
    This uses an airdrop as an example, but it can be adapted for voting, play-to-earn, play-to-learn, etc.
    Example: You want to give an airdrop to all your early users. You want to make your early users get their fare share and far more than they would get if it was just bots swooping up the airdrop and dumping.
    */
    contract SybilFreeAction {
        IAntiSybilStore registeredActions; // Where actions (including the actionId we care about) are registered
        uint actionId; // The ID for this particular action. We reccomend the default ID of 123456789
        mapping (address => bool) hasClaimedAirdrop;
    
        constructor(uint actionId_) {
            registeredActions = IAntiSybilStore(0xFcA7AC96b1F8A2b8b64C6f08e993D6A85031333e); // AntiSybilStore address for different chains can be found on https://github.com/holonym-foundation/app.holonym.id-frontend/blob/main/src/constants/proofContractAddresses.json
            actionId = actionId_;
        }
    
        function claimAirdrop() public {
            require(registeredActions.isUniqueForAction(msg.sender, actionId), "You have not yet claimed this action with your Holo");
            require(!hasClaimedAirdrop[msg.sender], "You already got your airdrop!");
            hasClaimedAirdrop[msg.sender] = true;
            _giveAirdrop(msg.sender);
        }
    
        function _giveAirdrop(address recipient) private {
            // not implemented
        }
    }

    Verax Attestations

    How to read Holonym attestations on Verax

    Holonym issues a Verax attestation for every SBT it sends. The following is an example of how to query and validate Holonym attestations.

    Install

    npm i @verax-attestation-registry/verax-sdk

    Code

    In this example, we check the attestation for the address 0xdcA2e9AE8423D7B0F94D7F9FC09E698a45F3c851.

    It's important to filter for Holonym's schema ID and attester account and to validate the attestation payload. Here, we are making sure the user has a uniqueness KYC SBT.

    See for the circuit IDs, action ID, and issuers.

    const { VeraxSdk } = require('@verax-attestation-registry/verax-sdk');
    
    // Initialize Verax
    const address = '0xdcA2e9AE8423D7B0F94D7F9FC09E698a45F3c851';
    const veraxSdk = new VeraxSdk(
      VeraxSdk.DEFAULT_LINEA_MAINNET, 
      address
    );
    
    // Query attestation registry
    const attestations = await veraxSdk.attestation.findBy(
      undefined,
      undefined,
      {
        // Holonym's schemaId
        schemaId: "0x1c14fd320660a59a50eb1f795116193a59c26f2463c0705b79d8cb97aa9f419b",
        // Holonym's attester account
        attester: "0xB1f50c6C34C72346b1229e5C80587D0D659556Fd",
        // Our address of interest
        subject: "0xdcA2e9AE8423D7B0F94D7F9FC09E698a45F3c851"
      }
    );
    
    const { circuitId, publicValues, revoked } = attestations[0].decodedPayload[0];
    
    const actionId = publicValues[2].toString();
    const issuer = publicValues[4];
    
    // Make sure circuitId matches KYC circuit ID
    if (circuitId != "0x729d660e1c02e4e419745e617d643f897a538673ccf1051e093bbfa58b0a120b") {
        throw new Error("Invalid circuit ID");
    }
    
    // Validate action ID
    if (actionId != "123456789") {
        throw new Error("Invalid action ID");
    }
    
    // Make sure issuer is the KYC Holonym issuer
    if (issuer != BigInt("0x03fae82f38bf01d9799d57fdda64fad4ac44e4c2c2f16c5bf8e1873d0a3e1993")) {
        throw new Error("Invalid issuer")
    }
    here

    API Reference

    Reference for https://api.holonym.io

    Endpoints

    • GET /sybil-resistance/gov-id/<network>

    • GET /sybil-resistance/epassport/<network>

    GET /sybil-resistance/<credential-type>/<network>?user=<user-address>&action-id=<action-id>

    Get whether the user has registered for the given action-id.

    When a user "registers", they are establishing that the given blockchain address is a unique person for the action ID. See the section Sybil resistance for more information about how action IDs can be used.

    If credential-type is gov-id, this endpoint uses Holonym smart contracts to check whether the user has completed KYC with a unique government ID. If credential-type is epassport, this endpoint uses Holonym smart contracts to check whether the user has a unique NFC-enabled passport. If credential-type is phone, this endpoint uses Holonym smart contract to check whether the user has proven ownership of a unique phone number. If credential-type is biometrics, this endpoint uses Holonym smart contract to check whether the user has proven ownership of a unique (non-personally identifying) face.

    See the following documentation for how to use action IDs.

    • Parameters

      name
      description
      type
      in
      required

    GET /residence/country/us/<network>?user=<user-address>

    Get whether the user resides in the US.

    For the /residence/country/<country-code> endpoints, <country-code> will be a 2-letter country code following the . Holonym currently only supports queries for US residency.

    • Parameters

      name
      description
      type
      in
      required

    GET /snapshot-strategies/residence/country/us?network=<network>&snapshot=<snapshot>&addresses=<addresses>

    Returns a list of scores indicating, for each address, whether the address has submitted a valid and unique proof of US residency.

    Every score is either 1 or 0.

    score
    description

    Use with Snapshot

    To use with the Snapshot strategy, specify the strategy parameters using the following format.

    Use without Snapshot

    • Parameters

      name
      description
      type
      in
      required

    GET /snapshot-strategies/sybil-resistance/gov-id?network=<network>&snapshot=<snapshot>&addresses=<addresses>&action-id=<action-id>

    Returns a list of scores indicating, for each address, whether the address has submitted a valid proof of uniqueness for the given action-id.

    Every score is either 1 or 0.

    score
    description

    Use with Snapshot

    To use with the Snapshot strategy, specify the strategy parameters using the following format. We highly recommend that projects use the default action-id 123456789 to avoid cases where users sell actions associated with action-ids that they do not care about.

    Use without Snapshot

    • Parameters

      name
      description
      type
      in
      required

    GET /snapshot-strategies/sybil-resistance/phone?network=<network>&snapshot=<snapshot>&addresses=<addresses>&action-id=<action-id>

    Returns a list of scores indicating, for each address, whether the address has submitted a valid proof of uniqueness (using phone number) for the given action-id.

    Every score is either 1 or 0.

    score
    description

    Use with Snapshot

    To use with the Snapshot strategy, specify the strategy parameters using the following format. We suggest that you use the default action-id 123456789. If you are using a different action-id, replace 123456789 with your action-id.

    Use without Snapshot

    • Parameters

      name
      description
      type
      in
      required

    GET /snapshot-strategies/sybil-resistance/biometrics?network=<network>&snapshot=<snapshot>&addresses=<addresses>&action-id=<action-id>

    Returns a list of scores indicating, for each address, whether the address has submitted a valid proof of uniqueness (using non-personally identifying face vectors) for the given action-id.

    Every score is either 1 or 0.

    score
    description

    Use with Snapshot

    To use with the Snapshot strategy, specify the strategy parameters using the following format. We suggest that you use the default action-id 123456789. If you are using a different action-id, replace 123456789 with your action-id.

    Use without Snapshot

    • Parameters

      name
      description
      type
      in
      required

    network

    'optimism' or 'base-sepolia'

    string

    path

    true

    user

    User's blockchain address

    string

    query

    true

    action-id

    Action ID

    string

    query

    true

  • Example

  • Responses

    • 200

    • 200

      Result if user has not submitted a valid proof.

  • user

    User's blockchain address

    string

    query

    true

  • Example

  • Responses

    • 200

      Result if user resides in the US.

    • 200

      Result if user has not submitted a valid proof that they reside in the US.

  • snapshot

    Block height

    string

    query

    true

    addresses

    List of blockchain address separated by commas

    string

    query

    true

  • Example

  • Responses

    • 200

  • snapshot

    Block height

    string

    query

    true

    addresses

    List of blockchain address separated by commas

    string

    query

    true

  • Example

  • Responses

    • 200

  • snapshot

    Block height

    string

    query

    true

    addresses

    List of blockchain address separated by commas

    string

    query

    true

  • Example

  • Responses

    • 200

  • snapshot

    Block height

    string

    query

    true

    addresses

    List of blockchain address separated by commas

    string

    query

    true

  • Example

  • Responses

    • 200

  • credential-type

    'gov-id' or 'phone' or 'biometrics'

    string

    path

    network

    'optimism' or 'optimism-goerli'

    string

    path

    1

    Address has proven US residency

    0

    Address has not proven US residency

    network

    Chain ID

    string

    query

    1

    Address has proven uniqueness for action-id

    0

    Address has not proven uniqueness for action-id

    network

    Chain ID

    string

    query

    1

    Address has proven uniqueness for action-id

    0

    Address has not proven uniqueness for action-id

    network

    Chain ID

    string

    query

    1

    Address has proven uniqueness for action-id

    0

    Address has not proven uniqueness for action-id

    network

    Chain ID

    string

    query

    GET /sybil-resistance/phone/<network>
    GET /sybil-resistance/biometrics/<network>
    GET /residence/country/us/<network>
    GET /snapshot-strategies/residence/country/us
    GET /snapshot-strategies/sybil-resistance/gov-id
    GET /snapshot-strategies/sybil-resistance/phone
    GET /snapshot-strategies/sybil-resistance/biometrics
    How to get user's proofs
    ISO 3166 standard
    "api"
    "api"
    "api"
    "api"

    true

    true

    true

    true

    true

    true

    const resp = await fetch('https://api.holonym.io/sybil-resistance/gov-id/optimism?user=0x0000000000000000000000000000000000000000&action-id=123456789');
    const { result: isUnique } = await resp.json();
    {
        "result": true,
    }
    {
        "result": false,
    }
    const resp = await fetch('https://api.holonym.io/residence/country/us/optimism?user=0x0000000000000000000000000000000000000000');
    const { result: isUSResident } = await resp.json();
    {
        "result": true,
    }
    {
        "result": false,
    }
    const resp = await fetch('https://api.holonym.io/snapshot-strategies/residence/country/us?network=420&snapshot=9001&addresses=0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001');
    const data = await resp.json();
    {
      "score" : [
          {
            "address" : "0x0000000000000000000000000000000000000000",
            "score" : 0
          },
          {
            "address" : "0x0000000000000000000000000000000000000001",
            "score" : 1
          }
      ]
    }
    const resp = await fetch('https://api.holonym.io/snapshot-strategies/sybil-resistance/gov-id?network=420&snapshot=9001&addresses=0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001&action-id=123');
    const data = await resp.json();
    {
      "score" : [
          {
            "address" : "0x0000000000000000000000000000000000000000",
            "score" : 0
          },
          {
            "address" : "0x0000000000000000000000000000000000000001",
            "score" : 1
          }
      ]
    }
    const resp = await fetch('https://api.holonym.io/snapshot-strategies/sybil-resistance/phone?network=420&snapshot=9001&addresses=0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001&action-id=123');
    const data = await resp.json();
    {
      "score" : [
          {
            "address" : "0x0000000000000000000000000000000000000000",
            "score" : 0
          },
          {
            "address" : "0x0000000000000000000000000000000000000001",
            "score" : 1
          }
      ]
    }
    const resp = await fetch('https://api.holonym.io/snapshot-strategies/sybil-resistance/biometrics?network=420&snapshot=9001&addresses=0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000001&action-id=123');
    const data = await resp.json();
    {
      "score" : [
          {
            "address" : "0x0000000000000000000000000000000000000000",
            "score" : 0
          },
          {
            "address" : "0x0000000000000000000000000000000000000001",
            "score" : 1
          }
      ]
    }
    {
      "api": "https://api.holonym.io",
      "symbol": "",
      "decimals": 0,
      "strategy": "snapshot-strategies/residence/country/us"
    }
    {
      "api": "https://api.holonym.io",
      "symbol": "",
      "decimals": 0,
      "strategy": "snapshot-strategies/sybil-resistance/gov-id",
      "additionalParameters": "action-id=123456789"
    }
    {
      "api": "https://api.holonym.io",
      "symbol": "",
      "decimals": 0,
      "strategy": "snapshot-strategies/sybil-resistance/phone",
      "additionalParameters": "action-id=123456789"
    }
    {
      "api": "https://api.holonym.io",
      "symbol": "",
      "decimals": 0,
      "strategy": "snapshot-strategies/sybil-resistance/biometrics",
      "additionalParameters": "action-id=123456789"
    }
    Government ID Issuer

    Credentials

    Credentials are signed attestations by an issuer. While it supports legacy credential types, Human ID has a standard format for such credentials designed for simplicity and efficiency in a ZKP.

    Human ID Standard Credential Format

    A leaf is an element of a Merkle tree. Merkle trees allow for efficient and anonymous proofs of set membership in zero-knowledge. Leaves are Poseidon hashes of 6 254-bit field elements:

    Index
    Name
    Description

    Note that while credentials can only have 2 issuer- fields, nothing prevents these fields from representing more: they can be set to the hash of unlimited custom fields

    Nullifier Secret

    The nullifier secret is a value that only the user knows. Because it is pseudorandom, the preimage of the leaf cannot be guessed without at least brute-forcing the secret, which is infeasible at 16 bytes. Thus, even if the other fields are predictable, the credential will still be anonymous without knowing the secret. In this way, it acts as a pepper, obscuring the preimage from the publicly known digest.

    It also has a particular property allowing for sybil resistance: credentials can be spent for a specific use case by publishing a salted hash of the secret pepper. We refer to this as a hashbrown because it is a mixture of salt & pepper_._ Accurate computation of the hashbrown can be verified in a ZKP without revealing the pepper. Some use cases of involve anonymous reputation, anonymous voting, bot prevention, and fair airdrops. For example

    Whenever a user votes in an election with actionID abc123, they must publish hash(abc123, secret) . And they generate a proof that the result came from abc123 and secret . This hash then gets added on-chain, so the user can never vote again without people seeing they're trying to "double-spend" their secret.

    Leaf Formats of Holonym Foundation Issuers

    Holonym Foundation provides two issuers, though others are encouraged to create more issuers.

    Government ID Issuer

    Index
    Name
    Description

    Phone Issuer

    Index
    Name
    Description

    Biometrics Issuer

    Index
    Name
    Description

    4

    Time of credential issuance

    Represented as seconds since 1900 (unix timestamp with -70yr offset)

    5

    Scope

    0

    4

    iat

    Day the credential was issued at, in days since 1900/01/01

    5

    Scope

    0

    4

    iat

    Day the credential was issued at, in days since 1900/01/01

    5

    Scope

    0

    0

    Issuer Address

    The Ethereum address of the credential issuer, i.e. who provided this attestation

    1

    Nullifier Secret

    A secret used to create nullifiers

    2

    Issuer-defined field

    Can be used for anything the issuer wants

    3

    Issuer-defined field

    Can be used for anything the issuer wants

    4

    iat

    Unix timestamp in seconds when the credential was issued at

    5

    Scope

    Default to 0 (wildcard) indicating it can be used anywhere. This field is mostly deprecated and is not used anywhere.

    0

    Issuer Address

    The Ethereum address of the credential issuer, i.e. who provided this attestation

    1

    Nullifier Secret

    A secret used to create nullifiers

    2

    Country

    This is a number corresponding to the user's country. Countries are numbered based on a custom accumulator scheme. This allows them to be used in efficient ZK arguments of presence in allowlists of countries.

    3

    Additional Info

    0

    Issuer Address

    The Ethereum address of the credential issuer, i.e. who provided this attestation

    1

    Nullifier Secret

    A secret used to create nullifiers

    2

    Phone Number

    Phone number in E.164 but without the leading + so it can be converted to a field element. E.g., +15555555555 becomes 15555555555

    3

    Empty

    0

    Issuer Address

    The Ethereum address of the credential issuer, i.e. who provided this attestation

    1

    Nullifier Secret

    A secret used to create nullifiers

    2

    Group Name

    1 (this maybe incremented)

    3

    Reference Hash

    Hash of the reference id

    Poseidon hash of: Name, city, state/province, street, zipcode

    0