# Welcome

\ <br>

![](/files/CzLmobFhegEseREgG9qG)

\ <br>

## 🧳 Introduction

The ZKSAFE development documentation provides zero-knowledge proof, smart contracts, and API overview

Here you will learn how we use zero knowledge proof for security, and how to connect with ZKSAFE infrastructure to build a secure Web3 world together

\ <br>

## 📦️ Modularization

We divide the product into 4 modules, which can operate independently, or can combine with each other, or combine with products of other project with detailed docking documents. Our cooperation objective is **to help our partners get rich !**<br>

### ZKSAFE

If private key is stolen, Safebox is still safe<br>

### ZKSAFE Password

The password you truly owned by yourself for the first time<br>

### ZKSAFE Wallet (Researching)

Based on 4337 wallets to realize convenience, security, gas-saving, and most importantly, to help user to make money<br>


# Intro

## ZKSAFE

We created a kind of Safebox with [password](/zkpass/zkpass)+private key to open, **even if the private key is stolen, the asset remains safe**

Users can have their own Safebox contracts, which can be understood as their own private banks. You can retrieve your assets even if you lost your private key and password by social recovery

You only need to install ZKSAFE extension, and no hard wallet is needed\ <br>

## Safebox and Wallet

We don’t save a large amount of money on gift card or bus pass in real life but small changes, but big money in the bank, same in the crypto world:

* Small money into hot wallet, which can be used for transferring and DEFI investment
* Large money into the Safebox, since safety first

ZKSAFE is a security partner of wallet. Take MetaMask as an example:

* MetaMask deal with your private key
* ZKSAFE deal with your password

ZKSAFE doesn’t store your private key or password, the withdrawal procedure as follows:

![](/files/DEeoHOqutgb1gqFXBmjx)

ZKSAFE confirmation box pops up and password is needed when withdrawing, and then ZK-SNARK Proof will be calculated by your computer through your password, and MetaMask confirmation box pops, to sign with your private key<br>

### What’s the differences between ZKSAFE password and MetaMask password

First, there are two completely different systems, MetaMask does not store your private key directly but the certificate of your private key. The password you enter when you open MetaMask is the password of the certificate for exporting the private key. If the certificate is lost (computer reinstall), the private key cannot be exported from the password and the asset cannot be withdrawn. If the private key is stolen, the hacker can steal the assets. Certificate + password are used to prevent the private key from being directly stored and hacked by the Trojan horse

Password ZKSAFE used is called [ZKSAFE Password](/zkpass/zkpass), which is another password for your account. This password is stored in smart contract, encrypted by Zero-knowledge proof, and no one can change your password but only yourself. Password is always online, and valid even if you changed another computer; Even if ZKSAFE is out of service, the password is still valid, and ZKSAFE Password will be valid as long as Ethereum exists. If the private key is stolen, the hacker cannot steal the assets without knowing the password<br>

### Where the assets are stored

See diagram as follows:

![](/files/UcApGyGfQHpiKXNc3OVr)

Wallet owns assets, each wallet can have its own ZKSAFE contract

Assets can be stored in the wallet and also ZKSAFE contract. The following 3 conditions need to be met when withdrawing assets from ZKSAFE contract:

1. ZKSAFE contract can be called only by it's owner (wallet)
2. Correct password
3. No approve problem, every withdrawral leads to it's owner wallet

These guaranteed:

1. No assets pool. DEFI usually put their users’ assets into an asset pool, therefore, all assets are stolen once the assets pool is hacked
2. Hacker can't steal your money even if he has your private key
3. No authorization and wrong transfers because all the assets can be only transferred into your wallet

\ <br>

## Security

To use ZKSAFE to protect your assets safety as early as you can

### Asset Security

The assets safety of ZKSAFE are with the following 3 possibilities:

1. Private key is hacked, password is safe, and your assets are safe
2. Password is cracked, private key is safe, and your assets are safe
3. Password is cracked, private key is hacked, your assets are not safe

> **Tips**: it’s suggested to write down your password on paper rather than on cell phone or computer, do not enter the password outside ZKSAFE.

<br>

### Password Security

ZKSAFE doesn't store your password, can't change your password either, you can set Social Recovery before password is lost

ZKSAFE extension can be called if the partner wants to verify the user's password. After ZKSAFE verification, all parameters generated by the password (excluding the password) will be returned to the partner's website. The password will not be shown anywhere to ensure security

> **Note**: Too simple a password like a 6 chars can be cracked in 9 days, 8 chars password now take decades for cracking, but it may take less time in the future as computer performance improves; So password of more than 12 characters (case sensitive letters+number+symbol) are recommended. We will upgrade password algorithm to ensure the security of the 12 chars password

<br>

### Social Recovery

If you forget the password or private key, you can use Social Recovery by initiating multi-signing (no password needed)

![](/files/HXRlh9GDv9rKxq3Ys3xz)

Once The ownership of the safebox be transferred, the password and private key are replaced

![](/files/wNruGDBqFA5G6kUDeYM3)

Guardians can be your trusted relatives or friends, or your own wallets. For security, it’s better not login all guardians’ accounts on one device

Guardian can also be Gnosis-safe Multi-sign wallets, which is in our plan

If you feel your private key or password has been exposed, you should transfer your Safebox to a new wallet

Fee is needed for transferring the Safebox

> **Reminding**: ZK-SNARK is still need time for testing, it’s strongly suggested that every user could set the Social Recovery


# Tutorial

## About ZKSAFE

ZKSAFE is an open source, free, protocol-level security product that uses on-chain password + private key multiple signing to protect assets:

* Private key is hacked, password is safe, and your assets are safe
* Password is cracked, private key is safe, and your assets are safe
* Password is cracked, private key is hacked, your assets are not safe

You need to install the ZKSAFE Dapp and MetaMask Dapp, one for the password and the other for the private key

There are three core functions:

1. Set password
2. Deposit/withdraw tokens
3. Social Recovery

ZKSAFE is protocol level products with no backend, no private key hosting, and no administrator

ZKSAFE is a security partner of your wallet and your personal bank. You can save your exchanges in Wallet, large funds in ZKSAFE, and transfer them from ZKSAFE to wallet because safety first

In one word: **with ZKSAFE, even if the private key is stolen, the asset remains safe**\ <br>

### Tutorial

Open Dapp <https://app.zksafe.pro/>

* Click on `Download` button to download ZKSAFE Extension in google chrome APP store, `Connected` will replace the `Download` button after connecting ZKSAFE Dapp
* Click on `Connect Wallet` to connect MetaMask<br>

![](/files/y5JV9iABbIZrDaGt0tnX)

<br>

New users will need to Activate safebox first, click the `Activate` button, and the `confirm` button when MetaMask confirmation box popped up, to deploy your proprietary Safebox smart contracts. And then `Safebox Address` will appear, which is the deployed contract address by ZKSAFE, You can transfer Token and NFT to this address, only you can transfer them out<br>

![](/files/s0LD5Csqor1r5mv6Bl9Q)

<br>

You must have on-chain password to withdraw the asset. Click the `SET` button and create your on-chain password in the ZKSAFE Extension pop-up. Wait a few seconds (depending on your computer performance), then click the `Confirm` button in the MetaMask confirmation box that popped up

![](/files/G2m3s2FUWyJUBkOz57Ru) ![](/files/0VH8zBmAuntaMd9sgFh5)<br>

`Owner Address` is your wallet address and you can transfer out your safebox assets from this address after depoyed on-chain

> One wallet can only create one safebox, and vice versa
>
> The on-chain password s not the safebox’s password, but the wallet's. The safebox can be transferred to another wallet through social recovery, to transfer all the assets in the safebox (see Social Recovery for details)

\ <br>

### Deposit & Withdrawal

You can transfer assets between the safebox and wallet after safebox is active and password is set

#### Deposit

Click the `green arrow` button, enter the tokens `amount` in the pop-up box, click `Confirm` button after MetaMask confirmation box popped up, then waiting for on-chain process

