From b24bc975da8f69b5493d8d8291e4615f53e3259e Mon Sep 17 00:00:00 2001 From: Prem Chaitanya Prathi Date: Fri, 26 Jun 2026 10:52:30 +0530 Subject: [PATCH] feat(mix): decouple libp2p peerId from RLN membership The node keeps its own (random) peerId and loads the SELECTED membership credential by copying rlnKeystoreSource -> rln_keystore_.json before mountMix. Lets many instances share an RLN membership without colliding on a peerId. New config field rlnKeystoreSource (parsed in client_api). --- library/api/client_api.nim | 4 ++++ src/chat/delivery/waku_client.nim | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/library/api/client_api.nim b/library/api/client_api.nim index a82b96c..c747ac4 100644 --- a/library/api/client_api.nim +++ b/library/api/client_api.nim @@ -64,6 +64,10 @@ proc createChatClient( wakuCfg.kadBootstrapNodes = @[] for node in config["kadBootstrapNodes"]: wakuCfg.kadBootstrapNodes.add(node.getStr()) + # Path to the chosen RLN membership keystore. The node loads this credential under + # its OWN (random) peerId, decoupling the libp2p identity from the membership. + if config.hasKey("rlnKeystoreSource"): + wakuCfg.rlnKeystoreSource = config["rlnKeystoreSource"].getStr() if config.hasKey("minMixPoolSize"): wakuCfg.minMixPoolSize = config["minMixPoolSize"].getInt(4) # Adopt a fixed identity (e.g. a provisioned mix-sim chat credential) so the diff --git a/src/chat/delivery/waku_client.nim b/src/chat/delivery/waku_client.nim index 25a52f3..2049e4b 100644 --- a/src/chat/delivery/waku_client.nim +++ b/src/chat/delivery/waku_client.nim @@ -42,6 +42,7 @@ import ], mix_rln_spam_protection/spam_protection +from std/os import copyFile logScope: topics = "chat waku" @@ -108,6 +109,7 @@ type WakuConfig* = object mixEnabled*: bool mixNodes*: seq[string] kadBootstrapNodes*: seq[string] + rlnKeystoreSource*: string minMixPoolSize*: int type @@ -316,6 +318,22 @@ proc start*(client: WakuClient) {.async.} = quit(QuitFailure) let mixNodeInfos = parseMixNodes(client.cfg.mixNodes) client.node.mountLightPushClient() + + # Decouple the libp2p peerId from the RLN membership: the node keeps its own + # (random) peerId, but loads the SELECTED membership credential. The mix-RLN + # plugin looks up rln_keystore_.json in cwd, so stage the chosen + # credential under THIS node's peerId. The credential is peerId-independent (only + # the filename is peerId-keyed), so many nodes can share a membership without + # colliding on a peerId. + if client.cfg.rlnKeystoreSource.len > 0: + let pid = $client.node.peerManager.switch.peerInfo.peerId + try: + copyFile(client.cfg.rlnKeystoreSource, "rln_keystore_" & pid & ".json") + info "Staged RLN membership under node peerId", peerId = pid + except CatchableError as e: + error "Failed to stage RLN credential", + source = client.cfg.rlnKeystoreSource, err = e.msg + (await client.node.mountMix(client.cfg.clusterId, mixPrivKey, mixNodeInfos, disableSpamProtection = false)).isOkOr: error "Failed to mount mix protocol", error = $error