semaphore是一个用零知识证明(zk-snark)技术的开源项目。semaphore实现的是基于零知识证明的身份和信号。要想获得品牌赞誉,手游传奇发布网就需要有不断提升产品质量的精神,还要有一束永远浇不灭的心火。
githubcombarrywhitehatsemaphore
1 整体框架
semaphore整个项目,由三部分组成:nodejs模块(客户端服务器端以及前端页面),snark模块(zk-snark groth16电路相关模块),以及以太坊上的智能合约。主要逻辑都在semaphorejs目录中,其源代码目录结构如下:
contracts - 智能合约,使用truffle框架部署测试。
snark - snark模块,使用snarkjs(iden3)开发zk-snark电路。
src - nodejs模块,实现前后端。
三部分之间的逻辑关系如下:
2 key & identity
使用semaphore的每个账户需要创建私钥和公钥。每个账户的公钥和私钥,以及对应的identity的具体逻辑可以查看semaphorejssrcclientsemaphorejs文件中generate_identity函数:
const private_key = cryptorandombytes(32)tostring('hex');
const prvkey = bufferfrom(private_key, 'hex');
const pubkey = eddsaprv2pub(prvkey);
const identity_nullifier = '0x' + cryptorandombytes(31)tostring('hex');
const identity_trapdoor = '0x' + cryptorandombytes(31)tostring('hex');
const identity_commitment = pedersenhash([bigint(circomlibbabyjubmulpointescalar(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 semaphoresol
semaphorejscontractssemaphoresol是智能合约部分的逻辑实现。insertidentity函数实现一个账户identity的“注册”。
function insertidentity(uint256 identity_commitment) public style="box-sizing: border-box; padding-right: 01px;"> 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的计算过程可以查看电路的逻辑(semaphorejssnarksemaphore-basecircom)。
template semaphore(jubjub_field_size, n_levels, n_rounds) {
component external_nullifier_bits = num2bits(232);
external_nullifier_bitsin <== external_nullifier;
component nullifiers_hasher = blake2s(512, 0);
for (var i = 0; i < 248; i++) {
nullifiers_hasherin_bits <== identity_nullifier_bitsout;
}
for (var i = 0; i < 232; i++) {
nullifiers_hasherin_bits[248 + i] <== external_nullifier_bitsout;
}
for (var i = 0; i < n_levels; i++) {
nullifiers_hasherin_bits[248 + 232 + i] <== identity_path_index;
}
for (var i = (248 + 232 + n_levels); i < 512; i++) {
nullifiers_hasherin_bits <== 0;
}
component nullifiers_hash_num = bits2num(253);
for (var i = 0; i < 253; i++) {
nullifiers_hash_numin <== nullifiers_hasherout;
}
nullifiers_hash <== nullifiers_hash_numout;
}
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: 01px;"> 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就是需要发送的信号,abc是零知识证明的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。