Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rln-relay-v2): rln-keystore-generator updates #2392

Merged
merged 7 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/wakunode2/external_config.nim
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick comment: shall we add the units to make it clearer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 3be31f0

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
134 changes: 88 additions & 46 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 Down Expand Up @@ -135,45 +163,59 @@ 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 proofGenRes = proofGen(rlnInstance = g.rlnInstance,
data = data,
membership = g.idCredentials.get(),
userMessageLimit = g.userMessageLimit.get(),
messageId = messageId,
index = g.membershipIndex.get(),
epoch = epoch)
if proofGenRes.isErr():
return err("proof generation failed: " & $proofGenRes.error())
return ok(proofGenRes.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())


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 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())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR but the valueOr and isOkOr result statements are very interesting

Suggested change
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())
let proofGen = proofGen(rlnInstance = g.rlnInstance,
data = data,
memKeys = g.idCredentials.get(),
memIndex = g.membershipIndex.get(),
epoch = epoch).valueOr:
return err("proof generation failed: " & $error)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in ea8dd11

return ok(proofGenRes.value())

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