You can also transfer your tokens to your `Safebox Address` from another wallet<br>

![](/files/LZHohX7kk6h4pJplxuW7)

#### Withdrawal

Click the `orange arrow` button, enter the token `amount` in the pop-up box, click `Confirm` button, then enter the `password` when ZKSAFE confirmation box popped up, click `Confirm` after MetaMask confirmation box popped up wait for on chain data

Due to security restrictions, you cannot transfer your assets to any address but only `Owner address`, to avoid wrong operations<br>

![](/files/UfnkNkMALoo26UJ6nL28)

\
ZKSAFE supports the transfer of NFT（ERC721）besides tokens (ERC20), other asset types (such as ERC1155) are not supported for now\ <br>

### Social Recovery

If you forget the password or private key, you can transfer ownership of the safebox to your other new wallet, so you can use the new wallet's on-chain password + private key to withdraw the assets

![](/files/HXRlh9GDv9rKxq3Ys3xz) ![](/files/wNruGDBqFA5G6kUDeYM3)

<br>

There are 2 ways for transferring the ownership of the safebox:

1. Set multi-signing wallet ahead, which can be your cold wallet or your friend’s wallet, to initiate multi-signing
2. Use your on-chain password and your private key

The original wallet will be invalid after your transferring the ownership of the safebox to new wallet

> **Reminding**：ZK-SNARK is still need time for testing, and we could exclude the possibility of assets loss caused by password issues, so it’s strongly suggested that each user can set the multi-signing

\ <br>


# Contract

## ZKSAFE Contract Description

### Preparations

