Semaphore是一個用零知識證明(zk-SNARK)技術的開源項目。Semaphore實現的是基于零知識證明的身份和信號。https: github com barryWhiteHat
Semaphore是一個用零知識證明(zk-SNARK)技術的開源項目。Semaphore實現的是基于零知識證明的身份和信號。
https://github.com/barryWhiteHat/semaphore
1. 整體框架
Semaphore整個項目,由三部分組成:nodejs模塊(客戶端/服務器端以及前端頁面),snark模塊(zk-SNARK Groth16電路相關模塊),以及以太坊上的智能合約。主要邏輯都在semaphorejs目錄中,其源代碼目錄結構如下:
contracts - 智能合約,使用truffle框架部署測試。
snark - snark模塊,使用snarkjs(iden3)開發zk-SNARK電路。
src - nodejs模塊,實現前后端。
三部分之間的邏輯關系如下:
2. Key & Identity
使用Semaphore的每個賬戶需要創建私鑰和公鑰。每個賬戶的公鑰和私鑰,以及對應的Identity的具體邏輯可以查看semaphorejs/src/client/semaphore.js文件中generate_identity函數:
const private_key = crypto.randomBytes(32).toString('hex');
const prvKey = Buffer.from(private_key, 'hex');
const pubKey = eddsa.prv2pub(prvKey);
const identity_nullifier = '0x' + crypto.randomBytes(31).toString('hex');
const identity_trapdoor = '0x' + crypto.randomBytes(31).toString('hex');
const identity_commitment = pedersenHash([bigInt(circomlib.babyJub.mulPointEscalar(pubKey, 8)[0]), bigInt(identity_nullifier), bigInt(identity_trapdoor)]);
私鑰是256位的隨機數。公鑰是私鑰的EdDSA的簽名。Identity主要由兩部分組成:31個字節的nullifier和31個字節的trapdoor。這兩部分都是隨機生成。這里的nullfier,不要和ZCash中的Nullifier混淆。這里的nullfier就是隨機數。每個Identity會對應兩個對應的信息:一個是commitment,一個是nullifier_hash。Commitment的計算方式如下圖:
Identity中的nullifier以及trapdoor并不記錄在以太坊的智能合約中,對應的commitment會記錄在合約中。
3. Semaphore.sol
semaphorejs/contracts/Semaphore.sol是智能合約部分的邏輯實現。insertIdentity函數實現一個賬戶Identity的“注冊”。
function insertIdentity(uint256 identity_commitment) public style="box-sizing: border-box; padding-right: 0.1px;"> insert(id_tree_index, identity_commitment);
uint256 root = tree_roots[id_tree_index];
root_history[root] = true;
}
Identity對應的commitment會添加到一個merkle樹上,同時新的merkle樹根會記錄在root_history的mapping中。
4. Nullifier Hash
Nullifier Hash是用來證明某個Identity對應commitment存在一個merkle樹上,并生成的標示。Nullfier Hash的計算過程可以查看電路的邏輯(semaphorejs/snark/semaphore-base.circom)。
template Semaphore(jubjub_field_size, n_levels, n_rounds) {
...
component external_nullifier_bits = Num2Bits(232);
external_nullifier_bits.in <== external_nullifier;
component nullifiers_hasher = Blake2s(512, 0);
for (var i = 0; i < 248; i++) {
nullifiers_hasher.in_bits[i] <== identity_nullifier_bits.out[i];
}
for (var i = 0; i < 232; i++) {
nullifiers_hasher.in_bits[248 + i] <== external_nullifier_bits.out[i];
}
for (var i = 0; i < n_levels; i++) {
nullifiers_hasher.in_bits[248 + 232 + i] <== identity_path_index[i];
}
for (var i = (248 + 232 + n_levels); i < 512; i++) {
nullifiers_hasher.in_bits[i] <== 0;
}
component nullifiers_hash_num = Bits2Num(253);
for (var i = 0; i < 253; i++) {
nullifiers_hash_num.in[i] <== nullifiers_hasher.out[i];
}
nullifiers_hash <== nullifiers_hash_num.out;
...
}
Nullifier Hash的計算邏輯如下圖:
其實nullfier和path index已經足夠表示。額外加入了external nullfier的原因是,同一個Identity,在external nullifier不同的情況下,生成不同的nullifier hash。也就是說,一個賬戶可以多次“消費”。這樣設計的原因是為了Signal的業務需求。
5. Signal
通過智能合約創建了Identity,就可以發信號(Signal)了。一個賬戶發送信號,必須首先提供Identity在merkle樹上的證明(能計算出commitment)。智能合約中的broadcastSignal是發送信號的接口:
function broadcastSignal(
bytes memory signal,
uint[2] memory a,
uint[2][2] memory b,
uint[2] memory c,
uint[4] memory input // (root, nullifiers_hash, signal_hash, external_nullifier)
) public
style="box-sizing: border-box; padding-right: 0.1px;"> isValidSignalAndProof(signal, a, b, c, input)
{
uint nullifiers_hash = input[1];
signals[current_signal_index++] = signal;
nullifier_hash_history[nullifiers_hash] = true;
emit SignalBroadcast(signal, nullifiers_hash, input[3]);
}
signal就是需要發送的信號,a/b/c是零知識證明的proof信息,input是零知識證明對應電路的輸入,包括merkle樹根,nullifier hash,signal hash以及external nullifier。
只有在proof信息驗證過后,對應signal才會記錄。每次發送signal時對應的nullifier hash會被記錄下來。也就是說,在external nullifier不變的情況下,所有Identity只能發送一次Signal。
總結:
Semaphore項目由js開發,結合零知識證明(zk-SNARK),在以太坊的智能合約的基礎上實現Identity。每個Identity可以發送信號。在external nullifier不變的情況下,每個Identity只能發送一次Signal。(Star Li)