Skip to content

Commit

Permalink
feat(rln-relay-v2): rln-keystore-generator updates (#2392)
Browse files Browse the repository at this point in the history
* chore: init rln-v2 in OnchainGroupManager

* chore: update wrappers

* fix: units for userMessageLimit

* valueOr for error handling

* fix: len usage
  • Loading branch information
rymnc authored Feb 9, 2024
1 parent a81092e commit 2d46c35
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 224 deletions.
5 changes: 5 additions & 0 deletions apps/wakunode2/external_config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ type
desc: "Private key for broadcasting transactions",
defaultValue: "",
name: "rln-relay-eth-private-key" }: string

rlnRelayUserMessageLimit* {.
desc: "Set a user message limit for the rln membership registration. Must be a positive integer. Default is 1.",
defaultValue: 1,
name: "rln-relay-user-message-limit" .}: uint64

maxMessageSize* {.
desc: "Maximum message size. Accepted units: KiB, KB, and B. e.g. 1024KiB; 1500 B; etc."
Expand Down
34 changes: 25 additions & 9 deletions tools/rln_keystore_generator/rln_keystore_generator.nim
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) =

# 5. register on-chain
try:
waitFor groupManager.register(credential)
when defined(rln_v2):
waitFor groupManager.register(credential, conf.rlnRelayUserMessageLimit)
else:
waitFor groupManager.register(credential)
except Exception, CatchableError:
error "failure while registering credentials on-chain", error=getCurrentExceptionMsg()
quit(1)
Expand All @@ -73,16 +76,29 @@ proc doRlnKeystoreGenerator*(conf: WakuNodeConf) =
info "Your membership has been registered on-chain.", chainId = $groupManager.chainId.get(),
contractAddress = conf.rlnRelayEthContractAddress,
membershipIndex = groupManager.membershipIndex.get()
when defined(rln_v2):
info "Your user message limit is", userMessageLimit = conf.rlnRelayUserMessageLimit

# 6. write to keystore
let keystoreCred = KeystoreMembership(
membershipContract: MembershipContract(
chainId: $groupManager.chainId.get(),
address: conf.rlnRelayEthContractAddress,
),
treeIndex: groupManager.membershipIndex.get(),
identityCredential: credential,
)
when defined(rln_v2):
let keystoreCred = KeystoreMembership(
membershipContract: MembershipContract(
chainId: $groupManager.chainId.get(),
address: conf.rlnRelayEthContractAddress,
),
treeIndex: groupManager.membershipIndex.get(),
identityCredential: credential,
userMessageLimit: conf.rlnRelayUserMessageLimit,
)
else:
let keystoreCred = KeystoreMembership(
membershipContract: MembershipContract(
chainId: $groupManager.chainId.get(),
address: conf.rlnRelayEthContractAddress,
),
treeIndex: groupManager.membershipIndex.get(),
identityCredential: credential,
)

