diff --git a/waku/waku_rln_relay/constants.nim b/waku/waku_rln_relay/constants.nim index 81cfe9449..7087ad390 100644 --- a/waku/waku_rln_relay/constants.nim +++ b/waku/waku_rln_relay/constants.nim @@ -1,6 +1,9 @@ import stint +import + ./protocol_types + import ../waku_keystore @@ -15,7 +18,8 @@ const # inputs of the membership contract constructor # TODO may be able to make these constants private and put them inside the waku_rln_relay_utils const - MembershipFee* = 1000000000000000.u256 + # in wei + MembershipFee* = 0.u256 # the current implementation of the rln lib supports a circuit for Merkle tree with depth 20 MerkleTreeDepth* = 20 EthClient* = "http://127.0.0.1:8540" @@ -29,6 +33,14 @@ const const DefaultRlnTreePath* = "rln_tree.db" +when defined(rln_v2): + const + # pre-processed "rln/waku-rln-relay/v2.0.0" to array[32, byte] + DefaultRlnIdentifier*: RlnIdentifier = [114, 108, 110, 47, 119, 97, 107, 117, + 45, 114, 108, 110, 45, 114, 101, 108, + 97, 121, 47, 118, 50, 46, 48, 46, + 48, 0, 0, 0, 0, 0, 0, 0] + # temporary variables to test waku-rln-relay performance in the static group mode const StaticGroupSize* = 10000 diff --git a/waku/waku_rln_relay/conversion_utils.nim b/waku/waku_rln_relay/conversion_utils.nim index d70c706f5..45201f805 100644 --- a/waku/waku_rln_relay/conversion_utils.nim +++ b/waku/waku_rln_relay/conversion_utils.nim @@ -53,18 +53,36 @@ proc encodeLengthPrefix*(input: openArray[byte]): seq[byte] = output = concat(@len, @input) return output -proc serialize*(idSecretHash: IdentitySecretHash, +when defined(rln_v2): + proc serialize*(idSecretHash: IdentitySecretHash, + memIndex: MembershipIndex, + userMessageLimit: UserMessageLimit, + messageId: MessageId, + externalNullifier: ExternalNullifier, + msg: openArray[byte]): seq[byte] = + ## a private proc to convert RateLimitProof and the data to a byte seq + ## this conversion is used in the proofGen proc + ## the serialization is done as instructed in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L146 + ## [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + let memIndexBytes = toBytes(uint64(memIndex), Endianness.littleEndian) + let userMessageLimitBytes = toBytes(uint64(userMessageLimit), Endianness.littleEndian) + let messageIdBytes = toBytes(uint64(messageId), Endianness.littleEndian) + let lenPrefMsg = encodeLengthPrefix(msg) + let output = concat(@idSecretHash, @memIndexBytes, @userMessageLimitBytes, @messageIdBytes, @externalNullifier, lenPrefMsg) + return output +else: + proc serialize*(idSecretHash: IdentitySecretHash, memIndex: MembershipIndex, epoch: Epoch, msg: openArray[byte]): seq[byte] = - ## a private proc to convert RateLimitProof and the data to a byte seq - ## this conversion is used in the proofGen proc - ## the serialization is done as instructed in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L146 - ## [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] - let memIndexBytes = toBytes(uint64(memIndex), Endianness.littleEndian) - let lenPrefMsg = encodeLengthPrefix(msg) - let output = concat(@idSecretHash, @memIndexBytes, @epoch, lenPrefMsg) - return output + ## a private proc to convert RateLimitProof and the data to a byte seq + ## this conversion is used in the proofGen proc + ## the serialization is done as instructed in https://github.com/kilic/rln/blob/7ac74183f8b69b399e3bc96c1ae8ab61c026dc43/src/public.rs#L146 + ## [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] + let memIndexBytes = toBytes(uint64(memIndex), Endianness.littleEndian) + let lenPrefMsg = encodeLengthPrefix(msg) + let output = concat(@idSecretHash, @memIndexBytes, @epoch, lenPrefMsg) + return output proc serialize*(proof: RateLimitProof, data: openArray[byte]): seq[byte] = @@ -72,14 +90,23 @@ proc serialize*(proof: RateLimitProof, data: openArray[byte]): seq[byte] = ## this conversion is used in the proof verification proc ## [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal ] let lenPrefMsg = encodeLengthPrefix(@data) - var proofBytes = concat(@(proof.proof), - @(proof.merkleRoot), - @(proof.epoch), - @(proof.shareX), - @(proof.shareY), - @(proof.nullifier), - @(proof.rlnIdentifier), - lenPrefMsg) + when defined(rln_v2): + var proofBytes = concat(@(proof.proof), + @(proof.merkleRoot), + @(proof.externalNullifier), + @(proof.shareX), + @(proof.shareY), + @(proof.nullifier), + lenPrefMsg) + else: + var proofBytes = concat(@(proof.proof), + @(proof.merkleRoot), + @(proof.epoch), + @(proof.shareX), + @(proof.shareY), + @(proof.nullifier), + @(proof.rlnIdentifier), + lenPrefMsg) return proofBytes diff --git a/waku/waku_rln_relay/group_manager/group_manager_base.nim b/waku/waku_rln_relay/group_manager/group_manager_base.nim index 7eb12bb79..947097af3 100644 --- a/waku/waku_rln_relay/group_manager/group_manager_base.nim +++ b/waku/waku_rln_relay/group_manager/group_manager_base.nim @@ -135,14 +135,26 @@ template slideRootQueue*(g: GroupManager): untyped = discard rootBuffer.slideRootQueue(root) rootBuffer -method verifyProof*(g: GroupManager, +when defined(rln_v2): + method verifyProof*(g: GroupManager, + input: openArray[byte], + proof: RateLimitProof): GroupManagerResult[bool] {.base,gcsafe,raises:[].} = + ## verifies the proof against the input and the current merkle root + ## TODO: verify the external nullifier with provided RateLimitProof + let proofVerifyRes = g.rlnInstance.proofVerify(input, RateLimitProof(proof), g.validRoots.items().toSeq()) + if proofVerifyRes.isErr(): + return err("proof verification failed: " & $proofVerifyRes.error()) + return ok(proofVerifyRes.value()) +else: + method verifyProof*(g: GroupManager, input: openArray[byte], proof: RateLimitProof): GroupManagerResult[bool] {.base,gcsafe,raises:[].} = - ## verifies the proof against the input and the current merkle root - let proofVerifyRes = g.rlnInstance.proofVerify(input, proof, g.validRoots.items().toSeq()) - if proofVerifyRes.isErr(): - return err("proof verification failed: " & $proofVerifyRes.error()) - return ok(proofVerifyRes.value()) + ## verifies the proof against the input and the current merkle root + let proofVerifyRes = g.rlnInstance.proofVerify(input, proof, g.validRoots.items().toSeq()) + if proofVerifyRes.isErr(): + return err("proof verification failed: " & $proofVerifyRes.error()) + return ok(proofVerifyRes.value()) + method generateProof*(g: GroupManager, data: openArray[byte], diff --git a/waku/waku_rln_relay/protocol_types.nim b/waku/waku_rln_relay/protocol_types.nim index 83ccfb89b..3fb29fee8 100644 --- a/waku/waku_rln_relay/protocol_types.nim +++ b/waku/waku_rln_relay/protocol_types.nim @@ -31,6 +31,12 @@ type RlnIdentifier* = array[32, byte] ZKSNARK* = array[128, byte] +when defined(rln_v2): + type + UserMessageLimit* = uint64 + MessageId* = uint64 + ExternalNullifier* = array[32, byte] + # Custom data types defined for waku rln relay ------------------------- type RateLimitProof* = object ## RateLimitProof holds the public inputs to rln circuit as @@ -39,8 +45,6 @@ type RateLimitProof* = object proof*: ZKSNARK ## the root of Merkle tree used for the generation of the `proof` merkleRoot*: MerkleNode - ## the epoch used for the generation of the `proof` - epoch*: Epoch ## shareX and shareY are shares of user's identity key ## these shares are created using Shamir secret sharing scheme ## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Linear-Equation-amp-SSS @@ -49,8 +53,13 @@ type RateLimitProof* = object ## nullifier enables linking two messages published during the same epoch ## see details in https://hackmd.io/tMTLMYmTR5eynw2lwK9n1w?view#Nullifiers nullifier*: Nullifier + ## the epoch used for the generation of the `proof` + epoch*: Epoch ## Application specific RLN Identifier rlnIdentifier*: RlnIdentifier + when defined(rln_v2): + ## the external nullifier used for the generation of the `proof` (derived from poseidon([epoch, rln_identifier])) + externalNullifier*: ExternalNullifier type ProofMetadata* = object nullifier*: Nullifier @@ -69,6 +78,7 @@ type # Protobufs enc and init proc init*(T: type RateLimitProof, buffer: seq[byte]): ProtoResult[T] = var nsp: RateLimitProof + let pb = initProtoBuffer(buffer) var proof: seq[byte] @@ -101,6 +111,7 @@ proc init*(T: type RateLimitProof, buffer: seq[byte]): ProtoResult[T] = return ok(nsp) + proc encode*(nsp: RateLimitProof): ProtoBuffer = var output = initProtoBuffer() @@ -113,7 +124,6 @@ proc encode*(nsp: RateLimitProof): ProtoBuffer = output.write3(7, nsp.rlnIdentifier) output.finish3() - return output type diff --git a/waku/waku_rln_relay/rln/rln_interface.nim b/waku/waku_rln_relay/rln/rln_interface.nim index 7a37ac990..f45880e2e 100644 --- a/waku/waku_rln_relay/rln/rln_interface.nim +++ b/waku/waku_rln_relay/rln/rln_interface.nim @@ -101,33 +101,46 @@ proc seeded_key_gen*(ctx: ptr RLN, input_buffer: ptr Buffer, output_buffer: ptr ## the return bool value indicates the success or failure of the operation proc generate_proof*(ctx: ptr RLN, - input_buffer: ptr Buffer, - output_buffer: ptr Buffer): bool {.importc: "generate_rln_proof".} + input_buffer: ptr Buffer, + output_buffer: ptr Buffer): bool {.importc: "generate_rln_proof".} +## rln-v2 +## input_buffer has to be serialized as [ identity_secret<32> | identity_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] +## output_buffer holds the proof data and should be parsed as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> ] +## rln-v1 ## input_buffer has to be serialized as [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] ## output_buffer holds the proof data and should be parsed as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ] ## integers wrapped in <> indicate value sizes in bytes ## the return bool value indicates the success or failure of the operation proc verify*(ctx: ptr RLN, - proof_buffer: ptr Buffer, - proof_is_valid_ptr: ptr bool): bool {.importc: "verify_rln_proof".} -## proof_buffer has to be serialized as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal ] + proof_buffer: ptr Buffer, + proof_is_valid_ptr: ptr bool): bool {.importc: "verify_rln_proof".} +## rln-v2 +## proof_buffer has to be serialized as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> | signal_len<8> | signal ] +## rln-v1 +## ## proof_buffer has to be serialized as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal ] ## the return bool value indicates the success or failure of the call to the verify function ## the verification of the zk proof is available in proof_is_valid_ptr, where a value of true indicates success and false a failure proc verify_with_roots*(ctx: ptr RLN, - proof_buffer: ptr Buffer, - roots_buffer: ptr Buffer, - proof_is_valid_ptr: ptr bool): bool {.importc: "verify_with_roots".} + proof_buffer: ptr Buffer, + roots_buffer: ptr Buffer, + proof_is_valid_ptr: ptr bool): bool {.importc: "verify_with_roots".} +## rln-v2 +## proof_buffer has to be serialized as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> | signal_len<8> | signal ] +## rln-v1 ## proof_buffer has to be serialized as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> | signal_len<8> | signal ] ## roots_buffer contains the concatenation of 32 bytes long serializations in little endian of root values ## the return bool value indicates the success or failure of the call to the verify function ## the verification of the zk proof is available in proof_is_valid_ptr, where a value of true indicates success and false a failure proc zk_prove*(ctx: ptr RLN, - input_buffer: ptr Buffer, - output_buffer: ptr Buffer): bool {.importc: "prove".} + input_buffer: ptr Buffer, + output_buffer: ptr Buffer): bool {.importc: "prove".} ## Computes the zkSNARK proof and stores it in output_buffer for input values stored in input_buffer +## rln-v2 +## input_buffer is serialized as input_data as [ identity_secret<32> | user_message_limit<32> | message_id<32> | path_elements> | identity_path_index> | x<32> | external_nullifier<32> ] +## rln-v1 ## input_buffer is serialized as input_data as [ id_key<32> | path_elements> | identity_path_index> | x<32> | epoch<32> | rln_identifier<32> ] ## output_buffer holds the proof data and should be parsed as [ proof<128> ] ## path_elements and indentity_path elements serialize a merkle proof for id_key and are vectors of elements of 32 and 1 bytes, respectively (not. Vec<>). @@ -136,8 +149,8 @@ proc zk_prove*(ctx: ptr RLN, ## the return bool value indicates the success or failure of the operation proc zk_verify*(ctx: ptr RLN, - proof_buffer: ptr Buffer, - proof_is_valid_ptr: ptr bool): bool {.importc: "verify".} + proof_buffer: ptr Buffer, + proof_is_valid_ptr: ptr bool): bool {.importc: "verify".} ## Verifies the zkSNARK proof passed in proof_buffer ## input_buffer is serialized as input_data as [ proof<128> ] ## the verification of the zk proof is available in proof_is_valid_ptr, where a value of true indicates success and false a failure diff --git a/waku/waku_rln_relay/rln/wrappers.nim b/waku/waku_rln_relay/rln/wrappers.nim index fb120ed6d..4b461b531 100644 --- a/waku/waku_rln_relay/rln/wrappers.nim +++ b/waku/waku_rln_relay/rln/wrappers.nim @@ -159,79 +159,157 @@ proc poseidon*(data: seq[seq[byte]]): RlnRelayResult[array[32, byte]] = return ok(output) -# TODO: collocate this proc with the definition of the RateLimitProof -# and the ProofMetadata types -proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = - let externalNullifierRes = poseidon(@[@(proof.epoch), - @(proof.rlnIdentifier)]) - if externalNullifierRes.isErr(): - return err("could not construct the external nullifier") - return ok(ProofMetadata( - nullifier: proof.nullifier, - shareX: proof.shareX, - shareY: proof.shareY, - externalNullifier: externalNullifierRes.get() - )) +when defined(rln_v2): + # TODO: collocate this proc with the definition of the RateLimitProof + # and the ProofMetadata types + proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = + return ok(ProofMetadata( + nullifier: proof.nullifier, + shareX: proof.shareX, + shareY: proof.shareY, + externalNullifier: externalNullifierRes.get() + )) +else: + proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] = + let externalNullifierRes = poseidon(@[@(proof.epoch), + @(proof.rlnIdentifier)]) + if externalNullifierRes.isErr(): + return err("could not construct the external nullifier") + return ok(ProofMetadata( + nullifier: proof.nullifier, + shareX: proof.shareX, + shareY: proof.shareY, + externalNullifier: externalNullifierRes.get() + )) -proc proofGen*(rlnInstance: ptr RLN, data: openArray[byte], +when defined(rln_v2): + proc proofGen*(rlnInstance: ptr RLN, + data: openArray[byte], + membership: IdentityCredential, + userMessageLimit: UserMessageLimit, + messageId: MessageId, + index: MembershipIndex, + epoch: Epoch): RateLimitProofResult = + + # obtain the external nullifier + let externalNullifierRes = poseidon(@[@(epoch), + @(DefaultRlnIdentifier)]) + + if externalNullifierRes.isErr(): + return err("could not construct the external nullifier") + + # serialize inputs + let serializedInputs = serialize(idSecretHash = membership.idSecretHash, + memIndex = index, + userMessageLimit = userMessageLimit, + messageId = messageId, + externalNullifier = externalNullifierRes.get(), + msg = data) + var inputBuffer = toBuffer(serializedInputs) + + debug "input buffer ", inputBuffer= repr(inputBuffer) + + # generate the proof + var proof: Buffer + let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof) + # check whether the generate_proof call is done successfully + if not proofIsSuccessful: + return err("could not generate the proof") + + var proofValue = cast[ptr array[320, byte]] (proof.`ptr`) + let proofBytes: array[320, byte] = proofValue[] + debug "proof content", proofHex = proofValue[].toHex + + ## parse the proof as [ proof<128> | root<32> | external_nullifier<32> | share_x<32> | share_y<32> | nullifier<32> ] + + let + proofOffset = 128 + rootOffset = proofOffset + 32 + externalNullifierOffset = rootOffset + 32 + shareXOffset = externalNullifierOffset + 32 + shareYOffset = shareXOffset + 32 + nullifierOffset = shareYOffset + 32 + + var + zkproof: ZKSNARK + proofRoot, shareX, shareY: MerkleNode + externalNullifier: ExternalNullifier + nullifier: Nullifier + + discard zkproof.copyFrom(proofBytes[0..proofOffset-1]) + discard proofRoot.copyFrom(proofBytes[proofOffset..rootOffset-1]) + discard externalNullifier.copyFrom(proofBytes[rootOffset..externalNullifierOffset-1]) + discard shareX.copyFrom(proofBytes[externalNullifierOffset..shareXOffset-1]) + discard shareY.copyFrom(proofBytes[shareXOffset..shareYOffset-1]) + discard nullifier.copyFrom(proofBytes[shareYOffset..nullifierOffset-1]) + + let output = RateLimitProof(proof: zkproof, + merkleRoot: proofRoot, + externalNullifier: externalNullifier, + shareX: shareX, + shareY: shareY, + nullifier: nullifier) + return ok(output) +else: + proc proofGen*(rlnInstance: ptr RLN, data: openArray[byte], memKeys: IdentityCredential, memIndex: MembershipIndex, epoch: Epoch): RateLimitProofResult = - # serialize inputs - let serializedInputs = serialize(idSecretHash = memKeys.idSecretHash, - memIndex = memIndex, - epoch = epoch, - msg = data) - var inputBuffer = toBuffer(serializedInputs) + # serialize inputs + let serializedInputs = serialize(idSecretHash = memKeys.idSecretHash, + memIndex = memIndex, + epoch = epoch, + msg = data) + var inputBuffer = toBuffer(serializedInputs) - debug "input buffer ", inputBuffer= repr(inputBuffer) + debug "input buffer ", inputBuffer= repr(inputBuffer) - # generate the proof - var proof: Buffer - let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof) - # check whether the generate_proof call is done successfully - if not proofIsSuccessful: - return err("could not generate the proof") + # generate the proof + var proof: Buffer + let proofIsSuccessful = generate_proof(rlnInstance, addr inputBuffer, addr proof) + # check whether the generate_proof call is done successfully + if not proofIsSuccessful: + return err("could not generate the proof") - var proofValue = cast[ptr array[320, byte]] (proof.`ptr`) - let proofBytes: array[320, byte] = proofValue[] - debug "proof content", proofHex = proofValue[].toHex + var proofValue = cast[ptr array[320, byte]] (proof.`ptr`) + let proofBytes: array[320, byte] = proofValue[] + debug "proof content", proofHex = proofValue[].toHex - ## parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ] + ## parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ] - let - proofOffset = 128 - rootOffset = proofOffset + 32 - epochOffset = rootOffset + 32 - shareXOffset = epochOffset + 32 - shareYOffset = shareXOffset + 32 - nullifierOffset = shareYOffset + 32 - rlnIdentifierOffset = nullifierOffset + 32 + let + proofOffset = 128 + rootOffset = proofOffset + 32 + epochOffset = rootOffset + 32 + shareXOffset = epochOffset + 32 + shareYOffset = shareXOffset + 32 + nullifierOffset = shareYOffset + 32 + rlnIdentifierOffset = nullifierOffset + 32 - var - zkproof: ZKSNARK - proofRoot, shareX, shareY: MerkleNode - epoch: Epoch - nullifier: Nullifier - rlnIdentifier: RlnIdentifier + var + zkproof: ZKSNARK + proofRoot, shareX, shareY: MerkleNode + epoch: Epoch + nullifier: Nullifier + rlnIdentifier: RlnIdentifier - discard zkproof.copyFrom(proofBytes[0..proofOffset-1]) - discard proofRoot.copyFrom(proofBytes[proofOffset..rootOffset-1]) - discard epoch.copyFrom(proofBytes[rootOffset..epochOffset-1]) - discard shareX.copyFrom(proofBytes[epochOffset..shareXOffset-1]) - discard shareY.copyFrom(proofBytes[shareXOffset..shareYOffset-1]) - discard nullifier.copyFrom(proofBytes[shareYOffset..nullifierOffset-1]) - discard rlnIdentifier.copyFrom(proofBytes[nullifierOffset..rlnIdentifierOffset-1]) + discard zkproof.copyFrom(proofBytes[0..proofOffset-1]) + discard proofRoot.copyFrom(proofBytes[proofOffset..rootOffset-1]) + discard epoch.copyFrom(proofBytes[rootOffset..epochOffset-1]) + discard shareX.copyFrom(proofBytes[epochOffset..shareXOffset-1]) + discard shareY.copyFrom(proofBytes[shareXOffset..shareYOffset-1]) + discard nullifier.copyFrom(proofBytes[shareYOffset..nullifierOffset-1]) + discard rlnIdentifier.copyFrom(proofBytes[nullifierOffset..rlnIdentifierOffset-1]) - let output = RateLimitProof(proof: zkproof, - merkleRoot: proofRoot, - epoch: epoch, - shareX: shareX, - shareY: shareY, - nullifier: nullifier, - rlnIdentifier: rlnIdentifier) + let output = RateLimitProof(proof: zkproof, + merkleRoot: proofRoot, + epoch: epoch, + shareX: shareX, + shareY: shareY, + nullifier: nullifier, + rlnIdentifier: rlnIdentifier) - return ok(output) + return ok(output) # validRoots should contain a sequence of roots in the acceptable windows. # As default, it is set to an empty sequence of roots. This implies that the validity check for the proof's root is skipped