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

* chore: init rln-v2 in OnchainGroupManager

* chore: update wrappers

* fix: units for userMessageLimit

* valueOr for error handling

* fix: len usage
This commit is contained in:
Aaryamann Challani 2024-02-09 16:31:45 +05:30 committed by GitHub
parent a81092e952
commit 2d46c35117
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 507 additions and 216 deletions

View File

@ -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."

View File

@ -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)
@ -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,

View File

@ -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)
@ -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 & ")"
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 & ")"
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 `==`*(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

View File

@ -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>]

View File

@ -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.}
@ -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
@ -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
@ -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")
@ -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`)
@ -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")

View File

@ -8,6 +8,7 @@ import
web3/ethtypes,
eth/keys as keys,
chronicles,
nimcrypto/keccak,
stint,
json,
std/tables,
@ -28,28 +29,54 @@ export group_manager_base
logScope:
topics = "waku rln_relay onchain_group_manager"
contract(WakuRlnRegistry):
# this describes the storage slot to use
proc usingStorageIndex(): Uint16 {.pure.}
# this map contains the address of a given storage slot
proc storages(index: Uint16): Address {.pure.}
# this serves as an entrypoint into the rln storage contract
proc register(storageIndex: Uint16, idCommitment: Uint256)
# this creates a new storage on the rln registry
proc newStorage()
# using the when predicate does not work within the contract macro, hence need to dupe
when defined(rln_v2):
contract(WakuRlnRegistry):
# this describes the storage slot to use
proc usingStorageIndex(): Uint16 {.pure.}
# this map contains the address of a given storage slot
proc storages(index: Uint16): Address {.pure.}
# this serves as an entrypoint into the rln storage contract
proc register(storageIndex: Uint16, idCommitment: Uint256, userMessageLimit: Uint256)
# this creates a new storage on the rln registry
proc newStorage()
# membership contract interface
contract(RlnStorage):
# this event is raised when a new member is registered
proc MemberRegistered(idCommitment: Uint256, userMessageLimit: Uint256, index: Uint256) {.event.}
# this constant contains the membership deposit of the contract
proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.}
# this map denotes existence of a given user
proc memberExists(idCommitment: Uint256): Uint256 {.view.}
# this constant describes the next index of a new member
proc idCommitmentIndex(): Uint256 {.view.}
# this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): Uint256 {.view.}
else:
contract(WakuRlnRegistry):
# this describes the storage slot to use
proc usingStorageIndex(): Uint16 {.pure.}
# this map contains the address of a given storage slot
proc storages(index: Uint16): Address {.pure.}
# this serves as an entrypoint into the rln storage contract
proc register(storageIndex: Uint16, idCommitment: Uint256)
# this creates a new storage on the rln registry
proc newStorage()
# membership contract interface
contract(RlnStorage):
# this event is raised when a new member is registered
proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.}
# this constant contains the membership deposit of the contract
proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.}
# this map denotes existence of a given user
proc memberExists(idCommitment: Uint256): Uint256 {.view.}
# this constant describes the next index of a new member
proc idCommitmentIndex(): Uint256 {.view.}
# this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): Uint256 {.view.}
# membership contract interface
contract(RlnStorage):
# this event is raised when a new member is registered
proc MemberRegistered(idCommitment: Uint256, index: Uint256) {.event.}
# this constant contains the membership deposit of the contract
proc MEMBERSHIP_DEPOSIT(): Uint256 {.pure.}
# this map denotes existence of a given user
proc memberExists(idCommitment: Uint256): Uint256 {.view.}
# this constant describes the next index of a new member
proc idCommitmentIndex(): Uint256 {.view.}
# this constant describes the block number this contract was deployed on
proc deployedBlockNumber(): Uint256 {.view.}
type
RegistryContractWithSender = Sender[WakuRlnRegistry]
@ -100,96 +127,197 @@ proc setMetadata*(g: OnchainGroupManager): RlnRelayResult[void] =
return err("failed to persist rln metadata: " & getCurrentExceptionMsg())
return ok()
method atomicBatch*(g: OnchainGroupManager,
start: MembershipIndex,
idCommitments = newSeq[IDCommitment](),
toRemoveIndices = newSeq[MembershipIndex]()):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
waku_rln_membership_insertion_duration_seconds.nanosecondTime:
let operationSuccess = g.rlnInstance.atomicWrite(some(start), idCommitments, toRemoveIndices)
if not operationSuccess:
raise newException(ValueError, "atomic batch operation failed")
# TODO: when slashing is enabled, we need to track slashed members
waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet()))
if g.registerCb.isSome():
var membersSeq = newSeq[Membership]()
for i in 0 ..< idCommitments.len():
var index = start + MembershipIndex(i)
trace "registering member", idCommitment = idCommitments[i], index = index
let member = Membership(idCommitment: idCommitments[i], index: index)
membersSeq.add(member)
await g.registerCb.get()(membersSeq)
g.validRootBuffer = g.slideRootQueue()
let setMetadataRes = g.setMetadata()
if setMetadataRes.isErr():
error "failed to persist rln metadata", error=setMetadataRes.error
method register*(g: OnchainGroupManager, idCommitment: IDCommitment):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
await g.registerBatch(@[idCommitment])
method registerBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]):
when defined(rln_v2):
method atomicBatch*(g: OnchainGroupManager,
start: MembershipIndex,
rateCommitments = newSeq[RateCommitment](),
toRemoveIndices = newSeq[MembershipIndex]()):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
initializedGuard(g)
await g.atomicBatch(g.latestIndex, idCommitments)
g.latestIndex += MembershipIndex(idCommitments.len())
# convert the rateCommitment struct to a leaf value
let leavesRes = rateCommitments.toLeaves()
if leavesRes.isErr():
raise newException(CatchableError, "failed to convert rateCommitments to leaves: " & leavesRes.error)
let leaves = cast[seq[seq[byte]]](leavesRes.get())
waku_rln_membership_insertion_duration_seconds.nanosecondTime:
let operationSuccess = g.rlnInstance.atomicWrite(some(start),
leaves,
toRemoveIndices)
if not operationSuccess:
raise newException(CatchableError, "atomic batch operation failed")
# TODO: when slashing is enabled, we need to track slashed members
waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet()))
if g.registerCb.isSome():
var membersSeq = newSeq[Membership]()
for i in 0 ..< rateCommitments.len:
var index = start + MembershipIndex(i)
trace "registering member", rateCommitment = rateCommitments[i], index = index
let member = Membership(rateCommitment: rateCommitments[i], index: index)
membersSeq.add(member)
await g.registerCb.get()(membersSeq)
g.validRootBuffer = g.slideRootQueue()
let setMetadataRes = g.setMetadata()
if setMetadataRes.isErr():
error "failed to persist rln metadata", error=setMetadataRes.error
else:
method atomicBatch*(g: OnchainGroupManager,
start: MembershipIndex,
idCommitments = newSeq[IDCommitment](),
toRemoveIndices = newSeq[MembershipIndex]()):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
waku_rln_membership_insertion_duration_seconds.nanosecondTime:
let operationSuccess = g.rlnInstance.atomicWrite(some(start), idCommitments, toRemoveIndices)
if not operationSuccess:
raise newException(ValueError, "atomic batch operation failed")
# TODO: when slashing is enabled, we need to track slashed members
waku_rln_number_registered_memberships.set(int64(g.rlnInstance.leavesSet()))
if g.registerCb.isSome():
var membersSeq = newSeq[Membership]()
for i in 0 ..< idCommitments.len:
var index = start + MembershipIndex(i)
trace "registering member", idCommitment = idCommitments[i], index = index
let member = Membership(idCommitment: idCommitments[i], index: index)
membersSeq.add(member)
await g.registerCb.get()(membersSeq)
g.validRootBuffer = g.slideRootQueue()
let setMetadataRes = g.setMetadata()
if setMetadataRes.isErr():
error "failed to persist rln metadata", error=setMetadataRes.error
when defined(rln_v2):
method register*(g: OnchainGroupManager,
rateCommitment: RateCommitment): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
await g.registerBatch(@[rateCommitment])
else:
method register*(g: OnchainGroupManager,
idCommitment: IDCommitment): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
await g.registerBatch(@[idCommitment])
method register*(g: OnchainGroupManager, identityCredentials: IdentityCredential):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
when defined(rln_v2):
method registerBatch*(g: OnchainGroupManager,
rateCommitments: seq[RateCommitment]): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let ethRpc = g.ethRpc.get()
let registryContract = g.registryContract.get()
let membershipFee = g.membershipFee.get()
await g.atomicBatch(g.latestIndex, rateCommitments)
g.latestIndex += MembershipIndex(rateCommitments.len)
else:
method registerBatch*(g: OnchainGroupManager,
idCommitments: seq[IDCommitment]): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
var gasPrice: int
retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"):
int(await ethRpc.provider.eth_gasPrice()) * 2
let idCommitment = identityCredentials.idCommitment.toUInt256()
await g.atomicBatch(g.latestIndex, idCommitments)
g.latestIndex += MembershipIndex(idCommitments.len)
var txHash: TxHash
let storageIndex = g.usingStorageIndex.get()
debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex
retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"):
await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice)
# wait for the transaction to be mined
var tsReceipt: ReceiptObject
retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"):
await ethRpc.getMinedTransactionReceipt(txHash)
debug "registration transaction mined", txHash = txHash
g.registrationTxHash = some(txHash)
# the receipt topic holds the hash of signature of the raised events
# TODO: make this robust. search within the event list for the event
let firstTopic = tsReceipt.logs[0].topics[0]
# the hash of the signature of MemberRegistered(uint256,uint256) event is equal to the following hex value
if firstTopic != cast[FixedBytes[32]](hexToByteArray[32](
"0x5a92c2530f207992057b9c3e544108ffce3beda4a63719f316967c49bf6159d2"
)):
raise newException(ValueError, "unexpected event signature")
when defined(rln_v2):
method register*(g: OnchainGroupManager,
identityCredential: IdentityCredential,
userMessageLimit: UserMessageLimit): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
# the arguments of the raised event i.e., MemberRegistered are encoded inside the data field
# data = pk encoded as 256 bits || index encoded as 256 bits
let arguments = tsReceipt.logs[0].data
debug "tx log data", arguments=arguments
let
argumentsBytes = arguments
# In TX log data, uints are encoded in big endian
eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1])
let ethRpc = g.ethRpc.get()
let registryContract = g.registryContract.get()
let membershipFee = g.membershipFee.get()
g.membershipIndex = some(eventIndex.toMembershipIndex())
var gasPrice: int
retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"):
int(await ethRpc.provider.eth_gasPrice()) * 2
let idCommitment = identityCredential.idCommitment.toUInt256()
# don't handle member insertion into the tree here, it will be handled by the event listener
return
var txHash: TxHash
let storageIndex = g.usingStorageIndex.get()
debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex, userMessageLimit = userMessageLimit
retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"):
await registryContract.register(storageIndex, idCommitment, u256(userMessageLimit)).send(gasPrice = gasPrice)
# wait for the transaction to be mined
var tsReceipt: ReceiptObject
retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"):
await ethRpc.getMinedTransactionReceipt(txHash)
debug "registration transaction mined", txHash = txHash
g.registrationTxHash = some(txHash)
# the receipt topic holds the hash of signature of the raised events
# TODO: make this robust. search within the event list for the event
let firstTopic = tsReceipt.logs[0].topics[0]
# the hash of the signature of MemberRegistered(uint256,uint256,uint256) event is equal to the following hex value
if firstTopic != cast[FixedBytes[32]](keccak256.digest("MemberRegistered(uint256,uint256,uint256)").data):
raise newException(ValueError, "unexpected event signature")
# the arguments of the raised event i.e., MemberRegistered are encoded inside the data field
# data = pk encoded as 256 bits || index encoded as 256 bits || userMessageLimit encoded as 256 bits
let arguments = tsReceipt.logs[0].data
debug "tx log data", arguments=arguments
let
argumentsBytes = arguments
# In TX log data, uints are encoded in big endian
userMessageLimit = UInt256.fromBytesBE(argumentsBytes[32..64])
membershipIndex = UInt256.fromBytesBE(argumentsBytes[64..^1])
g.membershipIndex = some(membershipIndex.toMembershipIndex())
g.userMessageLimit = some(userMessageLimit.toUserMessageLimit())
# don't handle member insertion into the tree here, it will be handled by the event listener
return
else:
method register*(g: OnchainGroupManager,
credentials: IdentityCredential): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let ethRpc = g.ethRpc.get()
let registryContract = g.registryContract.get()
let membershipFee = g.membershipFee.get()
var gasPrice: int
retryWrapper(gasPrice, RetryStrategy.new(), "Failed to get gas price"):
int(await ethRpc.provider.eth_gasPrice()) * 2
let idCommitment = credentials.idCommitment.toUInt256()
var txHash: TxHash
let storageIndex = g.usingStorageIndex.get()
debug "registering the member", idCommitment = idCommitment, storageIndex = storageIndex
retryWrapper(txHash, RetryStrategy.new(), "Failed to register the member"):
await registryContract.register(storageIndex, idCommitment).send(gasPrice = gasPrice)
# wait for the transaction to be mined
var tsReceipt: ReceiptObject
retryWrapper(tsReceipt, RetryStrategy.new(), "Failed to get the transaction receipt"):
await ethRpc.getMinedTransactionReceipt(txHash)
debug "registration transaction mined", txHash = txHash
g.registrationTxHash = some(txHash)
# the receipt topic holds the hash of signature of the raised events
# TODO: make this robust. search within the event list for the event
let firstTopic = tsReceipt.logs[0].topics[0]
# the hash of the signature of MemberRegistered(uint256,uint256) event is equal to the following hex value
if firstTopic != cast[FixedBytes[32]](keccak256.digest("MemberRegistered(uint256,uint256)").data):
raise newException(ValueError, "unexpected event signature")
# the arguments of the raised event i.e., MemberRegistered are encoded inside the data field
# data = pk encoded as 256 bits || index encoded as 256 bits
let arguments = tsReceipt.logs[0].data
debug "tx log data", arguments=arguments
let
argumentsBytes = arguments
# In TX log data, uints are encoded in big endian
eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1])
g.membershipIndex = some(eventIndex.toMembershipIndex())
# don't handle member insertion into the tree here, it will be handled by the event listener
return
method withdraw*(g: OnchainGroupManager, idCommitment: IDCommitment):
Future[void] {.async: (raises: [Exception]).} =
@ -209,6 +337,8 @@ proc parseEvent(event: type MemberRegistered,
## returns an error if it cannot parse the `data` parameter
var idComm: UInt256
var index: UInt256
when defined(rln_v2):
var userMessageLimit: UInt256
var data: string
# Remove the 0x prefix
try:
@ -221,7 +351,15 @@ proc parseEvent(event: type MemberRegistered,
offset += decode(data, offset, idComm)
# Parse the index
offset += decode(data, offset, index)
return ok(Membership(idCommitment: idComm.toIDCommitment(), index: index.toMembershipIndex()))
when defined(rln_v2):
# Parse the userMessageLimit
offset += decode(data, offset, userMessageLimit)
when defined(rln_v2):
return ok(Membership(rateCommitment: RateCommitment(idCommitment: idComm.toIDCommitment(),
userMessageLimit: userMessageLimit.toUserMessageLimit()),
index: index.toMembershipIndex()))
else:
return ok(Membership(idCommitment: idComm.toIDCommitment(), index: index.toMembershipIndex()))
except CatchableError:
return err("failed to parse the data field of the MemberRegistered event")
@ -294,15 +432,23 @@ proc handleEvents(g: OnchainGroupManager,
try:
let startIndex = blockTable[blockNumber].filterIt(not it[1])[0][0].index
let removalIndices = members.filterIt(it[1]).mapIt(it[0].index)
let idCommitments = members.mapIt(it[0].idCommitment)
await g.atomicBatch(start = startIndex,
idCommitments = idCommitments,
toRemoveIndices = removalIndices)
g.latestIndex = startIndex + MembershipIndex(idCommitments.len())
when defined(rln_v2):
let rateCommitments = members.mapIt(it[0].rateCommitment)
await g.atomicBatch(start = startIndex,
rateCommitments = rateCommitments,
toRemoveIndices = removalIndices)
g.latestIndex = startIndex + MembershipIndex(rateCommitments.len)
trace "new members added to the Merkle tree", commitments=rateCommitments
else:
let idCommitments = members.mapIt(it[0].idCommitment)
await g.atomicBatch(start = startIndex,
idCommitments = idCommitments,
toRemoveIndices = removalIndices)
g.latestIndex = startIndex + MembershipIndex(idCommitments.len)
trace "new members added to the Merkle tree", commitments=idCommitments
except CatchableError:
error "failed to insert members into the tree", error=getCurrentExceptionMsg()
raise newException(ValueError, "failed to insert members into the tree")
trace "new members added to the Merkle tree", commitments=members.mapIt(it[0].idCommitment.inHex())
return
@ -494,6 +640,8 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} =
raise newException(CatchableError, "could not parse the keystore: " & $keystoreCredRes.error)
let keystoreCred = keystoreCredRes.get()
g.membershipIndex = some(keystoreCred.treeIndex)
when defined(rln_v2):
g.userMessageLimit = some(keystoreCred.userMessageLimit)
# now we check on the contract if the commitment actually has a membership
try:
let membershipExists = await rlnContract.memberExists(keystoreCred

View File

@ -34,7 +34,7 @@ method init*(g: StaticGroupManager): Future[void] {.async.} =
discard g.slideRootQueue()
g.latestIndex += MembershipIndex(idCommitments.len() - 1)
g.latestIndex += MembershipIndex(idCommitments.len - 1)
g.initialized = true
@ -44,51 +44,90 @@ method startGroupSync*(g: StaticGroupManager): Future[void] {.async: (raises: [E
initializedGuard(g)
# No-op
method register*(g: StaticGroupManager, idCommitment: IDCommitment):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
when defined(rln_v2):
method register*(g: StaticGroupManager,
rateCommitment: RateCommitment): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
await g.registerBatch(@[idCommitment])
await g.registerBatch(@[rateCommitment])
else:
method register*(g: StaticGroupManager, idCommitment: IDCommitment):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
method registerBatch*(g: StaticGroupManager, idCommitments: seq[IDCommitment]):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
await g.registerBatch(@[idCommitment])
let membersInserted = g.rlnInstance.insertMembers(g.latestIndex + 1, idCommitments)
if not membersInserted:
raise newException(ValueError, "Failed to insert members into the merkle tree")
when defined(rln_v2):
method registerBatch*(g: StaticGroupManager,
rateCommitments: seq[RateCommitment]): Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
if g.registerCb.isSome():
var memberSeq = newSeq[Membership]()
for i in 0..<idCommitments.len():
memberSeq.add(Membership(idCommitment: idCommitments[i], index: g.latestIndex + MembershipIndex(i) + 1))
await g.registerCb.get()(memberSeq)
let leavesRes = rateCommitments.toLeaves()
if not leavesRes.isOk():
raise newException(ValueError, "Failed to convert rate commitments to leaves")
let leaves = cast[seq[seq[byte]]](leavesRes.get())
discard g.slideRootQueue()
let membersInserted = g.rlnInstance.insertMembers(g.latestIndex + 1, leaves)
if not membersInserted:
raise newException(ValueError, "Failed to insert members into the merkle tree")
g.latestIndex += MembershipIndex(idCommitments.len())
if g.registerCb.isSome():
var memberSeq = newSeq[Membership]()
for i in 0..<rateCommitments.len:
memberSeq.add(Membership(rateCommitment: rateCommitments[i], index: g.latestIndex + MembershipIndex(i) + 1))
await g.registerCb.get()(memberSeq)
return
discard g.slideRootQueue()
method withdraw*(g: StaticGroupManager, idSecretHash: IdentitySecretHash):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
g.latestIndex += MembershipIndex(rateCommitments.len)
let groupKeys = g.groupKeys
return
else:
method registerBatch*(g: StaticGroupManager, idCommitments: seq[IDCommitment]):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
for i in 0..<groupKeys.len():
if groupKeys[i].idSecretHash == idSecretHash:
let idCommitment = groupKeys[i].idCommitment
let index = MembershipIndex(i)
let memberRemoved = g.rlnInstance.removeMember(index)
if not memberRemoved:
raise newException(ValueError, "Failed to remove member from the merkle tree")
let membersInserted = g.rlnInstance.insertMembers(g.latestIndex + 1, idCommitments)
if not membersInserted:
raise newException(ValueError, "Failed to insert members into the merkle tree")
if g.withdrawCb.isSome():
await g.withdrawCb.get()(@[Membership(idCommitment: idCommitment, index: index)])
if g.registerCb.isSome():
var memberSeq = newSeq[Membership]()
for i in 0..<idCommitments.len:
memberSeq.add(Membership(idCommitment: idCommitments[i], index: g.latestIndex + MembershipIndex(i) + 1))
await g.registerCb.get()(memberSeq)
return
discard g.slideRootQueue()
g.latestIndex += MembershipIndex(idCommitments.len)
return
when defined(rln_v2):
method withdraw*(g: StaticGroupManager,
idSecretHash: IdentitySecretHash): Future[void] {.async: (raises: [Exception]).} =
# No-op
return
else:
method withdraw*(g: StaticGroupManager, idSecretHash: IdentitySecretHash):
Future[void] {.async: (raises: [Exception]).} =
initializedGuard(g)
let groupKeys = g.groupKeys
for i in 0..<groupKeys.len:
if groupKeys[i].idSecretHash == idSecretHash:
let idCommitment = groupKeys[i].idCommitment
let index = MembershipIndex(i)
let memberRemoved = g.rlnInstance.removeMember(index)
if not memberRemoved:
raise newException(ValueError, "Failed to remove member from the merkle tree")
if g.withdrawCb.isSome():
await g.withdrawCb.get()(@[Membership(idCommitment: idCommitment, index: index)])
return
method withdrawBatch*(g: StaticGroupManager, idSecretHashes: seq[IdentitySecretHash]):

View File

@ -37,6 +37,10 @@ when defined(rln_v2):
MessageId* = uint64
ExternalNullifier* = array[32, byte]
type RateCommitment* = object
idCommitment*: IDCommitment
userMessageLimit*: UserMessageLimit
# Custom data types defined for waku rln relay -------------------------
type RateLimitProof* = object
## RateLimitProof holds the public inputs to rln circuit as

View File

@ -161,6 +161,21 @@ proc poseidon*(data: seq[seq[byte]]): RlnRelayResult[array[32, byte]] =
return ok(output)
when defined(rln_v2):
func toLeaf*(rateCommitment: RateCommitment): RlnRelayResult[MerkleNode] {.inline.} =
let idCommitment = rateCommitment.idCommitment
let userMessageLimit = rateCommitment.userMessageLimit
let leafRes = poseidon(@[@idCommitment, cast[seq[byte]](userMessageLimit)])
return leafRes
func toLeaves*(rateCommitments: seq[RateCommitment]): RlnRelayResult[seq[MerkleNode]] {.inline.} =
var leaves = newSeq[MerkleNode](rateCommitments.len)
for rateCommitment in rateCommitments:
let leafRes = toLeaf(rateCommitment)
if leafRes.isErr():
return err("could not convert the rate commitment to a leaf: " & leafRes.error)
leaves.add(leafRes.get())
return ok(leaves)
# TODO: collocate this proc with the definition of the RateLimitProof
# and the ProofMetadata types
proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] =
@ -168,7 +183,7 @@ when defined(rln_v2):
nullifier: proof.nullifier,
shareX: proof.shareX,
shareY: proof.shareY,
externalNullifier: externalNullifierRes.get()
externalNullifier: proof.externalNullifier
))
else:
proc extractMetadata*(proof: RateLimitProof): RlnRelayResult[ProofMetadata] =
@ -190,22 +205,23 @@ when defined(rln_v2):
userMessageLimit: UserMessageLimit,
messageId: MessageId,
index: MembershipIndex,
epoch: Epoch): RateLimitProofResult =
epoch: Epoch,
rlnIdentifier = DefaultRlnIdentifier): RateLimitProofResult =
# obtain the external nullifier
let externalNullifierRes = poseidon(@[@(epoch),
@(DefaultRlnIdentifier)])
@(rlnIdentifier)])
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)
memIndex = index,
userMessageLimit = userMessageLimit,
messageId = messageId,
externalNullifier = externalNullifierRes.get(),
msg = data)
var inputBuffer = toBuffer(serializedInputs)
debug "input buffer ", inputBuffer= repr(inputBuffer)

View File

@ -78,6 +78,7 @@ type WakuRLNRelay* = ref object of RootObj
nullifierLog*: OrderedTable[Epoch, seq[ProofMetadata]]
lastEpoch*: Epoch # the epoch of the last published rln message
groupManager*: GroupManager
nonce*: uint64
method stop*(rlnPeer: WakuRLNRelay) {.async: (raises: [Exception]).} =
## stops the rln-relay protocol
@ -290,7 +291,11 @@ proc appendRLNProof*(rlnPeer: WakuRLNRelay,
let input = msg.toRLNSignal()
let epoch = calcEpoch(senderEpochTime)
let proofGenRes = rlnPeer.groupManager.generateProof(input, epoch)
when defined(rln_v2):
# TODO: add support for incrementing nonce, will address in another PR
let proofGenRes = rlnPeer.groupManager.generateProof(input, epoch, 1)
else:
let proofGenRes = rlnPeer.groupManager.generateProof(input, epoch)
if proofGenRes.isErr():
return false