-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import | ||
group_manager/[static, on_chain] | ||
|
||
export | ||
static, | ||
on_chain |
66 changes: 66 additions & 0 deletions
66
waku/v2/protocol/waku_rln_relay/group_manager/group_manager_base.nim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# This module contains the GroupManagerBase interface | ||
# The GroupManager is responsible for managing the group state | ||
# It should be used to register new members, and withdraw existing members | ||
# It should also be used to sync the group state with the rest of the group members | ||
|
||
type OnRegisterCallback* = proc (registrations: seq[(IDCommitment, MembershipIndex)]) {.async, gcsafe.} | ||
type OnWithdrawCallback* = proc (withdrawals: seq[(IDCommitment, MembershipIndex)]) {.async, gcsafe.} | ||
|
||
type | ||
GroupManagerBase*[Config] = ref object of RootObj | ||
idCredentials*: Option[IdentityCredentials] | ||
onRegisterCb: Option[OnRegisterCallback] | ||
onWithdrawCb: Option[OnWithdrawCallback] | ||
config*: Config | ||
rlnInstance: ptr RLN | ||
initialized*: bool | ||
|
||
# This method is used to initialize the group manager | ||
# Any initialization logic should be implemented here | ||
method init*(g: GroupManagerBase): Result[void] {.async,base.} = | ||
return err("init method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method is used to start the group sync process | ||
# It should be used to sync the group state with the rest of the group members | ||
method startGroupSync*(g: GroupManagerBase): Result[void] {.async,base.} = | ||
return err("startGroupSync method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method 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: GroupManagerBase, idCommitment: IDCommitment): Result[void] {.async,base.} = | ||
return err("register method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method 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: GroupManagerBase, credentials: IdentityCredentials): Result[void] {.async,base.} = | ||
return err("register method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method 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: GroupManagerBase, idCommitments: seq[IDCommitment]): Result[void] {.async,base.} = | ||
return err("registerBatch method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method 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 | ||
method onRegister*(g: GroupManagerBase, cb: OnRegisterCallback): Result[void] {.base.} = | ||
g.onRegisterCb = some(cb) | ||
return ok() | ||
|
||
# This method is used to withdraw/remove an identity commitment from the merkle tree | ||
# The user should have the identity secret hash to this commitment, by either deriving it, or owning it | ||
method withdraw*(g: GroupManagerBase, identitySecretHash: IdentitySecretHash): Result[void] {.async,base.} = | ||
return err("withdraw method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method is used to withdraw/remove a batch of identity commitments from the merkle tree | ||
# The user should have the identity secret hash to these commitments, by either deriving them, or owning them | ||
method withdrawBatch*(g: GroupManagerBase, identitySecretHashes: seq[IdentitySecretHash]): Result[void] {.async,base.} = | ||
return err("withdrawBatch method for " & $g.kind & " is not implemented yet") | ||
|
||
# This method is used to set a callback that will be called when an identity commitment is withdrawn | ||
# The callback may be called multiple times, and should be used to for any post processing | ||
method onWithdraw*(g: GroupManagerBase, cb: OnRegisterCallback): Result[void] {.base.} = | ||
g.onWithdrawCb = some(cb) | ||
return ok() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import on_chain/group_manager | ||
|
||
export group_manager |
118 changes: 118 additions & 0 deletions
118
waku/v2/protocol/waku_rln_relay/group_manager/on_chain/group_manager.nim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import | ||
../group_manager_base | ||
|
||
type | ||
RlnContractWithSender = Sender[RlnContract] | ||
OnchainGroupManagerConfig* = object | ||
ethClientUrl*: string | ||
ethPrivateKey*: Option[string] | ||
ethContractAddress*: string | ||
ethRpc: Option[web3] | ||
rlnContract: Option[RlnContractWithSender] | ||
membershipFee: Option[Uint256] | ||
|
||
OnchainGroupManager* = ref object of GroupManagerBase[OnchainGroupManagerConfig] | ||
|
||
template initializedGuard*(g: OnchainGroupManager): untyped = | ||
if not g.initialized: | ||
return err("OnchainGroupManager is not initialized") | ||
|
||
method init*(g: OnchainGroupManager): Result[void] {.async.} = | ||
var web3: Web3 | ||
var contract: RlnContractWithSender | ||
# check if the Ethereum client is reachable | ||
try: | ||
web3 = some(await newWeb3(ethClientAddress)) | ||
except: | ||
return err("could not connect to the Ethereum client") | ||
|
||
contract = some(web3.contractSender(RlnContract, g.config.ethContractAddress)) | ||
|
||
# check if the contract exists by calling a static method | ||
var membershipFee: uint64 | ||
try: | ||
membershipFee = await contract.MEMBERSHIP_DEPOSIT() | ||
except: | ||
return err("could not get the membership deposit") | ||
|
||
if g.config.ethPrivateKey.isSome(): | ||
let pk = g.config.ethPrivateKey.get() | ||
web3.privateKey = pk | ||
|
||
g.config.ethRpc = some(web3) | ||
g.config.rlnContract = some(contract) | ||
g.config.membershipFee = some(membershipFee) | ||
|
||
g.initialized = true | ||
return ok() | ||
|
||
method startGroupSync*(g: OnchainGroupManager): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
if g.config.ethPrivateKey.isSome(): | ||
# TODO: use register() after generating credentials | ||
debug "registering commitment on contract" | ||
await g.register(g.config.idCredentials) | ||
|
||
# TODO: set up the contract event listener and block listener | ||
|
||
|
||
method register*(g: OnchainGroupManager, idCommitment: IDCommitment): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
let memberInserted = g.rlnInstance.insertMember(idCommitment) | ||
if not memberInserted: | ||
return err("Failed to insert member into the merkle tree") | ||
|
||
if g.onRegisterCb.isSome(): | ||
await g.onRegisterCb.get()(@[idCommitment]) | ||
|
||
return ok() | ||
|
||
method register*(g: OnchainGroupManager, identityCredentials: IdentityCredentials): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
# TODO: interact with the contract | ||
let ethRpc = g.config.ethRpc.get() | ||
let rlnContract = g.config.rlnContract.get() | ||
let membershipFee = g.config.membershipFee.get() | ||
|
||
let gasPrice = int(await ethRpc.provider.eth_gasPrice()) * 2 | ||
let idCommitment = identityCredentials.idCommitment.toUInt256() | ||
|
||
var txHash: TxHash | ||
try: # send the registration transaction and check if any error occurs | ||
txHash = await rlnContract.register(pk).send(value = membershipFee, gasPrice = gasPrice) | ||
except ValueError as e: | ||
return err("registration transaction failed: " & e.msg) | ||
|
||
let tsReceipt = await ethRpc.getMinedTransactionReceipt(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[0..65] != "0x5a92c2530f207992057b9c3e544108ffce3beda4a63719f316967c49bf6159d2": | ||
return err("invalid event signature hash") | ||
|
||
# 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.hexToSeqByte() | ||
# In TX log data, uints are encoded in big endian | ||
eventIndex = UInt256.fromBytesBE(argumentsBytes[32..^1]) | ||
|
||
# don't handle member insertion into the tree here, it will be handled by the event listener | ||
return ok() | ||
|
||
method withdraw*(g: OnchainGroupManager, idCommitment: IDCommitment): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
# TODO: after slashing is enabled on the contract | ||
|
||
method withdrawBatch*(g: OnchainGroupManager, idCommitments: seq[IDCommitment]): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
# TODO: after slashing is enabled on the contract |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import static/group_manager | ||
|
||
export group_manager |
97 changes: 97 additions & 0 deletions
97
waku/v2/protocol/waku_rln_relay/group_manager/static/group_manager.nim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import | ||
../group_manager_base | ||
|
||
type | ||
StaticGroupManagerConfig* = object | ||
rawGroupKeys*: seq[string] | ||
groupKeys*: Option[seq[IdentityCredentials]] | ||
groupSize*: uint | ||
membershipIndex*: MembershipIndex | ||
|
||
StaticGroupManager* = ref object of GroupManagerBase[StaticGroupManagerConfig] | ||
|
||
template initializedGuard*(g: StaticGroupManager): untyped = | ||
if not g.initialized: | ||
return err("StaticGroupManager is not initialized") | ||
|
||
method init*(g: StaticGroupManager): Result[void] = | ||
let | ||
rawGroupKeys = g.config.rawGroupKeys | ||
groupSize = g.config.groupSize | ||
membershipIndex = g.config.membershipIndex | ||
|
||
if membershipIndex < MembershipIndex(0) or membershipIndex >= MembershipIndex(groupSize): | ||
return err("Invalid membership index. Must be within 0 and " & $(groupSize - 1) & "but was " & $membershipIndex) | ||
|
||
let parsedGroupKeys = rawGroupKeys.map(parseGroupKey) | ||
if parsedGroupKeys.anyIt(it.isErr): | ||
return err("Invalid group key: " & $parsedGroupKeys.findIt(it.isErr).getErr()) | ||
|
||
g.config.groupKeys = some(parsedGroupKeys.mapIt(it.get())) | ||
g.idCredentials = g.config.groupKeys[membershipIndex] | ||
|
||
# Seed the received commitments into the merkle tree | ||
let membersInserted = g.rlnInstance.insertMembers(g.config.groupKeys.mapIt(it.idCommitment)) | ||
if not membersInserted: | ||
return err("Failed to insert members into the merkle tree") | ||
|
||
g.initialized = true | ||
return ok() | ||
|
||
method startGroupSync*(g: StaticGroupManager): Result[void] {.async.} = | ||
initializedGuard(g) | ||
# No-op | ||
return ok() | ||
|
||
method register*(g: StaticGroupManager, idCommitment: IDCommitment): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
let memberInserted = g.rlnInstance.insertMember(idCommitment) | ||
if not memberInserted: | ||
return err("Failed to insert member into the merkle tree") | ||
|
||
if g.onRegisterCb.isSome(): | ||
await g.onRegisterCb.get()(@[idCommitment]) | ||
|
||
return ok() | ||
|
||
|
||
method registerBatch*(g: StaticGroupManager, idCommitments: seq[IDCommitment]): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
let membersInserted = g.rlnInstance.insertMembers(idCommitments) | ||
if not membersInserted: | ||
return err("Failed to insert members into the merkle tree") | ||
|
||
if g.onRegisterCb.isSome(): | ||
await g.onRegisterCb.get()(idCommitments) | ||
|
||
return ok() | ||
|
||
method withdraw*(g: StaticGroupManager, idSecretHash: IdentitySecretHash): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
let groupKeys = g.config.groupKeys.get() | ||
let idCommitment = groupKeys.findIt(it.idSecretHash == idSecretHash).get().idCommitment | ||
let memberRemoved = g.rlnInstance.removeMember(idCommitment) | ||
if not memberRemoved: | ||
return err("Failed to remove member from the merkle tree") | ||
|
||
if g.onWithdrawCb.isSome(): | ||
await g.onWithdrawCb.get()(@[idCommitment]) | ||
|
||
return ok() | ||
|
||
method withdrawBatch*(g: StaticGroupManager, idSecretHashes: seq[IdentitySecretHash]): Result[void] {.async.} = | ||
initializedGuard(g) | ||
|
||
let groupKeys = g.config.groupKeys.get() | ||
let identityCommitments = idSecretHashes.map(groupKeys.findIt(it.idSecretHash == idSecretHash).get().idCommitment) | ||
let membersRemoved = g.rlnInstance.removeMembers(idCommitments) | ||
if not membersRemoved: | ||
return err("Failed to remove members from the merkle tree") | ||
|
||
if g.onWithdrawCb.isSome(): | ||
await g.onWithdrawCb.get()(idCommitments) | ||
|
||
return ok() |