let persistRes = addMembershipCredentials(conf.rlnRelayCredPath,
keystoreCred,
Expand Down
42 changes: 29 additions & 13 deletions waku/waku_keystore/protocol_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ proc toIDCommitment*(idCommitmentUint: UInt256): IDCommitment =
type MembershipIndex* = uint

proc toMembershipIndex*(v: UInt256): MembershipIndex =
let membershipIndex: MembershipIndex = cast[MembershipIndex](v)
return membershipIndex
return cast[MembershipIndex](v)

# Converts a sequence of tuples containing 4 string (i.e. identity trapdoor, nullifier, secret hash and commitment) to an IndentityCredential
type RawMembershipCredentials* = (string, string, string, string)
Expand Down Expand Up @@ -93,18 +92,35 @@ type KeystoreMembership* = ref object of RootObj
membershipContract*: MembershipContract
treeIndex*: MembershipIndex
identityCredential*: IdentityCredential
when defined(rln_v2):
userMessageLimit*: uint64

proc `$`*(m: KeystoreMembership): string =
return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", identityCredential: " & $m.identityCredential & ")"

proc `==`*(x, y: KeystoreMembership): bool =
return x.membershipContract.chainId == y.membershipContract.chainId and
x.membershipContract.address == y.membershipContract.address and
x.treeIndex == y.treeIndex and
x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and
x.identityCredential.idNullifier == y.identityCredential.idNullifier and
x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and
x.identityCredential.idCommitment == y.identityCredential.idCommitment
when defined(rln_v2):
proc `$`*(m: KeystoreMembership): string =
return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", userMessageLimit: " & $m.userMessageLimit & ", identityCredential: " & $m.identityCredential & ")"
else:
proc `$`*(m: KeystoreMembership): string =
return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", identityCredential: " & $m.identityCredential & ")"

when defined(rln_v2):
proc `==`*(x, y: KeystoreMembership): bool =
return x.membershipContract.chainId == y.membershipContract.chainId and
x.membershipContract.address == y.membershipContract.address and
x.treeIndex == y.treeIndex and
x.userMessageLimit == y.userMessageLimit and
x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and
x.identityCredential.idNullifier == y.identityCredential.idNullifier and
x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and
x.identityCredential.idCommitment == y.identityCredential.idCommitment
else:
proc `==`*(x, y: KeystoreMembership): bool =
return x.membershipContract.chainId == y.membershipContract.chainId and
x.membershipContract.address == y.membershipContract.address and
x.treeIndex == y.treeIndex and
x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and
x.identityCredential.idNullifier == y.identityCredential.idNullifier and
x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and
x.identityCredential.idCommitment == y.identityCredential.idCommitment

proc hash*(m: KeystoreMembership): string =
# hash together the chainId, address and treeIndex
Expand Down
4 changes: 4 additions & 0 deletions waku/waku_rln_relay/conversion_utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ proc inHex*(value: IdentityTrapdoor or
valueHex = "0" & valueHex
return toLowerAscii(valueHex)

when defined(rln_v2):
proc toUserMessageLimit*(v: UInt256): UserMessageLimit =
return cast[UserMessageLimit](v)

proc encodeLengthPrefix*(input: openArray[byte]): seq[byte] =
## returns length prefixed version of the input
## with the following format [len<8>|input<var>]
Expand Down
132 changes: 85 additions & 47 deletions waku/waku_rln_relay/group_manager/group_manager_base.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ export
# It should also be used to sync the group state with the rest of the group members

type Membership* = object
idCommitment*: IDCommitment
index*: MembershipIndex
when defined(rln_v2):
rateCommitment*: RateCommitment
else:
idCommitment*: IDCommitment

type OnRegisterCallback* = proc (registrations: seq[Membership]): Future[void] {.gcsafe.}
type OnWithdrawCallback* = proc (withdrawals: seq[Membership]): Future[void] {.gcsafe.}
Expand All @@ -41,6 +44,8 @@ type
initialized*: bool
latestIndex*: MembershipIndex
validRoots*: Deque[MerkleNode]
when defined(rln_v2):
userMessageLimit*: Option[UserMessageLimit]

# This proc is used to initialize the group manager
# Any initialization logic should be implemented here
Expand All @@ -55,20 +60,35 @@ method startGroupSync*(g: GroupManager): Future[void] {.base, async: (raises: [E
# This proc is used to register a new identity commitment into the merkle tree
# The user may or may not have the identity secret to this commitment
# It should be used when detecting new members in the group, and syncing the group state
method register*(g: GroupManager, idCommitment: IDCommitment): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")
when defined(rln_v2):
method register*(g: GroupManager,
rateCommitment: RateCommitment): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")
else:
method register*(g: GroupManager, idCommitment: IDCommitment): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")

# This proc is used to register a new identity commitment into the merkle tree
# The user should have the identity secret to this commitment
# It should be used when the user wants to join the group
method register*(g: GroupManager, credentials: IdentityCredential): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")
when defined(rln_v2):
method register*(g: GroupManager,
credentials: IdentityCredential,
userMessageLimit: UserMessageLimit): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")
else:
method register*(g: GroupManager, credentials: IdentityCredential): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "register proc for " & $g.type & " is not implemented yet")

# This proc is used to register a batch of new identity commitments into the merkle tree
# The user may or may not have the identity secret to these commitments
# It should be used when detecting a batch of new members in the group, and syncing the group state
method registerBatch*(g: GroupManager, idCommitments: seq[IDCommitment]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet")
when defined(rln_v2):
method registerBatch*(g: GroupManager, rateCommitments: seq[RateCommitment]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet")
else:
method registerBatch*(g: GroupManager, idCommitments: seq[IDCommitment]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "registerBatch proc for " & $g.type & " is not implemented yet")

# This proc is used to set a callback that will be called when a new identity commitment is registered
# The callback may be called multiple times, and should be used to for any post processing
Expand All @@ -86,8 +106,16 @@ method withdrawBatch*(g: GroupManager, identitySecretHashes: seq[IdentitySecretH
raise newException(CatchableError, "withdrawBatch proc for " & $g.type & " is not implemented yet")

# This proc is used to insert and remove a set of commitments from the merkle tree
method atomicBatch*(g: GroupManager, idCommitments: seq[IDCommitment], toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet")
when defined(rln_v2):
method atomicBatch*(g: GroupManager,
rateCommitments: seq[RateCommitment],
toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet")
else:
method atomicBatch*(g: GroupManager,
idCommitments: seq[IDCommitment],
toRemoveIndices: seq[MembershipIndex]): Future[void] {.base,async: (raises: [Exception]).} =
raise newException(CatchableError, "atomicBatch proc for " & $g.type & " is not implemented yet")

method stop*(g: GroupManager): Future[void] {.base,async.} =
raise newException(CatchableError, "stop proc for " & $g.type & " is not implemented yet")
Expand All @@ -99,7 +127,7 @@ method onWithdraw*(g: GroupManager, cb: OnWithdrawCallback) {.base,gcsafe.} =

proc slideRootQueue*(rootQueue: var Deque[MerkleNode], root: MerkleNode): seq[MerkleNode] =
## updates the root queue with the latest root and pops the oldest one when the capacity of `AcceptableRootWindowSize` is reached
let overflowCount = rootQueue.len() - AcceptableRootWindowSize + 1
let overflowCount = rootQueue.len - AcceptableRootWindowSize + 1
var overflowedRoots = newSeq[MerkleNode]()
if overflowCount > 0:
# Delete the oldest `overflowCount` roots in the deque (index 0..`overflowCount`)
Expand Down Expand Up @@ -135,45 +163,55 @@ template slideRootQueue*(g: GroupManager): untyped =
discard rootBuffer.slideRootQueue(root)
rootBuffer

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())

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())
method generateProof*(g: GroupManager,
data: openArray[byte],
epoch: Epoch,
messageId: MessageId,
rlnIdentifier = DefaultRlnIdentifier): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} =
## generates a proof for the given data and epoch
## the proof is generated using the current merkle root
if g.idCredentials.isNone():
return err("identity credentials are not set")
if g.membershipIndex.isNone():
return err("membership index is not set")
if g.userMessageLimit.isNone():
return err("user message limit is not set")
waku_rln_proof_generation_duration_seconds.nanosecondTime:
let proof = proofGen(rlnInstance = g.rlnInstance,
data = data,
memKeys = g.idCredentials.get(),
memIndex = g.membershipIndex.get(),
epoch = epoch).valueOr:
return err("proof generation failed: " & $error)
return ok(proof)
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())


method generateProof*(g: GroupManager,
data: openArray[byte],
epoch: Epoch): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} =
## generates a proof for the given data and epoch
## the proof is generated using the current merkle root
if g.idCredentials.isNone():
return err("identity credentials are not set")
if g.membershipIndex.isNone():
return err("membership index is not set")
waku_rln_proof_generation_duration_seconds.nanosecondTime:
let proofGenRes = proofGen(rlnInstance = g.rlnInstance,
data = data,
memKeys = g.idCredentials.get(),
memIndex = g.membershipIndex.get(),
epoch = epoch)
if proofGenRes.isErr():
return err("proof generation failed: " & $proofGenRes.error())
return ok(proofGenRes.value())
method generateProof*(g: GroupManager,
data: openArray[byte],
epoch: Epoch): GroupManagerResult[RateLimitProof] {.base,gcsafe,raises:[].} =
## generates a proof for the given data and epoch
## the proof is generated using the current merkle root
if g.idCredentials.isNone():
return err("identity credentials are not set")
if g.membershipIndex.isNone():
return err("membership index is not set")
waku_rln_proof_generation_duration_seconds.nanosecondTime:
let proof = proofGen(rlnInstance = g.rlnInstance,
data = data,
memKeys = g.idCredentials.get(),
memIndex = g.membershipIndex.get(),
epoch = epoch).valueOr:
return err("proof generation failed: " & $error)
return ok(proof)

method isReady*(g: GroupManager): Future[bool] {.base,async.} =
raise newException(CatchableError, "isReady proc for " & $g.type & " is not implemented yet")
Loading

0 comments on commit 2d46c35

Please sign in to comment.