Required Node.js v16, install [snarkjs](https://github.com/iden3/snarkjs)

```javascript
npm install -g snarkjs
```

Install [ethers](https://docs.ethers.io/v5/getting-started/), you need to know how to use ethers, all the code examples bellow assumed you know it:

```javascript
npm install ethers
```

[Contract source code](https://github.com/ZKSAFE/all-contracts/tree/main/contracts/zkSafe)

[Testing code](https://github.com/ZKSAFE/all-contracts/blob/main/test/Safebox-withdraw.js)

> Note：The test environment is hardhat. ethers is used slightly differently than the formal environment. The following code is based on the test environment

Proof used in contract interface is the ZKSAFE Password based on ZK-SNARK, please refer to [ZKSAFE Password build](/zkpass/build)

\ <br>

### SafeboxFactory Contract

The factory contract in ZKSAFE to manage the Safebox contracts<br>

#### createSafebox() Activate Safebox

First, you need to call createSafebox(), which will deploy your own Safebox contract, called activation

One wallet can only activate one Safebox. If you have already activated it, an error will be reported if you activate it again. Unless you transferred your Safebox ownership, then you can activate another one

The contract address of the Safebox is determined by your address and your activating times. Your Safebox address is the same regardless of which blockchain and can be imported. If you transfer your safebox ownership, the Safebox address will be different when you reactivate it<br>

#### getSafeboxAddr() derive the Safebox address

With or without activation, you can derive the Safebox address for a particular wallet

The format of the Safebox address is the same as that of the wallet address. You can directly transfer the ERC20 or ERC721 assets into Safebox address. regardless of whether it is activated or not, and withdraw them after activated

> Note: Only native tokens (ETH), ERC20 (Token), and ERC721 (NFT) can be transferred. Other types of assets (such as ERC1155) are not supported

However, it’s suggested to activate the Safebox before transferring your assets in<br>

#### userToSafebox\[] check the activated safebox address

Check the activated safebox address of a wallet

if it’s inactivated, return 0x00<br>

#### changeSafeboxOwner() transfer Safebox's ownership

only for Safebox contract call, and it can’t be transferred if the new owner already have one\ <br>

### Safebox Contract - Normal Operation

Every user can deploy an exclusive Safebox contract for himself/herself using SafeboxFactory<br>

#### owner() check the owner of the Safebox

Only owner can call the Safebox contract except the ownership is obtained by Social Recovery<br>

#### transferOwnership() transfer Safebox

Password required, only owner can call

After the transfer, the password will also be reset, which means that the new owner owns the safebox and all the assets in it<br>

#### withdrawETH() withdraw native token

Password required, only owner can call

Withdrawal amount is datahash when generating proof<br>

#### withdrawERC20() 提取ERC20代币

Password required, only owner can call

Keccak256 hash for Token address and amount is datahash when generating proof<br>

#### withdrawERC721() 提取ERC721标准NFT

Password required, only owner can call

keccak256 hash for NFT address and tokenId is datahash when generating proof\ <br>

### Safebox Contract - Social Recovery

It’s suggested to set guardian in advance in case you need it under the following

Possible situation:

* Forget private key
* Forget ZASAFE password
* ZKSAFE password invalid (0 chance under 200,000 pressure testing)
* Private key been hacked (hacker control the owner and transfer all gas out, so owner has no gas to do anything but the guardians)<br>

#### setSocialRecover() set guardians

Password required, only owner can call

Keccak256 hash for all guardians address and valid guardians number is datahash when generating proof

If you set 5 guardians, all the 5 address will be needed in \_guardians, valid gurdians number \_needGuardiansNum can be 1-5, means \_needGuardiansNum of 5 can multi-signing to realize social recovery. Usually multi-signing is set as 2 of 3, which means 2 of the 3 gurdians can do the social recovery<br>

#### getSocialRecover() check gurdians

Return 3 fields:

* guardians: the preset guardians’ addresses
* needGuardiansNum: preset valid guardian number
* doneGuardians: guardian initiated multi-signing, social recovery will be done when reach the needGuardiansNum<br>

#### transferOwnership2() transfer Safebox by gurdians

Only guardians can call

The Guardians need to specify an address to transfer, it can be any address, but need to be consistent. If not, the whole process will go over again until it’s consistent

After the Safebox is transferred, the guardian remains in effect<br>


# Intro

## ZKSAFE Password

Have you ever thought that the administrator can change your password, and you never have your own password actually

That’s why we want to design such a cryptosystem that should satisfy:

* No downtime
* Password not be exposed
* Only you can change your own password

It can be realized in EVM smart contract but the new issue: Sandwich Attack<br>

### Sandwich Attack

Let's say you have submitted a tx of a withdrawal with your password verification information. Supposed a hacker copied the tx when loading and processing because the tx is open, and changed withdrawal address to his own address, because no dynamic password authentication information, so the verification passed in the contract, then he submitted with higher gas, it can be processed before you, and take your money

Therefore, it also should be with

* Signing Feature

<br>

### Signing Feature

When you submit the withdrawal tx, the tx should sign the withdrawal information by password. if the tx information is tampered, it can be checked in the contract, so as to prevent the sandwich attack

Only the private key can sign the data in traditional algorithm. We unexpectedly found that ZK-SNARK can do some circuit programming and realize the password to sign the data. After many times of questioning and algorithm adjustment, we finally made it !

<br>

### Origin

The first product created with this system is **ZKSAFE**, and it works great !

We then decided that such a great cryptography system should be expanded to support not only ZKSAFE, but also various asset management platforms, and even private key-less wallets (which would greatly reduce the barrier for users to entry Web3). And this password system is

**ZKSAFE Password (abbr.ZKPass)**

**Your own password, you deserve it !**<br>

### Competitor MPC

ZKSAFE Password（abbr.ZKPass）does not use the MPC (private key sharding) scheme. Here we would like to give a comparison since there are many people ask this:

* MPC divides the private key into pieces (shards) and distributes it to multiple nodes
  * It with centralization risk because the node can be attacked which can lead the loss of the private keys
  * The password is useless when private key is stolen
* ZKPASS is pure algorithm to achieve the password function with no node
  * Decentralized, stores no private keys or passwords
  * Even private key is stolen, the password still works

MPC is good as the private key custody plan. And ZKPass is protocol-level, non-custody password algorithm<br>


# Tutorial

## ZKSAFE Password Intro

ZKPass (ZKSAFE Password abbr.ZKPass) has 2 features:

1. Set Password
2. Verify signing

In this case, signing verifying is for another project’s contract, such as the ZKSAFE contract. Setting passwords is also implemented in the ZKSAFE plugin. To connect with more projects, an ZKPass website was developed where ZKPass is an independent project, so partners can better understand how ZKPass operates at the protocol level rather than relying on the ZKSAFE extension.

The password of ZKPass is the same as ZKSAFE

In short, ZKPass is to B and ZKSAFE is to C

\ <br>

### Tutorial

Open <https://password.zksafe.pro/> , click`Connect Wallet`button to connect MetaMask<br>

![](/files/caqhDvEWyWt7KiP7M8Rj)

<br>

Enter `password` twice, then click the `Set Password` button, the computer will run the ZK calculation, wait a few seconds (depending on the performance of your computer). After the computer finishes the ZK calculation, the MetaMask confirmation box will pop up

<br>

![](/files/Q4RsgHDc1LE4DtfjRfOD)

<br>

If you have set a password, you will see the reset page and view your recent password setting records<br>

![](/files/zeY7l9gRSp2CdwUlN0BS)

\
Old password is required for resetting new one\ <br>

### About Password (Very important)

Here's what you need to know about passwords. Please remember them carefully

* ZKPass does not store your password, and no one knows your password except yourself
* ZKPass has no administrator, no one can help you retrieve or reset your password
* Don't forget your password. Don't save in computer or phone. Write it down on paper

There's nothing we can do to stop the password-cracking, about the password strength

* 6 characters can be cracked in 10 days, don't make it that short
* 8 characters may take only months in the future as computer performance improves;
* 12 characters are recommended, which can be a short sentence + a number, such as *2022IHaveADreamToday*

\ <br>


# How It Works

## How ZKSAFE Password(ZKPass) works

ZKPass saves `pwdhash` hash of the password in the contract. ENS is to bind a `name` to an address, our ZKPass tries to bind `pwdhash` to an address.

<br>

![](/files/tRODDYY6fTeNPcwIHoNo)

<br>

We will sign **User actions** with Keccak256 to produce `datahash`. With `expiration`, `chainId`, an auto-incrementing `nonce` and the `datahash`, we will sign them again with Keccak256 to produce `fullhash`. `nonce` is used to avoid the multiple submission attempts.

In a ZK circuit，we use Poseidon algorithm to produce hash. The algorithm is chose as it requires a low gas fee.

The circuit is shown below.

```javascript
pragma circom 2.0.0;

include "../../node_modules/circomlib/circuits/poseidon.circom";

template Main() {
    signal input in[3];
    signal output out[3];

    component poseidon1 = Poseidon(2);
    component poseidon2 = Poseidon(2);

    poseidon1.inputs[0] <== in[0];  //pwd
    poseidon1.inputs[1] <== in[1];  //address
    out[0] <== poseidon1.out; //pwdhash

    poseidon2.inputs[0] <== poseidon1.out;
    poseidon2.inputs[1] <== in[2]; //fullhash
    out[1] <== in[2]; //fullhash
    out[2] <== poseidon2.out; //allhash
}

component main = Main();
```

The chart below shows the logic.

<br>

![](/files/gIafrNjY0pqxR5xN1nHQ)

<br>

`password` and `address` are first combined to produce `pwdhash`. With the use of `address`, it guarantees the `pwdhash` will be different even if the passwords are the same.

`pwdhash` and `fullhash` give us `allhash`. It covers all the user actions we want to verify.

At last, `proof` will show `allhash`, `pwdhash` and `fullhash` are all generated via the circuit. Without know what `password` is, we know that `pwdhash` is produced by `password`. As well as `allhash` and `fullhash`, we can be sure that only people knowing what the `password` is produce the hashes and give the proof for them.

`fullhash` as the output will prevent any unwanted modification.

> Poseidon requires 254 bits input data, but Keccak256 consume 256 bits. We will `fullhash` right shift 3 bits.

<br>

### Notes

ZKPass system only provide password change action for the user. We can verify the password with `pwdhash` off chain. On chain, ZKPass can be used as signature verification. For example, in ZKSAFE contract, ZKSAFE will use **user actions** to produce `datahash`. It will be passed to ZKPass for verification. After the **user actions** being verified, ZKSAFE knows whoever knows the password wants to perform these **user actions**.<br>


# Build

## ZKSAFE Password Docking

### Preparations

Required Node.js v16，install [snarkjs](https://github.com/iden3/snarkjs)

```javascript
npm install -g snarkjs
```

install [ethers](https://docs.ethers.io/v5/getting-started/), you need to know how to use ethers, all the code examples bellow assumed you know how to use ethers

```javascript
npm install ethers
```

[Contract source code](https://github.com/ZKSAFE/all-contracts/tree/main/contracts/zkpass)

[Testing code](https://github.com/ZKSAFE/all-contracts/blob/main/test/ZKPass-test.js)

> Note: The test environment is hardhat. ethers is used slightly differently than the formal environment. The following code is based on the test environment

We suggested don't enter password outside ZKPass and ZKSAFE, to prevent password leakage. ZKPass *(short of ZKSAFE Password)* contracts are open to partner contracts, such as ZKSAFE

<br>

### resetPassword() reset password

Initializing password and changing password are the same interface. Let's start with the util function `getProof()` that all ZK use

#### Util Function

```javascript
//util
async function getProof(pwd, address, nonce, datahash) {
    let expiration = parseInt(Date.now() / 1000 + 600)
    let chainId = (await provider.getNetwork()).chainId
    let fullhash = utils.solidityKeccak256(['uint256','uint256','uint256','uint256'], [expiration, chainId, nonce, datahash])
    fullhash = s(b(fullhash).div(8)) //fullhash must 254b, solidityKeccak256 is 256b, so it need convert

    let input = [stringToHex(pwd), address, fullhash]
    let data = await snarkjs.groth16.fullProve({in:input}, "./zk/v1/circuit_js/circuit.wasm", "./zk/v1/circuit_final.zkey")

    const vKey = JSON.parse(fs.readFileSync("./zk/v1/verification_key.json"))
    const res = await snarkjs.groth16.verify(vKey, data.publicSignals, data.proof)

    if (res === true) {
        console.log("Verification OK")

        let pwdhash = data.publicSignals[0]
        let fullhash = data.publicSignals[1]
        let allhash = data.publicSignals[2]

        let proof = [
            BigNumber.from(data.proof.pi_a[0]).toHexString(),
            BigNumber.from(data.proof.pi_a[1]).toHexString(),
            BigNumber.from(data.proof.pi_b[0][1]).toHexString(),
            BigNumber.from(data.proof.pi_b[0][0]).toHexString(),
            BigNumber.from(data.proof.pi_b[1][1]).toHexString(),
            BigNumber.from(data.proof.pi_b[1][0]).toHexString(),
            BigNumber.from(data.proof.pi_c[0]).toHexString(),
            BigNumber.from(data.proof.pi_c[1]).toHexString()
        ]

        return {proof, pwdhash, address, expiration, chainId, nonce, datahash, fullhash, allhash}

    } else {
        console.log("Invalid proof")
    }
}
```

For the convenience, we wrote a util function `getProof()`, wraps all of our ZK algorithms. Note that `circuit.wasm`, `circuit_final.zkey`, `verification_key.json` are fixed values that can be found in [ZK source code](https://github.com/ZKSAFE/all-contracts/tree/main/zk)

`getProof()` is the ZK Circuit in the diagram<br>

![](/files/tRODDYY6fTeNPcwIHoNo)

<br>

`getProof()` has 4 params:

* pwd: your password, string type
* address: your wallet address, string type
* nonce: obtain your nonce value from ZKPass contarct, string type
* datahash: the hash of the data you would like to sign, string type

Return all data related to ZK algorithm:

* proof: proof of ZK-SNARK, array of 8 uint256
* pwdhash: pwdhash needed in ZKPass contract, uint256 type
* address: address from params, string type
* expiration: password signing expiration seconds, int type
* chainId: chain id, int type
* nonce: nonce from params, string type
* datahash: datahash from params, string type
* fullhash: dosen’t need to upload to contract, 254 bits
* allhash: hash of all above, uint256 type<br>

#### Initialize Password

```javascript
let pwd = 'abc123' //your password
let nonce = '1' //Initialize password, nonce=1
let datahash = '0' //for resetPassword, datahash=0
let p = await getProof(pwd, accounts[0].address, nonce, datahash)

let gasLimit = await zkPass.estimateGas.resetPassword(p.proof, 0, 0, p.proof, p.pwdhash, p.expiration, p.allhash)
await zkPass.resetPassword(p.proof, 0, 0, p.proof, p.pwdhash, p.expiration, p.allhash, {gasLimit})
console.log('initPassword done')
```

`resetPassword()` has 7 params:

* proof1: proof generated by the old password, array of 8 uint256
* expiration1: old password signing expiry seconds, uint256 type
* allhash1: allhash generated by the old password, uint256 type
* proof2: proof generated by the new password, array of 8 uint 256
* pwdhash2: pwdhash of the new password generated by ZK, uint256
* expiration2: new password signing expiry seconds, uint256 type
* allhash2: allhash generated by the new password, uint256 type

Since there’s no old password for initial password, the first 3 parameters related to the old password are not required in the contract. However, they were all required to the contract (parameter as 0) or take proof2 of the new password as proof1 (as in the example)

Upon success, the password for the caller's address (msg.sender) is `pwd`

<br>

#### Reset Password

```javascript
let oldpwd = 'abc123' //old password
let nonce = await zkPass.nonceOf(accounts[0].address) //current nonce
let datahash = '0' //for resetPassword, datahash=0
let oldZkp = await getProof(oldpwd, accounts[0].address, s(nonce), datahash) //old password proof

let newpwd = '123123' //new password
let newZkp = await getProof(newpwd, accounts[0].address, s(nonce.add(1)/**new password nonce+1*/), datahash) //new password proof

await zkPass.resetPassword(oldZkp.proof, oldZkp.expiration, oldZkp.allhash, newZkp.proof, newZkp.pwdhash, newZkp.expiration, newZkp.allhash)
console.log('resetPassword done')
```

Still `resetPassword()` function, old password is required for resetting password, so the first 3 params were generated by the old password

Upon success, the password for the caller's address (msg.sender) is `newpwd`, and the `oldpwd` is invalid<br>

### verify() verify password

Password can be verified off chain by obtaining `pwdhash`, or onchain with the partner contract. The partner contract calls `ZKPAss.verify()`, if the password is incorrect, it throws an error. If no errors, the password is correct, and the signature is valid

Unsuggested to enter passwords outside ZKPass and ZKSAFE, to prevent password leakage. Partners can use ZKPass for on-chain verification

`verify()` has 5 params:

* user: the password owner, address type
* proof: from getProof(), array of 8 uint256
* datahash: the data what user signing, this is the hash of the data, uint256 type
* expiration: from getProof(), uint256 type
* allhash：from getProof()，uint256 type

The contract will use the user's pwdhash to verify the password and convert the datahash to 254 bits fullhash... In summary, the getProof() tool will process all ZK validation parameters

ZKSAFE as a partner contract to call ZKPass

```javascript
function withdrawERC20(
    uint[8] memory proof,
    address tokenAddr,
    uint amount,
    uint expiration,
    uint allhash
) external onlyOwner {
    uint datahash = uint(keccak256(abi.encodePacked(tokenAddr, amount))); //calculate datahash
    eps.verify(owner(), proof, datahash, expiration, allhash); //verify password and signing

    IERC20(tokenAddr).safeTransfer(owner(), amount); //verified！

    emit WithdrawERC20(tokenAddr, amount);
}
```

In this example, user wants to withdaw the token from ZKSAFE, so the `tokenAddr` and token `amount` needs to be signed with password

ZKSAFE off-chain code

```javascript
let pwd = 'abc123' //user’s password 
let nonce = s(await eps.nonceOf(accounts[0].address)) //user's current nonce
let tokenAddr = usdt.address //token to withdraw
let amount = s(m(40, 18)) //amount of the token to withdraw
let datahash = utils.solidityKeccak256(['address', 'uint256'], [tokenAddr, amount]) //calculate datahash
datahash = s(b(datahash)) //convert to string type
let p = await getProof(pwd, accounts[0].address, nonce, datahash) //calculate ZK Proof

await safebox.withdrawERC20(p.proof, tokenAddr, amount, p.expiration, p.allhash) //call the contract, withdraw
console.log('withdrawERC20 done')

await print()
```

`datahash` is defined by the partner, uint256 type, which is usually a hash value. There are exceptions, such as address for the signed code which is uint160 type, it fits `datahash` without Keccak256

The `datahash` calculated off chain should be consistent with the one in the partner contract


# Intro

## ZKSAFE Wallet

Still in researching, may has following features：

* Based on ERC4337
* Use ZKPass instead of private key to realize privekey-less wallet
* Integrate ZKSAFE Safebox, the assets is still safe even if the private key is hacked
* Lower gas fee
* Integrate Dapp store, users play in the crypto world freely and safely


# Deployed

### Alpha

#### Polygon

PasswordService deployed: 0x2CB213127Fa481E7D9303bedB0Bd3FC3461D2a9A

SafeboxFactory deployed: 0xB53CB1feEbea105C30982e7f2Ed803a2195DA922

<br>

### Beta

#### Polygon

EthereumPasswordService deployed: 0x555DE00394cEBb92f49e9DC4399372c81F5360e4

SafeboxFactory deployed: 0xEF6b7A04BF73f8674b5B7BcDd460778862dd5b90

<br>

### Beta 2nd

#### Polygon

ZKPass deployed: 0x72f3E7DdAe7f5B8859a230FE00f4214d582622fF

SafeboxFactory deployed: 0xd9403569f3447121eb78d426Bb5eFC7D10316b50

<br>

### V1 [(SlowMist Audit Report)](https://github.com/ZKSAFE/zksafe-docs/blob/en/images/SlowMistAuditReport-ZKSAFE.pdf)

#### Ethereum\BSC\Optimism\Arbitrum\Polygon\\

ZKPass deployed: 0x9802cBf6480FE2a0c69740Bc8008739DfF1E7CEF

SafeboxFactory deployed: 0x8528d5a340Bef2e50844CDABdFa21bC6B57c3982


# 欢迎

\ <br>

![](/files/CzLmobFhegEseREgG9qG)

\ <br>

## 🧳 开始

ZKSAFE开发文档提供零知识证明、智能合约、API的概述

在这里，你可以了解到我们是如何用零知识证明来做安全，以及，如何对接ZKSAFE的基础设施，一起构筑安全的Web3世界

\ <br>

## 📦️ 模块化

我们将产品拆分为4个模块，模块可以独立运行，可以互相组合，还可以和别人家的产品组合，有详细的对接文档，我们的合作宗旨是：**帮助别人发财！**<br>

### ZKSAFE

即使私钥被盗，资产依然安全<br>

### ZKSAFE Password

协议级的密码方案<br>

### ZKSAFE Wallet (探索中)

基于ERC4337，既要便利，又要安全，还要省gas，最重要的是：能帮用户赚钱<br>


# 介绍

## ZKSAFE

我们创建了一种要用[密码](/zh/zkpass/zkpass)+私钥才能打开的保险箱，**即使私钥被盗，资产依然安全**

每个用户都可以拥有一个自己的保险箱合约，它是你的私人银行，只为你一个人服务，如果你的私钥和密码忘记了，它可以通过社交恢复帮你重置

无需购买硬件，只需安装ZKSAFE浏览器插件，点击\[这里]领取你的私人银行\ <br>

## 保险箱和钱包

生活中，我们不会把太多的钱放购物卡/公交卡，只把零钱放进去，而大部分的钱都是存银行，跟这个现实类似：

少部分钱，即热资产存钱包，可以去转账，去DEFI

大部分钱，即冷资产存保险箱，安全第一

ZKSAFE插件是钱包的安全伴侣，以MetaMask为例

* MetaMask 处理你的私钥
* ZKSAFE 处理你的密码

ZKSAFE不存储用户的私钥，也不存储用户的密码，提款流程见下图

![](/files/DEeoHOqutgb1gqFXBmjx)

在提款的时候，先弹出ZKSAFE确认框，输入密码，ZKSAFE通过你的密码计算出ZK-SNARK Proof，并调出MetaMask确认框，通过MetaMask进行私钥签名上链<br>

### ZKSAFE的密码和MetaMask的密码有什么区别

完全不同的体系，MetaMask不直接存储你的私钥，而是存储了你私钥的证书，打开MetaMask时候输入的密码，其实是证书的密码，目的是导出私钥。如果证书丢失（比如重装系统），密码就导不出私钥，资产就取不出来；如果私钥被盗，黑客不需要密码，也能盗走资产。证书+密码是用来避免直接存储私钥，从而避免私钥被木马盗取

ZKSAFE用的密码是[ZKSAFE Password](/zh/zkpass/zkpass)，是你账户的另一个密码。这个密码存储在智能合约里，通过零知识证明加密，除了你自己，没人能改你的密码。密码永远在线，你是换了台电脑，密码依然有效；即使EPS倒闭，密码依然有效；只要以太坊不倒，[ZKSAFE Password](/zh/zkpass/zkpass)不倒。如果私钥被盗，黑客不知道密码，也就盗不走资产<br>

### 资产存放在哪里

如图所示

![](/files/UcApGyGfQHpiKXNc3OVr)

钱包可以拥有资产，每个钱包也可以拥有一个自己的ZKSAFE合约。

资产可以放在钱包，用私钥就可以转移；资产可以存到自己的ZKSAFE合约，也可以存到别人的ZKSAFE合约。从ZKSAFE合约取出资产需要同时满足3点：

1. 用户（钱包）只能调用自己的ZKSAFE合约，不能调用别人的ZKSAFE合约
2. 输入正确的EPS密码
3. 不能提到任意地址，只能提到用户自己的钱包地址

这保证了：

1. 没有资金池，DEFI通常把大家的钱都放一个资金池里，只要资金池被盗，所有人都被盗
2. 私钥被盗，没有密码，黑客也取不出钱
3. 没有授权或转错之类的问题，每一笔转出都是转到用户自己的钱包

\ <br>

## 安全性

正确的使用ZKSAFE才能保护你的资产安全，不要出事了才跑来看这章

### 资产安全性

ZKSAFE的资产安全性有以下3种可能：

1. 私钥泄漏，密码不泄漏，资产安全
2. 密码泄漏，私钥不泄漏，资产安全
3. 私钥泄漏，密码也泄漏，资产不安全

> **强烈建议**：密码怕忘记可以记在纸上，不要放在手机或电脑里，建议不要在ZKSAFE以外的地方输入密码，防止密码泄漏

<br>

### 密码安全性

ZKSAFE不存储你的密码，也没有办法替你改密码，如果你密码忘记，只有事先设置好的社交恢复能帮到你

合作方如果希望校验用户的密码，可以调用ZKSAFE插件，弹出ZKSAFE密码输入框，ZKSAFE校验后将密码生成的所有参数（不包括密码）返回给合作方网站，密码不出插件，确保安全

> **特别注意**：过于简单的密码，比如6位数字，9天之内可以破解；8位数字+英文，当下硬件需要上百年破解；但考虑到硬件的进步，我们建议12位大小写+数字+符号。在新硬件出现前，我们会升级密码算法，以确保12位密码的安全

<br>

### 社交恢复

如果忘记密码或私钥，可以通过事先设置的守护者们发起多签（无需密码）来社交恢复

![](/files/XuVpAnIaIvCZSTg2sjDv)

保险箱的所有权转移，新拥有者的密码和私钥替代旧的

![](/files/Lrnjr16XH3u50HUtKcjJ)

守护者可以是你最信任的亲人朋友，也可以是你自己的其他钱包。为保障安全，不要让守护者钱包都在同一台设备上。

守护者还可以是Gnosis-safe多签钱包，这个对接我们计划中

转移保险箱所有权，除了守护者多签，密码+私钥也可以转移，如果你觉得自己的私钥或密码已泄漏，可以用这种方式转移到新的钱包

转移保险箱需要额外的手续费

> **强烈建议**：强烈建议每个用户都设置社交恢复，关键时刻能救你


# 教程

## ZKSAFE 说明

ZKSAFE是开源免费的，协议级的安全产品，使用链上密码+私钥的多签来保护资产：

* 私钥被盗，密码还在，资产安全
* 密码被盗，私钥还在，资产安全
* 密码被盗，私钥被盗，资产不安全

需要安装ZKSAFE插件和MetaMask插件，一个管密码，另一个管私钥

核心功能有3个：

1. 设置密码
2. 存取资产
3. 社交恢复

ZKSAFE是协议级的，没有后台，没有私钥托管，没有管理员

ZKSAFE是钱包的安全伴侣，也是你的私人银行。钱包放零钱，大资金放ZKSAFE，安全第一，需要用时再从ZKSAFE提到钱包

一句话说明ZKSAFE：**即使私钥被盗，资产依然安全**\ <br>

### 使用教程

打开网站 <https://app.zksafe.pro/>

* 点击`Download`按钮，跳转到Chrome应用商店下载ZKSAFE插件，安装后`Download`变成`Connected`表示ZKSAFE插件连接成功
* 点击`Connect Wallet`按钮，连接MetaMask钱包<br>

![](/files/y5JV9iABbIZrDaGt0tnX)

<br>

新用户需要先激活保险箱，点击`Activate`按钮，弹出MetaMask确认框，再点击`确认`按钮，部署一个你专有的Safebox智能合约。上链后，`Safebox Address`即刚部署的合约地址，以后你可以直接给这个地址转Token和NFT，只有你能取出来<br>

![](/files/s0LD5Csqor1r5mv6Bl9Q)

<br>

取出资产必须要有链上密码，点击`SET`按钮，在ZKSAFE插件弹出框中创建你的链上密码。等待几秒到10几秒时间（根据你的电脑性能），然后在弹出的MetaMask确认框中点击`确认`按钮。

![](/files/G2m3s2FUWyJUBkOz57Ru) ![](/files/0VH8zBmAuntaMd9sgFh5)<br>

上链后，`Owner Address`即你的钱包地址，以后只能这个钱包才能取出保险箱资产

> 一个钱包只能创建一个保险箱，一个钱包也只能创建一个链上密码
>
> 链上密码不是保险箱的密码，而是钱包的，通过社交恢复可以把保险箱转给另一个钱包，从而转移保险箱内的全部资产（详见社交恢复）

\ <br>

### 存取资产

激活保险箱和创建链上密码后，你就可以在保险箱和钱包之间来回转移资产了

#### 存入

点击`绿色箭头`按钮，在弹出框中`输入Token数量`，点击`Confirm`按钮，弹出MetaMask确认框，点击`确认`等待上链即可

你也可以通过其他钱包给你的`Safebox Address`转Token<br>

![](/files/LZHohX7kk6h4pJplxuW7)

#### 取出

点击`橙色箭头`按钮，在弹出框中`输入Token数量`，点击`Confirm`按钮，弹出ZKSAFE转出确认框，`输入密码`，点击`Confirm`后弹出MetaMask确认框，点击`确认`等待上链即可

由于协议安全方面的限制，不能转出到任意地址，只能转到Owner地址，即转给自己，避免转错<br>

![](/files/UfnkNkMALoo26UJ6nL28)

\
除了Token（ERC20），还支持NFT（ERC721）的存取，别的资产类型不支持（比如ERC1155）\ <br>

### 社交恢复

如果忘记密码或者私钥，可以把保险箱的所有权转给你的另一个新钱包，这样你就可以用新钱包的链上密码+私钥转出资产

![](/files/HXRlh9GDv9rKxq3Ys3xz) ![](/files/wNruGDBqFA5G6kUDeYM3)

<br>

转移保险箱所有权有2种方式：

1. 提前设定好多签钱包，一般是你的冷钱包或好友的钱包，一起发起多签
2. 用你的链上密码+私钥也可以转移

保险箱转移给新钱包后，原来钱包的管理权将失效

> **强烈建议**：强烈建议每个用户都设置社交恢复，关键时刻能救你

\ <br>


# 合约说明

## ZKSAFE 合约说明

### 准备工作

Node.js 建议 v16，安装 [snarkjs](https://github.com/iden3/snarkjs)，你可以不会snarkjs，照着代码写也行

```javascript
npm install -g snarkjs
```

安装 [ethers](https://docs.ethers.io/v5/getting-started/)，你必须会ethers，所有代码示例都假设你会ethers

```javascript
npm install ethers
```

[合约源码](https://github.com/ZKSAFE/all-contracts/tree/main/contracts/zkSafe)

[测试代码](https://github.com/ZKSAFE/all-contracts/blob/main/test/Safebox-withdraw.js)

> 注意：测试环境是hardhat，ethers的用法跟正式环境略有不同，以上代码都基于测试环境

合约接口里用到proof的地方即基于ZK-SNARK的ZKSAFE Password，参考[ZKSAFE Passwor 技术对接](/zh/zkpass/build)

本章假设你熟悉Solidity，所以不详细说明调用方法\ <br>

### SafeboxFactory合约

Safebox合约的工厂合约，管理每个人的Safebox<br>

#### createSafebox() 激活保险箱

首先，你需要调用createSafebox()，该方法会部署一个你专属的Safebox合约，即激活

一个钱包只能激活一个保险箱，如果你已经激活，再次激活会报错。除非你转让了你的保险箱，那你就可以再激活一个

Safebox的合约地址由你的地址和激活的次数决定，无论在哪条链上，你的Safebox地址都是一样的，并且可以推导出来。如果你转让了你的保险箱，那么你再激活，Safebox的地址会跟之前不一样<br>

#### getSafeboxAddr() 推导保险箱地址

不管有没有激活，都可以推导出某个钱包对应的Safebox地址

Safebox地址和钱包地址格式一样，可以直接给Safebox地址转ERC20或ERC721，无论有没有激活都可以转进去，激活后可以提取出来

> 注意：仅支持提取原生代币（ETH）、ERC20（Token）、ERC721（NFT），其他类型资产（比如ERC1155）一概不支持，请不要存错

不过还是建议先激活再转币进去<br>

#### userToSafebox\[] 查看已激活的保险箱地址

查看某个钱包对应的已激活的Safebox地址

如果未激活，返回0x00<br>

#### changeSafeboxOwner() 转让保险箱

仅限Safebox合约调用，如果对方已有保险箱，那就不能转给他\ <br>

### Safebox合约-常规操作

每个用户都可以通过SafeboxFactory部署一个自己的专属Safebox合约<br>

#### owner() 查看保险箱的拥有者

Safebox合约的拥有者，合约里除了社交恢复的所有方法都只能拥有者调用<br>

#### transferOwnership() 转让保险箱

需要密码，仅限拥有者调用

转让之后，密码也将重置为新owner的密码，也就是说，新owner拥有了保险箱，以及里面的所有资产<br>

#### withdrawETH() 提取原生代币

需要密码，仅限拥有者调用

生成proof的时候，需要把提取数量amount作为datahash<br>

#### withdrawERC20() 提取ERC20代币

需要密码，仅限拥有者调用

生成proof的时候，需要把代币地址tokenAddr、提取数量amount的keccak256 hash值作为datahash<br>

#### withdrawERC721() 提取ERC721标准NFT

需要密码，仅限拥有者调用

生成proof的时候，需要把NFT地址tokenAddr、NFT的tokenId的keccak256 hash值作为datahash\ <br>

### Safebox合约-社交恢复

事先设置好守护者，出事时候的救命稻草

可能出事的情况：

* 忘记私钥
* 忘记ZKSAFE密码
* ZKSAFE密码失效（在20万次压力测试中从未发生）
* 私钥被盗（这时候owner被控制，转入gas给owner可能瞬间被划走，导致没法用owner，只能靠守护者）<br>

#### setSocialRecover() 设置守护者

需要密码，仅限拥有者调用

生成proof的时候，需要把所有守护者的地址\_guardians、有效守护者数量\_needGuardiansNum的keccak256 hash值作为datahash

如果你设置了5个守护者，那么把5个地址都放进\_guardians，有效守护者数量\_needGuardiansNum可以是1～5，意思是5个守护者中只要\_needGuardiansNum个发起多签，就可以实现社交恢复，通常多签设置为 2 of 3，意思是3个守护者中的2个发起多签就有效<br>

#### getSocialRecover() 查看守护者

返回3个字段：

* guardians：设定的所有守护者的地址
* needGuardiansNum：设定的有效守护者数量
* doneGuardians：已发起多签的守护者，达到needGuardiansNum数量，社交恢复即有效<br>

#### transferOwnership2() 通过守护者转让保险箱

仅限守护者调用

守护者需要指定转让给谁，可以是任意地址，但必须每个人指定的地址都一样。如果不一样，那么重新来过，直到达成共识

保险箱转让后，守护者依然有效<br>


# 介绍

## ZKSAFE Password

你有没有想过，管理员可以修改你的密码，其实你从来没拥有过你的密码

所以我们想要设计这样一个密码体系，它应该满足：

* 永不宕机
* 密码不暴露
* 只有你自己能修改你自己的密码

在EVM智能合约里，可以实现，但是有一个新的问题：三明治攻击<br>

### 三明治攻击

比方说你提交了一个取款的tx，里面带上了你的密码验证信息，因为tx是公开的，在缓冲区排队的时候，黑客复制这个tx，把取款地址换成自己的，因为没动密码验证信息，所以在合约里是可以校验通过的，然后用更高的gas提交，这样能在你前面抢跑，就能取走你的钱

所以还得加上一条：

* 签名功能<br>

### 签名功能

在你提交取款tx的时候，tx里要把**提多少钱给谁**这个信息用密码进行签名，如果篡改了tx的信息，在合约里就能校验出来，从而阻止三明治攻击

传统的算法里只有私钥能对数据签名，我们意外的发现ZK-SNARK做一些电路编程，也可以实现密码对数据签名，虽然历尽千辛万苦，在N次质疑和9次算法调整后，我们做出来了<br>

### 起源

采用这套密码体系做的第一个产品：**ZKSAFE**，运行非常棒！

然后我们觉得这么棒的密码体系应该发扬光大，它不光能支持ZKSAFE，还能支持各种资管平台，甚至能支持无私钥钱包（这将大大降低用户进入加密世界的门槛）。所以这套密码体系就是：

**ZKSAFE Password（简称ZKPass）**

**你的密码，你值得拥有！**<br>

### 竞品MPC

再次强调一下，ZKPass *（ZKSAFE Password简称ZKPass）* 没有采用MPC（私钥分片）方案。很多人问这个，可以当作竞品对比一下：

* MPC将私钥分布式存放在多个节点
  * 有中心化风险，节点被攻击可能导致所有人私钥丢失
  * 私钥被盗，密码啥的都没用
* ZKPass纯算法实现密码功能，没有节点
  * 去中心化，不存储私钥也不存储密码
  * 私钥被盗，密码依然管用

当然了，作为私钥托管方案，MPC是不错的。而ZKPass是协议级、非托管的密码算法。<br>


# 教程

## ZKSAFE Password 说明

ZKPass *（ZKSAFE Password简称ZKPass）* 只有两个功能：

1. 设置密码
2. 验证签名

其中，验证签名对接的是其他项目的合约，比如ZKSAFE合约。而设置密码在ZKSAFE插件中也有实现。为了对接更多的项目，ZKPass作为一个独立子项目，开发了ZKPass网页端，可以不依赖ZKSAFE插件，也能让合作方更好理解ZKPass协议级的运行方式

ZKPass的密码和ZKSAFE插件的密码是同一个

简而言之，ZKPass是to B，ZKSAFE是to C\ <br>

### 使用教程

打开网站 <https://password.zksafe.pro/> ，点击`Connect Wallet`按钮，连接MetaMask钱包<br>

![](/files/caqhDvEWyWt7KiP7M8Rj)

<br>

输入两次相同的密码后，点击`Set Password`按钮，电脑进入ZK计算，需要等待loading几秒到10几秒时间（根据你电脑的性能），电脑计算完ZK后，弹出MetaMask确认框，再点击`确认`按钮，等待链上确认即可<br>

![](/files/Q4RsgHDc1LE4DtfjRfOD)

<br>

如果你设置过密码，那么你打开ZKPass就是重置密码页面，还可以查看最近的设置密码记录<br>

![](/files/zeY7l9gRSp2CdwUlN0BS)

\
需要用旧密码才能重置新密码\ <br>

### 关于密码（非常重要）

关于密码你需要知道的几点，不要出事了才来看

* ZKPass没有存储你的密码，没有人知道你的密码，除了你自己
* ZKPass没有管理员，没有人能帮助你找回密码或重置密码
* 不要忘记你的密码，不要记在电脑或手机上，记在纸上

我们没法阻止密码破解，关于密码强度

* 6位密码可以在10天内破解，所以不能设置这么短
* 8位密码破解现在需要几十年，随着电脑性能发展，未来可能只需要几个月
* 建议12位以上密码，可以是一个短句子+数字，比如 *2022IHaveADreamToday*

\ <br>


# 工作原理

## ZKSAFE Password 工作原理

ZKPass *（ZKSAFE Password简称ZKPass）* 本质上是把`pwdhash`（密码的哈希值）存在合约里，如果说ENS是把`name`绑定到地址，那么ZKPass就是把`pwdhash`绑定到地址

<br>

![](/files/tRODDYY6fTeNPcwIHoNo)

<br>

为了实现签名，需要把 **用户想要干什么** 这个信息，用Keccak256生成`datahash`，再跟签名过期时间`expiration`、指定的链`chainId`、从1开始自增的`nonce`，用Keccak256生成`fullhash`

> 为什么用nonce？
>
> 用来确保签名不能重复提交，避免双花

在ZK电路里，使用Poseidon算法来生成hash（gas较低的hash算法），代码如下：

```javascript
pragma circom 2.0.0;

include "../../node_modules/circomlib/circuits/poseidon.circom";

template Main() {
    signal input in[3];
    signal output out[3];

    component poseidon1 = Poseidon(2);
    component poseidon2 = Poseidon(2);

    poseidon1.inputs[0] <== in[0];  //pwd
    poseidon1.inputs[1] <== in[1];  //address
    out[0] <== poseidon1.out; //pwdhash

    poseidon2.inputs[0] <== poseidon1.out;
    poseidon2.inputs[1] <== in[2]; //fullhash
    out[1] <== in[2]; //fullhash
    out[2] <== poseidon2.out; //allhash
}

component main = Main();
```

画成逻辑图就是下图

<br>

![](/files/gIafrNjY0pqxR5xN1nHQ)

<br>

`password`和`address`生成`pwdhash`，确保每个用户的`pwdhash`都不一样

`pwdhash`和`fullhash`生成`allhash`，确保所有的数据都有带上

最后`proof`就相当于给`allhash`、`pwdhash`、`fullhash`盖了个章，证明`pwdhash`是由`password`生成的，但是不知道`password`是啥，也证明了`allhash`是由`password`和`fullhash`生成

`fullhash`作为输出，在合约里可以验证是否被篡改（即签名）

> 听起来像绕口令？
>
> 是的，还有一个坑没说，Poseidon算法的输入是254位，但是Keccak256生成的`fullhash`是256位，所以需要`fullhash`除以8再输入到ZK电路，ZKPass合约已经自动除以8了，需要前端也除以8，这样才能在ZKPass合约校验通过

<br>

### 补充说明

在用户侧，ZKPass只有改密码的功能，如果只是验证密码，获取`pwdhash`在链下就可以验证，而链上的验证通常是配合其他合约一起，做数据签名用，比如ZKSAFE合约：ZKSAFE合约把 **用户想要干什么** 这些参数，在合约内生成`datahash`传给ZKPass合约，ZKPass验证成功后，ZKSAFE合约就知道用户的密码正确，以及 **用户想要干什么** 这些参数没有被篡改（即签名），ZKSAFE合约就可以做下一步（提币）操作了<br>


# 技术对接

## ZKSAFE Password 技术对接

### 准备工作

Node.js 建议 v16，安装 [snarkjs](https://github.com/iden3/snarkjs)，你可以不会snarkjs，照着代码写也行

```javascript
npm install -g snarkjs
```

安装 [ethers](https://docs.ethers.io/v5/getting-started/)，你必须会ethers，所有代码示例都假设你会ethers

```javascript
npm install ethers
```

[合约源码](https://github.com/ZKSAFE/all-contracts/tree/main/contracts/zkpass)

[测试代码](https://github.com/ZKSAFE/all-contracts/blob/main/test/ZKPass-test.js)

> 注意：测试环境是hardhat，ethers的用法跟正式环境略有不同，以下代码都基于测试环境

不建议用户在ZKSAFE以外的地方输入密码，防止密码泄漏。所以ZKPass *（ZKSAFE Password简称ZKPass）* 的合约面向的是合作方合约，比如ZKSAFE<br>

### resetPassword() 设置密码

初始化密码和改密码都是这个接口，先说所有跟ZK相关的接口都要用到的工具方法`getProof()`

#### 工具方法

```javascript
//util
async function getProof(pwd, address, nonce, datahash) {
    let expiration = parseInt(Date.now() / 1000 + 600)
    let chainId = (await provider.getNetwork()).chainId
    let fullhash = utils.solidityKeccak256(['uint256','uint256','uint256','uint256'], [expiration, chainId, nonce, datahash])
    fullhash = s(b(fullhash).div(8)) //fullhash必须是254位, solidityKeccak256是256位，所以要转换

    let input = [stringToHex(pwd), address, fullhash]
    let data = await snarkjs.groth16.fullProve({in:input}, "./zk/v1/circuit_js/circuit.wasm", "./zk/v1/circuit_final.zkey")

    const vKey = JSON.parse(fs.readFileSync("./zk/v1/verification_key.json"))
    const res = await snarkjs.groth16.verify(vKey, data.publicSignals, data.proof)

    if (res === true) {
        console.log("Verification OK")

        let pwdhash = data.publicSignals[0]
        let fullhash = data.publicSignals[1]
        let allhash = data.publicSignals[2]

        let proof = [
            BigNumber.from(data.proof.pi_a[0]).toHexString(),
            BigNumber.from(data.proof.pi_a[1]).toHexString(),
            BigNumber.from(data.proof.pi_b[0][1]).toHexString(),
            BigNumber.from(data.proof.pi_b[0][0]).toHexString(),
            BigNumber.from(data.proof.pi_b[1][1]).toHexString(),
            BigNumber.from(data.proof.pi_b[1][0]).toHexString(),
            BigNumber.from(data.proof.pi_c[0]).toHexString(),
            BigNumber.from(data.proof.pi_c[1]).toHexString()
        ]

        return {proof, pwdhash, address, expiration, chainId, nonce, datahash, fullhash, allhash}

    } else {
        console.log("Invalid proof")
    }
}
```

为方便起见，我们写了一个工具方法`getProof()`，封装了所有用到的ZK算法，处理了ZK里面256位转254位的坑，需要注意的是`circuit.wasm`、`circuit_final.zkey`、`verification_key.json`是固定值，可以在[ZK源码](https://github.com/ZKSAFE/all-contracts/tree/main/zk)找到

`getProof()`即图中的ZK Circuit<br>

![](/files/tRODDYY6fTeNPcwIHoNo)

<br>

`getProof()`有4个参数，分别是：

* pwd：你的密码，string类型
* address：你的钱包地址，string类型
* nonce：从ZKPass合约获取的你的nonce值，string类型
* datahash：你想要对什么数据进行签名，这个数据的hash值，string类型

返回所有ZK算法有关的数据：

* proof：ZK-SNARK的proof，由8个uint256组成的数组
* pwdhash：ZKPass合约需要用到的pwdhash，uint256类型
* address：参数里的address，string类型
* expiration：签名过期时间，默认10分钟，int类型
* chainId：公链id，int类型
* nonce：参数里的nonce，string类型
* datahash：参数里的datahash，string类型
* fullhash：这个不需要传入合约，254位，string类型
* allhash：以上所有参数的hash，uint256类型<br>

#### 初始化密码

```javascript
let pwd = 'abc123' //你的密码
let nonce = '1' //初始化密码，nonce就是1
let datahash = '0' //对于resetPassword接口，datahash固定是0
let p = await getProof(pwd, accounts[0].address, nonce, datahash)

//需要付一点手续费 :)
fee = await zkPass.fee()
console.log('zkPass fee(Ether)', utils.formatEther(fee))

let gasLimit = await zkPass.estimateGas.resetPassword(p.proof, 0, 0, p.proof, p.pwdhash, p.expiration, p.allhash, {value: fee})
await zkPass.resetPassword(p.proof, 0, 0, p.proof, p.pwdhash, p.expiration, p.allhash, {value: fee, gasLimit})
console.log('initPassword done')
```

`resetPassword()`有7个参数，分别是：

* proof1：旧密码生成proof，由8个uint256组成的数组
* expiration1：旧密码的过期时间，uint256类型
* allhash1：旧密码生成allhash，uint256类型
* proof2：新密码生成proof，由8个uint256组成的数组
* pwdhash2：新密码的pwdhash，由ZK生成，uint256类型
* expiration2：新密码的过期时间，uint256类型
* allhash2：新密码生成allhash，uint256类型

因为初始化密码没有旧密码，所以前3个旧密码相关的参数在合约里是用不到的，但是必须得传，全部传0即可，或者把新密码的`proof2`当`proof1`传也行（示例就是这么干的）

成功后，调用者的address（msg.sender）的密码就是`pwd`<br>

#### 修改密码

```javascript
let oldpwd = 'abc123' //旧密码
let nonce = await zkPass.nonceOf(accounts[0].address) //当前的nonce
let datahash = '0' //对于resetPassword接口，datahash固定是0
let oldZkp = await getProof(oldpwd, accounts[0].address, s(nonce), datahash) //旧密码的proof

let newpwd = '123123' //新密码
let newZkp = await getProof(newpwd, accounts[0].address, s(nonce.add(1)/**新密码的nonce+1*/), datahash) //新密码的proof

fee = await zkPass.fee()
console.log('zkPass fee(Ether)', utils.formatEther(fee))

//need fee
await zkPass.resetPassword(oldZkp.proof, oldZkp.expiration, oldZkp.allhash, newZkp.proof, newZkp.pwdhash, newZkp.expiration, newZkp.allhash, {value: fee})
console.log('resetPassword done')
```

还是`resetPassword()`接口，修改密码需要用旧密码，所以要用旧密码生成前3个参数

成功后，调用者的address（msg.sender）的密码就是`newpwd`，旧密码`oldpwd`作废<br>

### verify() 校验密码

密码可以在链下校验，获取`pwdhash`在链下就可以校验；也可以上链校验，通常是配合合作方合约一起，由合作方合约调用`ZKPass.verify()`，密码错误就报错，不报错的话就是密码正确，且签名有效，合作方合约可以继续处理后续

不建议用户在ZKSAFE以外的地方输入密码，防止密码泄漏，所以链下校验只在ZKPass就行，合作方可以用链上校验的方式对接ZKPass

`verify()`有5个参数，分别是

* user：哪个用户的签名，address类型
* proof：密码在ZK生成的proof，由8个uint256组成的数组
* datahash：用户对什么数据进行的签名，这个就是数据的hash，uint256类型
* expiration：签名的过期时间，uint256类型
* allhash：签名在ZK生成allhash，uint256类型

合约内会用user的`pwdhash`进行密码的校验，以及把`datahash`转成254位的`fullhash`。。。总之，`getProof()`工具会处理所有ZK校验相关的参数

ZKSAFE作为合作方的合约调用ZKPass

```javascript
function withdrawERC20(
    uint[8] memory proof, //转给ZKPass的参数
    address tokenAddr, //提什么token
    uint amount, //提多少
    uint expiration, //转给ZKPass的参数
    uint allhash //转给ZKPass的参数
) external onlyOwner {
    uint datahash = uint(keccak256(abi.encodePacked(tokenAddr, amount))); //计算datahash
    eps.verify(owner(), proof, datahash, expiration, allhash); //密码和签名的校验

    IERC20(tokenAddr).safeTransfer(owner(), amount); //校验通过，干活！

    emit WithdrawERC20(tokenAddr, amount);
}
```

在这个示例中，用户想要把token从ZKSAFE提出来，所以需要对提什么token（`tokenAddr`）、提多少（`amount`）用密码进行签名

ZKSAFE的链下代码

```javascript
let pwd = 'abc123' //用户的密码
let nonce = s(await eps.nonceOf(accounts[0].address)) //用户当前的nonce
let tokenAddr = usdt.address //提什么token
let amount = s(m(40, 18)) //提多少
let datahash = utils.solidityKeccak256(['address', 'uint256'], [tokenAddr, amount]) //计算datahash
datahash = s(b(datahash)) //转成string类型数字
let p = await getProof(pwd, accounts[0].address, nonce, datahash) //计算ZK Proof

await safebox.withdrawERC20(p.proof, tokenAddr, amount, p.expiration, p.allhash) //调用合约，提款
console.log('withdrawERC20 done')

await print()
```

`datahash`是合作方定义的，uint256类型，通常是hash值。也有例外的，比方说签名的是address，即uint160类型，直接放`datahash`也能装得下，可以不用hash

合作方链下计算的`datahash`，和合作方合约计算的`datahash`必须一致


# 介绍

## ZKSAFE Wallet

还在探索中。。大概会有以下特点：

* 基于ERC4337
* 使用ZKPass密码替代私钥，实现无私钥钱包
* 集成ZKSAFE保险箱，实现密码被盗，资产依然安全
* 更低的gas费
* 集成应用市场，安全省心畅游加密世界


# 合约部署

### Alpha版本

#### Polygon

PasswordService deployed: 0x2CB213127Fa481E7D9303bedB0Bd3FC3461D2a9A

SafeboxFactory deployed: 0xB53CB1feEbea105C30982e7f2Ed803a2195DA922

<br>

### Beta版本

#### Polygon

EthereumPasswordService deployed: 0x555DE00394cEBb92f49e9DC4399372c81F5360e4

SafeboxFactory deployed: 0xEF6b7A04BF73f8674b5B7BcDd460778862dd5b90

<br>

### Beta版本2

#### Polygon

ZKPass deployed: 0x72f3E7DdAe7f5B8859a230FE00f4214d582622fF

SafeboxFactory deployed: 0xd9403569f3447121eb78d426Bb5eFC7D10316b50

<br>

### V1正式版 [(SlowMist Audit Report)](https://github.com/ZKSAFE/zksafe-docs/blob/zh/images/SlowMistAuditReport-ZKSAFE.pdf)

#### Ethereum\BSC\Optimism\Arbitrum\Polygon\\

ZKPass deployed: 0x9802cBf6480FE2a0c69740Bc8008739DfF1E7CEF

SafeboxFactory deployed: 0x8528d5a340Bef2e50844CDABdFa21bC6B57c3982


