2022-11-04 09:52:27 +00:00
when ( NimMajor , NimMinor ) < ( 1 , 4 ) :
{. push raises : [ Defect ] . }
else :
{. push raises : [ ] . }
2021-07-15 18:25:52 +00:00
2022-05-10 21:09:18 +00:00
import
2022-11-01 00:25:39 +00:00
std / [ sequtils , tables , times , os , deques ] ,
2021-03-23 08:04:51 +00:00
chronicles , options , chronos , stint ,
2022-06-27 19:35:26 +00:00
confutils ,
2022-11-10 16:58:31 +00:00
strutils ,
2022-05-30 19:14:07 +00:00
web3 , json ,
2022-06-27 19:35:26 +00:00
web3 / ethtypes ,
2022-06-17 22:00:19 +00:00
eth / keys ,
2022-06-27 19:35:26 +00:00
libp2p / protocols / pubsub / rpc / messages ,
libp2p / protocols / pubsub / pubsub ,
2021-08-24 19:25:29 +00:00
stew / results ,
2023-01-16 12:56:18 +00:00
stew / [ byteutils , arrayops ]
2022-11-21 14:15:03 +00:00
import
2022-12-07 11:30:32 +00:00
. / rln ,
2023-01-16 12:56:18 +00:00
. / conversion_utils ,
2022-11-21 14:15:03 +00:00
. / constants ,
. / protocol_types ,
. / protocol_metrics
import
2022-09-30 12:43:42 +00:00
.. / .. / utils / time ,
2022-10-28 09:13:05 +00:00
.. / .. / utils / keyfile ,
2022-12-13 09:26:24 +00:00
.. / waku_message ,
.. / waku_relay
2021-02-19 19:44:18 +00:00
2021-07-15 18:25:52 +00:00
logScope :
2022-11-03 15:36:24 +00:00
topics = " waku rln_relay "
2021-07-15 18:25:52 +00:00
2022-11-09 18:45:04 +00:00
type WakuRlnConfig * = object
rlnRelayDynamic * : bool
rlnRelayPubsubTopic * : PubsubTopic
rlnRelayContentTopic * : ContentTopic
rlnRelayMembershipIndex * : uint
rlnRelayEthContractAddress * : string
rlnRelayEthClientAddress * : string
rlnRelayEthAccountPrivateKey * : string
rlnRelayEthAccountAddress * : string
rlnRelayCredPath * : string
rlnRelayCredentialsPassword * : string
2022-12-07 11:30:32 +00:00
type
2022-11-09 18:45:04 +00:00
SpamHandler * = proc ( wakuMessage : WakuMessage ) : void {. gcsafe , closure , raises : [ Defect ] . }
RegistrationHandler * = proc ( txHash : string ) : void {. gcsafe , closure , raises : [ Defect ] . }
2022-12-07 11:30:32 +00:00
GroupUpdateHandler * = proc ( blockNumber : BlockNumber ,
2022-11-10 16:58:31 +00:00
members : seq [ MembershipTuple ] ) : RlnRelayResult [ void ] {. gcsafe . }
MembershipTuple * = tuple [ index : MembershipIndex , idComm : IDCommitment ]
2022-02-16 22:52:21 +00:00
2021-02-19 19:44:18 +00:00
# membership contract interface
contract ( MembershipContract ) :
proc register ( pubkey : Uint256 ) # external payable
2022-05-30 19:14:07 +00:00
proc MemberRegistered ( pubkey : Uint256 , index : Uint256 ) {. event . }
# TODO the followings are to be supported
# proc registerBatch(pubkeys: seq[Uint256]) # external payable
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
2021-02-18 22:59:10 +00:00
2023-01-16 12:56:18 +00:00
proc inHex * ( value :
IdentityTrapdoor or
IdentityNullifier or
IdentitySecretHash or
IDCommitment or
MerkleNode or
Nullifier or
Epoch or
RlnIdentifier
) : string =
var valueHex = UInt256 . fromBytesLE ( value )
valueHex = valueHex . toHex ( )
2022-10-12 08:38:48 +00:00
# We pad leading zeroes
while valueHex . len < value . len * 2 :
valueHex = " 0 " & valueHex
return valueHex
2022-10-12 02:18:11 +00:00
proc register * ( idComm : IDCommitment , ethAccountAddress : Option [ Address ] , ethAccountPrivKey : keys . PrivateKey , ethClientAddress : string , membershipContractAddress : Address , registrationHandler : Option [ RegistrationHandler ] = none ( RegistrationHandler ) ) : Future [ Result [ MembershipIndex , string ] ] {. async . } =
2022-06-17 22:00:19 +00:00
# TODO may need to also get eth Account Private Key as PrivateKey
## registers the idComm into the membership contract whose address is in rlnPeer.membershipContractAddress
2022-12-07 11:30:32 +00:00
2022-08-30 17:59:02 +00:00
var web3 : Web3
try : # check if the Ethereum client is reachable
web3 = await newWeb3 ( ethClientAddress )
except :
return err ( " could not connect to the Ethereum client " )
2022-10-12 02:18:11 +00:00
if ethAccountAddress . isSome ( ) :
web3 . defaultAccount = ethAccountAddress . get ( )
2022-07-01 01:05:38 +00:00
# set the account private key
web3 . privateKey = some ( ethAccountPrivKey )
# set the gas price twice the suggested price in order for the fast mining
let gasPrice = int ( await web3 . provider . eth_gasPrice ( ) ) * 2
2022-12-07 11:30:32 +00:00
2022-09-30 12:43:42 +00:00
# when the private key is set in a web3 instance, the send proc (sender.register(pk).send(MembershipFee))
2021-02-22 17:40:02 +00:00
# does the signing using the provided key
2022-06-17 22:00:19 +00:00
# web3.privateKey = some(ethAccountPrivateKey)
var sender = web3 . contractSender ( MembershipContract , membershipContractAddress ) # creates a Sender object with a web3 field and contract address of type Address
2022-11-10 16:58:31 +00:00
debug " registering an id commitment " , idComm = idComm . inHex ( )
2022-08-30 17:59:02 +00:00
let pk = idComm . toUInt256 ( )
var txHash : TxHash
try : # send the registration transaction and check if any error occurs
2022-09-30 12:43:42 +00:00
txHash = await sender . register ( pk ) . send ( value = MembershipFee , gasPrice = gasPrice )
2022-08-30 17:59:02 +00:00
except ValueError as e :
return err ( " registration transaction failed: " & e . msg )
let tsReceipt = await web3 . getMinedTransactionReceipt ( txHash )
2022-12-07 11:30:32 +00:00
2022-06-17 22:00:19 +00:00
# the receipt topic holds the hash of signature of the raised events
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
2022-12-07 11:30:32 +00:00
let
2022-06-17 22:00:19 +00:00
argumentsBytes = arguments . hexToSeqByte ( )
2022-10-10 17:35:43 +00:00
# In TX log data, uints are encoded in big endian
2022-06-17 22:00:19 +00:00
eventIdCommUint = UInt256 . fromBytesBE ( argumentsBytes [ 0 .. 31 ] )
eventIndex = UInt256 . fromBytesBE ( argumentsBytes [ 32 .. ^ 1 ] )
eventIdComm = eventIdCommUint . toIDCommitment ( )
2022-11-10 16:58:31 +00:00
debug " the identity commitment key extracted from tx log " , eventIdComm = eventIdComm . inHex ( )
2022-06-17 22:00:19 +00:00
debug " the index of registered identity commitment key " , eventIndex = eventIndex
if eventIdComm ! = idComm :
return err ( " invalid id commitment key " )
2021-02-19 19:44:18 +00:00
await web3 . close ( )
2022-08-30 17:59:02 +00:00
2022-08-24 22:47:06 +00:00
if registrationHandler . isSome ( ) :
let handler = registrationHandler . get
handler ( toHex ( txHash ) )
2022-06-17 22:00:19 +00:00
return ok ( toMembershipIndex ( eventIndex ) )
2022-09-20 13:08:05 +00:00
proc register * ( rlnPeer : WakuRLNRelay , registrationHandler : Option [ RegistrationHandler ] = none ( RegistrationHandler ) ) : Future [ RlnRelayResult [ bool ] ] {. async . } =
2022-12-14 11:28:09 +00:00
## registers the public key of the rlnPeer which is rlnPeer.identityCredential.publicKey
2022-06-17 22:00:19 +00:00
## into the membership contract whose address is in rlnPeer.membershipContractAddress
2022-12-14 11:28:09 +00:00
let pk = rlnPeer . identityCredential . idCommitment
2022-08-30 17:59:02 +00:00
let regResult = await register ( idComm = pk , ethAccountAddress = rlnPeer . ethAccountAddress , ethAccountPrivKey = rlnPeer . ethAccountPrivateKey . get ( ) , ethClientAddress = rlnPeer . ethClientAddress , membershipContractAddress = rlnPeer . membershipContractAddress , registrationHandler = registrationHandler )
if regResult . isErr :
return err ( regResult . error ( ) )
return ok ( true )
2021-03-16 18:18:40 +00:00
2022-09-27 04:40:04 +00:00
proc updateValidRootQueue * ( wakuRlnRelay : WakuRLNRelay , root : MerkleNode ) : void =
2022-12-07 11:30:32 +00:00
## updates the valid Merkle root queue with the latest root and pops the oldest one when the capacity of `AcceptableRootWindowSize` is reached
2022-09-27 04:40:04 +00:00
let overflowCount = wakuRlnRelay . validMerkleRoots . len ( ) - AcceptableRootWindowSize
if overflowCount > = 0 :
# Delete the oldest `overflowCount` elements in the deque (index 0..`overflowCount`)
for i in 0 .. overflowCount :
2022-12-07 11:30:32 +00:00
wakuRlnRelay . validMerkleRoots . popFirst ( )
2022-09-27 04:40:04 +00:00
# Push the next root into the queue
wakuRlnRelay . validMerkleRoots . addLast ( root )
2022-12-07 11:30:32 +00:00
proc insertMembers * ( wakuRlnRelay : WakuRLNRelay ,
index : MembershipIndex ,
2022-11-10 16:58:31 +00:00
idComms : seq [ IDCommitment ] ) : RlnRelayResult [ void ] =
## inserts a sequence of id commitments into the local merkle tree, and adds the changed root to the
2022-09-27 04:40:04 +00:00
## queue of valid roots
2022-11-04 03:00:42 +00:00
## Returns an error if the insertion fails
2022-09-30 12:43:42 +00:00
waku_rln_membership_insertion_duration_seconds . nanosecondTime :
2022-11-10 16:58:31 +00:00
let actionSucceeded = wakuRlnRelay . rlnInstance . insertMembers ( index , idComms )
2022-09-27 04:40:04 +00:00
if not actionSucceeded :
2022-11-10 16:58:31 +00:00
return err ( " could not insert id commitments into the merkle tree " )
2022-09-27 04:40:04 +00:00
let rootAfterUpdate = ? wakuRlnRelay . rlnInstance . getMerkleRoot ( )
wakuRlnRelay . updateValidRootQueue ( rootAfterUpdate )
return ok ( )
proc removeMember * ( wakuRlnRelay : WakuRLNRelay , index : MembershipIndex ) : RlnRelayResult [ void ] =
## removes a commitment from the local merkle tree at `index`, and adds the changed root to the
## queue of valid roots
2022-11-04 03:00:42 +00:00
## Returns an error if the removal fails
2022-09-27 04:40:04 +00:00
let actionSucceeded = wakuRlnRelay . rlnInstance . removeMember ( index )
if not actionSucceeded :
return err ( " could not remove id commitment from the merkle tree " )
let rootAfterUpdate = ? wakuRlnRelay . rlnInstance . getMerkleRoot ( )
wakuRlnRelay . updateValidRootQueue ( rootAfterUpdate )
return ok ( )
proc validateRoot * ( wakuRlnRelay : WakuRLNRelay , root : MerkleNode ) : bool =
## Validate against the window of roots stored in wakuRlnRelay.validMerkleRoots
return root in wakuRlnRelay . validMerkleRoots
2022-11-04 03:00:42 +00:00
proc calcMerkleRoot * ( list : seq [ IDCommitment ] ) : RlnRelayResult [ string ] =
2022-05-10 21:09:18 +00:00
## returns the root of the Merkle tree that is computed from the supplied list
2021-09-17 17:31:25 +00:00
## the root is in hexadecimal format
2022-11-04 03:00:42 +00:00
## Returns an error if the computation fails
2022-05-10 21:09:18 +00:00
2022-11-04 03:00:42 +00:00
let rlnInstance = createRLNInstance ( )
if rlnInstance . isErr ( ) :
return err ( " could not create rln instance: " & rlnInstance . error ( ) )
let rln = rlnInstance . get ( )
2021-09-17 17:31:25 +00:00
2022-05-10 21:09:18 +00:00
# create a Merkle tree
2022-11-10 16:58:31 +00:00
let membersAdded = rln . insertMembers ( 0 , list )
if not membersAdded :
return err ( " could not insert members into the tree " )
let root = rln . getMerkleRoot ( ) . value ( ) . inHex ( )
2022-11-04 03:00:42 +00:00
return ok ( root )
2021-09-17 17:31:25 +00:00
2022-11-04 03:00:42 +00:00
proc createMembershipList * ( n : int ) : RlnRelayResult [ (
2022-12-14 11:28:09 +00:00
seq [ ( string , string , string , string ) ] , string
2022-11-04 03:00:42 +00:00
) ] =
2022-12-14 11:28:09 +00:00
## createMembershipList produces a sequence of identity credentials in the form of (identity trapdoor, identity nullifier, identity secret hash, id commitment) in the hexadecimal format
2021-09-17 17:31:25 +00:00
## this proc also returns the root of a Merkle tree constructed out of the identity commitment keys of the generated list
## the output of this proc is used to initialize a static group keys (to test waku-rln-relay in the off-chain mode)
2022-11-04 03:00:42 +00:00
## Returns an error if it cannot create the membership list
2022-05-10 21:09:18 +00:00
2021-09-17 17:31:25 +00:00
# initialize a Merkle tree
2022-11-04 03:00:42 +00:00
let rlnInstance = createRLNInstance ( )
if rlnInstance . isErr ( ) :
return err ( " could not create rln instance: " & rlnInstance . error ( ) )
let rln = rlnInstance . get ( )
2021-09-17 17:31:25 +00:00
2022-12-14 11:28:09 +00:00
var output = newSeq [ ( string , string , string , string ) ] ( )
2022-11-10 16:58:31 +00:00
var idCommitments = newSeq [ IDCommitment ] ( )
2021-09-17 17:31:25 +00:00
2022-12-14 11:28:09 +00:00
for i in 0 .. n - 1 :
# generate an identity credential
let idCredentialRes = rln . membershipKeyGen ( )
if idCredentialRes . isErr ( ) :
return err ( " could not generate an identity credential: " & idCredentialRes . error ( ) )
let idCredential = idCredentialRes . get ( )
let idTuple = ( idCredential . idTrapdoor . inHex ( ) , idCredential . idNullifier . inHex ( ) , idCredential . idSecretHash . inHex ( ) , idCredential . idCommitment . inHex ( ) )
output . add ( idTuple )
idCommitments . add ( idCredential . idCommitment )
2022-12-07 11:30:32 +00:00
2022-11-10 16:58:31 +00:00
# Insert members into tree
let membersAdded = rln . insertMembers ( 0 , idCommitments )
if not membersAdded :
return err ( " could not insert members into the tree " )
2021-09-17 17:31:25 +00:00
2022-11-10 16:58:31 +00:00
let root = rln . getMerkleRoot ( ) . value ( ) . inHex ( )
2022-11-04 03:00:42 +00:00
return ok ( ( output , root ) )
2021-09-24 17:32:45 +00:00
2022-11-04 03:00:42 +00:00
proc rlnRelayStaticSetUp * ( rlnRelayMembershipIndex : MembershipIndex ) : RlnRelayResult [ ( Option [ seq [
2022-12-14 11:28:09 +00:00
IDCommitment ] ] , Option [ IdentityCredential ] , Option [
2022-11-04 03:00:42 +00:00
MembershipIndex ] ) ] =
## rlnRelayStaticSetUp is a proc that is used to initialize the static group keys and the static membership index
## this proc is used to test waku-rln-relay in the off-chain mode
2022-12-14 11:28:09 +00:00
## it returns the static group id commitments, the static identity credentials, and the static membership indexes
2022-11-04 03:00:42 +00:00
## Returns an error if it cannot initialize the static group keys and the static membership index
2021-09-24 17:32:45 +00:00
let
# static group
2022-09-30 12:43:42 +00:00
groupKeys = StaticGroupKeys
groupSize = StaticGroupSize
2021-09-24 17:32:45 +00:00
2022-10-03 20:25:56 +00:00
debug " rln-relay membership index " , rlnRelayMembershipIndex
2021-09-24 17:32:45 +00:00
# validate the user-supplied membership index
2022-10-03 20:25:56 +00:00
if rlnRelayMembershipIndex < MembershipIndex ( 0 ) or rlnRelayMembershipIndex > =
2022-05-10 21:09:18 +00:00
MembershipIndex ( groupSize ) :
2021-09-24 17:32:45 +00:00
error " wrong membership index "
2022-12-14 11:28:09 +00:00
return ok ( ( none ( seq [ IDCommitment ] ) , none ( IdentityCredential ) , none ( MembershipIndex ) ) )
2022-05-10 21:09:18 +00:00
2021-09-24 17:32:45 +00:00
# prepare the outputs from the static group keys
2022-05-10 21:09:18 +00:00
let
2022-12-14 11:28:09 +00:00
# create a sequence of IdentityCredentials from the group keys (group keys are in string format)
groupIdCredentialsRes = groupKeys . toIdentityCredentials ( )
2022-11-04 03:00:42 +00:00
2022-12-14 11:28:09 +00:00
if groupIdCredentialsRes . isErr ( ) :
return err ( " could not convert the group keys to IdentityCredentials: " &
groupIdCredentialsRes . error ( ) )
2022-11-04 03:00:42 +00:00
let
2022-12-14 11:28:09 +00:00
groupIdCredentials = groupIdCredentialsRes . get ( )
2021-09-24 17:32:45 +00:00
# extract id commitment keys
2022-12-14 11:28:09 +00:00
groupIDCommitments = groupIdCredentials . mapIt ( it . idCommitment )
groupIDCommitmentsOpt = some ( groupIDCommitments )
# user selected rln membership credential
groupIdCredentialsOpt = some ( groupIdCredentials [ rlnRelayMembershipIndex ] )
2022-10-03 20:25:56 +00:00
memIndexOpt = some ( rlnRelayMembershipIndex )
2022-05-10 21:09:18 +00:00
2022-12-14 11:28:09 +00:00
return ok ( ( groupIDCommitmentsOpt , groupIdCredentialsOpt , memIndexOpt ) )
2021-11-23 22:48:40 +00:00
2023-01-16 12:56:18 +00:00
proc calcEpoch * ( t : float64 ) : Epoch =
## gets time `t` as `flaot64` with subseconds resolution in the fractional part
## and returns its corresponding rln `Epoch` value
let e = uint64 ( t / EpochUnitSeconds )
return toEpoch ( e )
2022-09-20 13:08:05 +00:00
proc hasDuplicate * ( rlnPeer : WakuRLNRelay , msg : WakuMessage ) : RlnRelayResult [ bool ] =
2022-05-10 21:09:18 +00:00
## returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same
2021-11-23 22:48:40 +00:00
## epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares
## otherwise, returns false
2022-11-04 03:00:42 +00:00
## Returns an error if it cannot check for duplicates
2022-05-10 21:09:18 +00:00
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( msg . proof )
if decodeRes . isErr ( ) :
return err ( " failed to decode the RLN proof " )
let proof = decodeRes . get ( )
2021-11-23 22:48:40 +00:00
# extract the proof metadata of the supplied `msg`
2022-11-22 17:29:43 +00:00
let proofMD = ProofMetadata (
2022-12-07 11:30:32 +00:00
nullifier : proof . nullifier ,
shareX : proof . shareX ,
2022-11-22 17:29:43 +00:00
shareY : proof . shareY
)
2021-11-23 22:48:40 +00:00
# check if the epoch exists
2022-11-22 17:29:43 +00:00
if not rlnPeer . nullifierLog . hasKey ( proof . epoch ) :
2021-11-23 22:48:40 +00:00
return ok ( false )
try :
2022-11-22 17:29:43 +00:00
if rlnPeer . nullifierLog [ proof . epoch ] . contains ( proofMD ) :
2021-11-23 22:48:40 +00:00
# there is an identical record, ignore rhe mag
return ok ( false )
# check for a message with the same nullifier but different secret shares
2022-11-22 17:29:43 +00:00
let matched = rlnPeer . nullifierLog [ proof . epoch ] . filterIt ( (
2022-05-10 21:09:18 +00:00
it . nullifier = = proofMD . nullifier ) and ( ( it . shareX ! = proofMD . shareX ) or
( it . shareY ! = proofMD . shareY ) ) )
2021-11-23 22:48:40 +00:00
if matched . len ! = 0 :
# there is a duplicate
return ok ( true )
2022-05-10 21:09:18 +00:00
2021-11-23 22:48:40 +00:00
# there is no duplicate
return ok ( false )
except KeyError as e :
return err ( " the epoch was not found " )
2022-09-20 13:08:05 +00:00
proc updateLog * ( rlnPeer : WakuRLNRelay , msg : WakuMessage ) : RlnRelayResult [ bool ] =
2022-05-10 21:09:18 +00:00
## extracts the `ProofMetadata` of the supplied messages `msg` and
2021-11-23 22:48:40 +00:00
## saves it in the `nullifierLog` of the `rlnPeer`
2022-11-04 03:00:42 +00:00
## Returns an error if it cannot update the log
2021-11-23 22:48:40 +00:00
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( msg . proof )
if decodeRes . isErr ( ) :
return err ( " failed to decode the RLN proof " )
let proof = decodeRes . get ( )
# extract the proof metadata of the supplied `msg`
let proofMD = ProofMetadata (
2022-12-07 11:30:32 +00:00
nullifier : proof . nullifier ,
shareX : proof . shareX ,
2022-11-22 17:29:43 +00:00
shareY : proof . shareY
)
2022-05-10 21:09:18 +00:00
debug " proof metadata " , proofMD = proofMD
2021-11-23 22:48:40 +00:00
# check if the epoch exists
2022-11-22 17:29:43 +00:00
if not rlnPeer . nullifierLog . hasKey ( proof . epoch ) :
rlnPeer . nullifierLog [ proof . epoch ] = @ [ proofMD ]
2021-11-23 22:48:40 +00:00
return ok ( true )
2022-05-10 21:09:18 +00:00
2021-11-23 22:48:40 +00:00
try :
# check if an identical record exists
2022-11-22 17:29:43 +00:00
if rlnPeer . nullifierLog [ proof . epoch ] . contains ( proofMD ) :
2021-11-23 22:48:40 +00:00
return ok ( true )
# add proofMD to the log
2022-11-22 17:29:43 +00:00
rlnPeer . nullifierLog [ proof . epoch ] . add ( proofMD )
2021-11-23 22:48:40 +00:00
return ok ( true )
except KeyError as e :
return err ( " the epoch was not found " )
proc getCurrentEpoch * ( ) : Epoch =
## gets the current rln Epoch time
return calcEpoch ( epochTime ( ) )
2022-11-01 00:25:39 +00:00
proc absDiff * ( e1 , e2 : Epoch ) : uint64 =
## returns the absolute difference between the two rln `Epoch`s `e1` and `e2`
2022-05-10 21:09:18 +00:00
## i.e., e1 - e2
2021-11-23 22:48:40 +00:00
# convert epochs to their corresponding unsigned numerical values
2022-05-10 21:09:18 +00:00
let
2021-11-23 22:48:40 +00:00
epoch1 = fromEpoch ( e1 )
epoch2 = fromEpoch ( e2 )
2022-12-07 11:30:32 +00:00
2022-11-01 00:25:39 +00:00
# Manually perform an `abs` calculation
if epoch1 > epoch2 :
return epoch1 - epoch2
else :
return epoch2 - epoch1
2022-05-10 21:09:18 +00:00
proc validateMessage * ( rlnPeer : WakuRLNRelay , msg : WakuMessage ,
timeOption : Option [ float64 ] = none ( float64 ) ) : MessageValidationResult =
2021-11-23 22:48:40 +00:00
## validate the supplied `msg` based on the waku-rln-relay routing protocol i.e.,
2022-09-30 12:43:42 +00:00
## the `msg`'s epoch is within MaxEpochGap of the current epoch
2021-11-23 22:48:40 +00:00
## the `msg` has valid rate limit proof
## the `msg` does not violate the rate limit
2022-05-10 21:09:18 +00:00
## `timeOption` indicates Unix epoch time (fractional part holds sub-seconds)
2022-01-28 21:57:17 +00:00
## if `timeOption` is supplied, then the current epoch is calculated based on that
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( msg . proof )
if decodeRes . isErr ( ) :
return MessageValidationResult . Invalid
let proof = decodeRes . get ( )
2021-11-23 22:48:40 +00:00
2022-09-30 12:43:42 +00:00
# track message count for metrics
waku_rln_messages_total . inc ( )
2022-05-10 21:09:18 +00:00
2021-11-23 22:48:40 +00:00
# checks if the `msg`'s epoch is far from the current epoch
# it corresponds to the validation of rln external nullifier
2022-01-28 21:57:17 +00:00
var epoch : Epoch
if timeOption . isSome ( ) :
epoch = calcEpoch ( timeOption . get ( ) )
else :
2021-11-23 22:48:40 +00:00
# get current rln epoch
epoch = getCurrentEpoch ( )
2022-01-28 21:57:17 +00:00
2022-05-10 21:09:18 +00:00
debug " current epoch " , currentEpoch = fromEpoch ( epoch )
let
2022-11-22 17:29:43 +00:00
msgEpoch = proof . epoch
2021-11-23 22:48:40 +00:00
# calculate the gaps
2022-11-01 00:25:39 +00:00
gap = absDiff ( epoch , msgEpoch )
2022-02-16 22:52:21 +00:00
2022-05-10 21:09:18 +00:00
debug " message epoch " , msgEpoch = fromEpoch ( msgEpoch )
2022-02-16 22:52:21 +00:00
2021-11-23 22:48:40 +00:00
# validate the epoch
2022-11-01 00:25:39 +00:00
if gap > MaxEpochGap :
2021-11-23 22:48:40 +00:00
# message's epoch is too old or too ahead
2022-09-30 12:43:42 +00:00
# accept messages whose epoch is within +-MaxEpochGap from the current epoch
2022-11-10 16:58:31 +00:00
warn " invalid message: epoch gap exceeds a threshold " , gap = gap ,
2022-05-10 21:09:18 +00:00
payload = string . fromBytes ( msg . payload )
2022-09-30 12:43:42 +00:00
waku_rln_invalid_messages_total . inc ( labelValues = [ " invalid_epoch " ] )
2021-11-23 22:48:40 +00:00
return MessageValidationResult . Invalid
2022-05-10 21:09:18 +00:00
2022-11-22 17:29:43 +00:00
if not rlnPeer . validateRoot ( proof . merkleRoot ) :
debug " invalid message: provided root does not belong to acceptable window of roots " , provided = proof . merkleRoot , validRoots = rlnPeer . validMerkleRoots . mapIt ( it . inHex ( ) )
2022-09-30 12:43:42 +00:00
waku_rln_invalid_messages_total . inc ( labelValues = [ " invalid_root " ] )
2022-12-05 18:22:54 +00:00
return MessageValidationResult . Invalid
2022-09-20 13:08:05 +00:00
2021-11-23 22:48:40 +00:00
# verify the proof
2022-05-10 21:09:18 +00:00
let
2022-02-04 23:58:27 +00:00
contentTopicBytes = msg . contentTopic . toBytes
input = concat ( msg . payload , contentTopicBytes )
2022-09-30 12:43:42 +00:00
waku_rln_proof_verification_total . inc ( )
waku_rln_proof_verification_duration_seconds . nanosecondTime :
2022-11-22 17:29:43 +00:00
let proofVerificationRes = rlnPeer . rlnInstance . proofVerify ( input , proof )
2022-09-20 13:08:05 +00:00
if proofVerificationRes . isErr ( ) :
2022-09-30 12:43:42 +00:00
waku_rln_errors_total . inc ( labelValues = [ " proof_verification " ] )
2022-11-10 16:58:31 +00:00
warn " invalid message: proof verification failed " , payload = string . fromBytes ( msg . payload )
2022-09-20 13:08:05 +00:00
return MessageValidationResult . Invalid
if not proofVerificationRes . value ( ) :
2021-11-23 22:48:40 +00:00
# invalid proof
2022-05-10 21:09:18 +00:00
debug " invalid message: invalid proof " , payload = string . fromBytes ( msg . payload )
2022-09-30 12:43:42 +00:00
waku_rln_invalid_messages_total . inc ( labelValues = [ " invalid_proof " ] )
2021-11-23 22:48:40 +00:00
return MessageValidationResult . Invalid
2022-05-10 21:09:18 +00:00
2021-11-23 22:48:40 +00:00
# check if double messaging has happened
let hasDup = rlnPeer . hasDuplicate ( msg )
2022-09-30 12:43:42 +00:00
if hasDup . isErr ( ) :
waku_rln_errors_total . inc ( labelValues = [ " duplicate_check " ] )
elif hasDup . value = = true :
debug " invalid message: message is spam " , payload = string . fromBytes ( msg . payload )
waku_rln_spam_messages_total . inc ( )
2021-11-23 22:48:40 +00:00
return MessageValidationResult . Spam
2022-05-10 21:09:18 +00:00
# insert the message to the log
2021-11-23 22:48:40 +00:00
# the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
# it will never error out
discard rlnPeer . updateLog ( msg )
2022-05-10 21:09:18 +00:00
debug " message is valid " , payload = string . fromBytes ( msg . payload )
2022-11-22 17:29:43 +00:00
let rootIndex = rlnPeer . validMerkleRoots . find ( proof . merkleRoot )
2022-09-30 12:43:42 +00:00
waku_rln_valid_messages_total . observe ( rootIndex . toFloat ( ) )
2021-11-23 22:48:40 +00:00
return MessageValidationResult . Valid
2022-02-16 22:52:21 +00:00
proc toRLNSignal * ( wakumessage : WakuMessage ) : seq [ byte ] =
## it is a utility proc that prepares the `data` parameter of the proof generation procedure i.e., `proofGen` that resides in the current module
## it extracts the `contentTopic` and the `payload` of the supplied `wakumessage` and serializes them into a byte sequence
2022-05-10 21:09:18 +00:00
let
2022-02-16 22:52:21 +00:00
contentTopicBytes = wakumessage . contentTopic . toBytes
output = concat ( wakumessage . payload , contentTopicBytes )
return output
2022-05-10 21:09:18 +00:00
proc appendRLNProof * ( rlnPeer : WakuRLNRelay , msg : var WakuMessage ,
senderEpochTime : float64 ) : bool =
2021-11-23 22:48:40 +00:00
## returns true if it can create and append a `RateLimitProof` to the supplied `msg`
## returns false otherwise
## `senderEpochTime` indicates the number of seconds passed since Unix epoch. The fractional part holds sub-seconds.
## The `epoch` field of `RateLimitProof` is derived from the provided `senderEpochTime` (using `calcEpoch()`)
2022-02-16 22:52:21 +00:00
let input = msg . toRLNSignal ( )
2022-05-10 21:09:18 +00:00
2021-11-23 22:48:40 +00:00
var proof : RateLimitProofResult = proofGen ( rlnInstance = rlnPeer . rlnInstance , data = input ,
2022-12-14 11:28:09 +00:00
memKeys = rlnPeer . identityCredential ,
2022-05-10 21:09:18 +00:00
memIndex = rlnPeer . membershipIndex ,
2021-11-23 22:48:40 +00:00
epoch = calcEpoch ( senderEpochTime ) )
2022-05-10 21:09:18 +00:00
2022-11-22 17:29:43 +00:00
if proof . isErr ( ) :
2021-11-23 22:48:40 +00:00
return false
2022-11-22 17:29:43 +00:00
msg . proof = proof . value . encode ( ) . buffer
2021-11-23 22:48:40 +00:00
return true
2022-09-27 04:40:04 +00:00
proc addAll * ( wakuRlnRelay : WakuRLNRelay , list : seq [ IDCommitment ] ) : RlnRelayResult [ void ] =
# add members to the Merkle tree of the `rlnInstance`
2022-11-04 03:00:42 +00:00
## Returns an error if it cannot add any member to the Merkle tree
2022-11-10 16:58:31 +00:00
let membersAdded = wakuRlnRelay . insertMembers ( 0 , list )
if not membersAdded . isOk ( ) :
return err ( " failed to add members to the Merkle tree " )
2022-09-27 04:40:04 +00:00
return ok ( )
2022-05-30 19:14:07 +00:00
2022-11-01 02:45:34 +00:00
proc generateGroupUpdateHandler ( rlnPeer : WakuRLNRelay ) : GroupUpdateHandler =
## assuming all the members arrive in order
## TODO: check the index and the pubkey depending on
## the group update operation
var handler : GroupUpdateHandler
2022-11-10 16:58:31 +00:00
handler = proc ( blockNumber : BlockNumber , members : seq [ MembershipTuple ] ) : RlnRelayResult [ void ] =
let startingIndex = members [ 0 ] . index
debug " starting index " , startingIndex = startingIndex , members = members . mapIt ( it . idComm . inHex ( ) )
let isSuccessful = rlnPeer . insertMembers ( startingIndex , members . mapIt ( it . idComm ) )
2022-11-01 02:45:34 +00:00
if isSuccessful . isErr ( ) :
2022-11-10 16:58:31 +00:00
return err ( " failed to add new members to the Merkle tree " )
2022-11-01 02:45:34 +00:00
else :
2022-11-10 16:58:31 +00:00
debug " new members added to the Merkle tree " , pubkeys = members . mapIt ( it . idComm . inHex ( ) ) , startingIndex = startingIndex
debug " acceptable window " , validRoots = rlnPeer . validMerkleRoots . mapIt ( it . inHex ( ) )
let lastIndex = members [ 0 ] . index + members . len . uint - 1
let indexGap = startingIndex - rlnPeer . lastSeenMembershipIndex
if not ( toSeq ( startingIndex .. lastIndex ) = = members . mapIt ( it . index ) ) :
return err ( " the indexes of the new members are not in order " )
if indexGap ! = 1 . uint :
warn " membership index gap, may have lost connection " , lastIndex , currIndex = rlnPeer . lastSeenMembershipIndex , indexGap = indexGap
rlnPeer . lastSeenMembershipIndex = lastIndex
rlnPeer . lastProcessedBlock = blockNumber
debug " last processed block " , blockNumber = blockNumber
2022-11-01 02:45:34 +00:00
return ok ( )
return handler
2022-12-07 11:30:32 +00:00
proc parse * ( event : type MemberRegistered ,
2022-11-10 16:58:31 +00:00
log : JsonNode ) : RlnRelayResult [ MembershipTuple ] =
## parses the `data` parameter of the `MemberRegistered` event `log`
## returns an error if it cannot parse the `data` parameter
var pubkey : UInt256
var index : UInt256
var data : string
# Remove the 0x prefix
try :
data = strip0xPrefix ( log [ " data " ] . getStr ( ) )
except CatchableError :
return err ( " failed to parse the data field of the MemberRegistered event: " & getCurrentExceptionMsg ( ) )
var offset = 0
try :
# Parse the pubkey
offset + = decode ( data , offset , pubkey )
# Parse the index
offset + = decode ( data , offset , index )
2022-12-07 11:30:32 +00:00
return ok ( ( index : index . toMembershipIndex ( ) ,
2022-11-10 16:58:31 +00:00
idComm : pubkey . toIDCommitment ( ) ) )
except :
return err ( " failed to parse the data field of the MemberRegistered event " )
type BlockTable = OrderedTable [ BlockNumber , seq [ MembershipTuple ] ]
proc getHistoricalEvents * ( ethClientUri : string ,
contractAddress : Address ,
fromBlock : string = " 0x0 " ,
toBlock : string = " latest " ) : Future [ RlnRelayResult [ BlockTable ] ] {. async , gcsafe . } =
## `ethClientUri` is the URI of the Ethereum client
## `contractAddress` is the address of the contract
## `fromBlock` is the block number from which the events are fetched
## `toBlock` is the block number to which the events are fetched
## returns a table that maps block numbers to the list of members registered in that block
## returns an error if it cannot retrieve the historical events
let web3 = await newWeb3 ( ethClientUri )
let contract = web3 . contractSender ( MembershipContract , contractAddress )
# Get the historical events, and insert memberships into the tree
let historicalEvents = await contract . getJsonLogs ( MemberRegistered ,
fromBlock = some ( fromBlock . blockId ( ) ) ,
toBlock = some ( toBlock . blockId ( ) ) )
# Create a table that maps block numbers to the list of members registered in that block
var blockTable = OrderedTable [ BlockNumber , seq [ MembershipTuple ] ] ( )
for log in historicalEvents :
# batch according to log.blockNumber
let blockNumber = parseHexInt ( log [ " blockNumber " ] . getStr ( ) ) . uint
let parsedEventRes = parse ( MemberRegistered , log )
if parsedEventRes . isErr ( ) :
error " failed to parse the MemberRegistered event " , error = parsedEventRes . error ( )
return err ( " failed to parse the MemberRegistered event " )
let parsedEvent = parsedEventRes . get ( )
# Add the parsed event to the table
if blockTable . hasKey ( blockNumber ) :
blockTable [ blockNumber ] . add ( parsedEvent )
else :
blockTable [ blockNumber ] = @ [ parsedEvent ]
return ok ( blockTable )
2022-11-01 02:45:34 +00:00
proc subscribeToGroupEvents * ( ethClientUri : string ,
ethAccountAddress : Option [ Address ] = none ( Address ) ,
contractAddress : Address ,
blockNumber : string = " 0x0 " ,
2022-12-07 11:30:32 +00:00
handler : GroupUpdateHandler ) {. async , gcsafe . } =
2022-10-10 12:25:12 +00:00
## connects to the eth client whose URI is supplied as `ethClientUri`
## subscribes to the `MemberRegistered` event emitted from the `MembershipContract` which is available on the supplied `contractAddress`
## it collects all the events starting from the given `blockNumber`
2022-11-10 16:58:31 +00:00
## for every received block, it calls the `handler`
2022-10-10 12:25:12 +00:00
let web3 = await newWeb3 ( ethClientUri )
2022-11-10 16:58:31 +00:00
let contract = web3 . contractSender ( MembershipContract , contractAddress )
2022-12-07 11:30:32 +00:00
let blockTableRes = await getHistoricalEvents ( ethClientUri ,
contractAddress ,
2022-11-10 16:58:31 +00:00
fromBlock = blockNumber )
if blockTableRes . isErr ( ) :
error " failed to retrieve historical events " , error = blockTableRes . error
return
let blockTable = blockTableRes . get ( )
# Update MT by batch
for blockNumber , members in blockTable . pairs ( ) :
debug " updating the Merkle tree " , blockNumber = blockNumber , members = members
let res = handler ( blockNumber , members )
if res . isErr ( ) :
error " failed to update the Merkle tree " , error = res . error
# We don't need the block table after this point
discard blockTable
var latestBlock : BlockNumber
2022-12-07 11:30:32 +00:00
let handleLog = proc ( blockHeader : BlockHeader ) {. async , gcsafe . } =
2022-11-10 16:58:31 +00:00
try :
let membershipRegistrationLogs = await contract . getJsonLogs ( MemberRegistered ,
blockHash = some ( blockheader . hash ) )
if membershipRegistrationLogs . len = = 0 :
return
var members : seq [ MembershipTuple ]
for log in membershipRegistrationLogs :
let parsedEventRes = parse ( MemberRegistered , log )
if parsedEventRes . isErr ( ) :
fatal " failed to parse the MemberRegistered event " , error = parsedEventRes . error ( )
return
let parsedEvent = parsedEventRes . get ( )
members . add ( parsedEvent )
let res = handler ( blockHeader . number . uint , members )
if res . isErr ( ) :
error " failed to update the Merkle tree " , error = res . error
except CatchableError :
warn " failed to get logs " , error = getCurrentExceptionMsg ( )
return
2022-10-10 12:25:12 +00:00
let newHeadCallback = proc ( blockheader : BlockHeader ) {. gcsafe . } =
2022-11-10 16:58:31 +00:00
latestBlock = blockheader . number . uint
2022-10-10 12:25:12 +00:00
debug " block received " , blockNumber = latestBlock
2022-11-10 16:58:31 +00:00
# get logs from the last block
try :
asyncSpawn handleLog ( blockHeader )
except CatchableError :
warn " failed to handle log: " , error = getCurrentExceptionMsg ( )
2022-10-10 12:25:12 +00:00
let newHeadErrorHandler = proc ( err : CatchableError ) {. gcsafe . } =
error " Error from subscription: " , err = err . msg
discard await web3 . subscribeForBlockHeaders ( newHeadCallback , newHeadErrorHandler )
web3 . onDisconnect = proc ( ) =
debug " connection to ethereum node dropped " , lastBlock = latestBlock
2022-11-01 02:45:34 +00:00
proc handleGroupUpdates * ( rlnPeer : WakuRLNRelay ) {. async , gcsafe . } =
## generates the groupUpdateHandler which is called when a new member is registered,
## and has the WakuRLNRelay instance as a closure
let handler = generateGroupUpdateHandler ( rlnPeer )
await subscribeToGroupEvents ( ethClientUri = rlnPeer . ethClientAddress ,
ethAccountAddress = rlnPeer . ethAccountAddress ,
contractAddress = rlnPeer . membershipContractAddress ,
handler = handler )
2022-12-07 11:30:32 +00:00
2022-12-13 09:26:24 +00:00
proc addRLNRelayValidator * ( wakuRlnRelay : WakuRLNRelay ,
wakuRelay : WakuRelay ,
2023-01-16 12:56:18 +00:00
pubsubTopic : PubsubTopic ,
contentTopic : ContentTopic ,
2022-12-13 09:26:24 +00:00
spamHandler : Option [ SpamHandler ] = none ( SpamHandler ) ) =
2022-06-27 19:35:26 +00:00
## this procedure is a thin wrapper for the pubsub addValidator method
2022-12-07 11:30:32 +00:00
## it sets a validator for the waku messages published on the supplied pubsubTopic and contentTopic
2022-06-27 19:35:26 +00:00
## if contentTopic is empty, then validation takes place for All the messages published on the given pubsubTopic
## the message validation logic is according to https://rfc.vac.dev/spec/17/
proc validator ( topic : string , message : messages . Message ) : Future [ pubsub . ValidationResult ] {. async . } =
trace " rln-relay topic validator is called "
2022-12-07 11:30:32 +00:00
let decodeRes = WakuMessage . decode ( message . data )
2022-11-22 17:29:43 +00:00
if decodeRes . isOk ( ) :
2022-12-07 11:30:32 +00:00
let
2022-11-22 17:29:43 +00:00
wakumessage = decodeRes . value
2022-06-27 19:35:26 +00:00
payload = string . fromBytes ( wakumessage . payload )
# check the contentTopic
if ( wakumessage . contentTopic ! = " " ) and ( contentTopic ! = " " ) and ( wakumessage . contentTopic ! = contentTopic ) :
trace " content topic did not match: " , contentTopic = wakumessage . contentTopic , payload = payload
return pubsub . ValidationResult . Accept
2022-11-22 17:29:43 +00:00
let decodeRes = RateLimitProof . init ( wakumessage . proof )
if decodeRes . isErr ( ) :
return pubsub . ValidationResult . Reject
let msgProof = decodeRes . get ( )
2022-06-27 19:35:26 +00:00
# validate the message
2022-12-07 11:30:32 +00:00
let
2022-12-13 09:26:24 +00:00
validationRes = wakuRlnRelay . validateMessage ( wakumessage )
2022-11-22 17:29:43 +00:00
proof = toHex ( msgProof . proof )
epoch = fromEpoch ( msgProof . epoch )
root = inHex ( msgProof . merkleRoot )
shareX = inHex ( msgProof . shareX )
shareY = inHex ( msgProof . shareY )
nullifier = inHex ( msgProof . nullifier )
2022-06-27 19:35:26 +00:00
case validationRes :
of Valid :
debug " message validity is verified, relaying: " , contentTopic = wakumessage . contentTopic , epoch = epoch , timestamp = wakumessage . timestamp , payload = payload
trace " message validity is verified, relaying: " , proof = proof , root = root , shareX = shareX , shareY = shareY , nullifier = nullifier
return pubsub . ValidationResult . Accept
of Invalid :
debug " message validity could not be verified, discarding: " , contentTopic = wakumessage . contentTopic , epoch = epoch , timestamp = wakumessage . timestamp , payload = payload
trace " message validity could not be verified, discarding: " , proof = proof , root = root , shareX = shareX , shareY = shareY , nullifier = nullifier
return pubsub . ValidationResult . Reject
of Spam :
debug " A spam message is found! yay! discarding: " , contentTopic = wakumessage . contentTopic , epoch = epoch , timestamp = wakumessage . timestamp , payload = payload
trace " A spam message is found! yay! discarding: " , proof = proof , root = root , shareX = shareX , shareY = shareY , nullifier = nullifier
2022-11-22 17:29:43 +00:00
if spamHandler . isSome ( ) :
let handler = spamHandler . get ( )
2022-07-14 10:23:52 +00:00
handler ( wakumessage )
2022-11-22 17:29:43 +00:00
return pubsub . ValidationResult . Reject
2022-12-07 11:30:32 +00:00
# set a validator for the supplied pubsubTopic
2022-12-13 09:26:24 +00:00
let pb = PubSub ( wakuRelay )
2022-06-27 19:35:26 +00:00
pb . addValidator ( pubsubTopic , validator )
2022-12-13 09:26:24 +00:00
proc mountRlnRelayStatic * ( wakuRelay : WakuRelay ,
group : seq [ IDCommitment ] ,
2022-12-14 11:28:09 +00:00
memIdCredential : IdentityCredential ,
2022-12-13 09:26:24 +00:00
memIndex : MembershipIndex ,
pubsubTopic : PubsubTopic ,
contentTopic : ContentTopic ,
spamHandler : Option [ SpamHandler ] = none ( SpamHandler ) ) : RlnRelayResult [ WakuRlnRelay ] =
2022-11-04 03:00:42 +00:00
# Returns RlnRelayResult[void] to indicate the success of the call
2022-06-27 19:35:26 +00:00
debug " mounting rln-relay in off-chain/static mode "
# check the peer's index and the inclusion of user's identity commitment in the group
2022-12-14 11:28:09 +00:00
if not memIdCredential . idCommitment = = group [ int ( memIndex ) ] :
2022-11-04 03:00:42 +00:00
return err ( " The peer ' s index is not consistent with the group " )
2022-06-27 19:35:26 +00:00
# create an RLN instance
2022-11-04 03:00:42 +00:00
let rlnInstance = createRLNInstance ( )
if rlnInstance . isErr ( ) :
return err ( " RLN instance creation failed " )
let rln = rlnInstance . get ( )
2022-06-27 19:35:26 +00:00
# create the WakuRLNRelay
2022-12-14 11:28:09 +00:00
let rlnPeer = WakuRLNRelay ( identityCredential : memIdCredential ,
2022-06-27 19:35:26 +00:00
membershipIndex : memIndex ,
2022-12-07 11:30:32 +00:00
rlnInstance : rln ,
2022-06-27 19:35:26 +00:00
pubsubTopic : pubsubTopic ,
contentTopic : contentTopic )
2022-11-10 16:58:31 +00:00
# add members to the Merkle tree
let membersAdded = rlnPeer . insertMembers ( 0 , group )
if membersAdded . isErr ( ) :
return err ( " member addition to the Merkle tree failed: " & membersAdded . error )
2022-09-27 04:40:04 +00:00
2022-06-27 19:35:26 +00:00
# adds a topic validator for the supplied pubsub topic at the relay protocol
# messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped
# the topic validator checks for the correct non-spamming proof of the message
2022-12-13 09:26:24 +00:00
rlnPeer . addRLNRelayValidator ( wakuRelay , pubsubTopic , contentTopic , spamHandler )
2022-06-27 19:35:26 +00:00
debug " rln relay topic validator is mounted successfully " , pubsubTopic = pubsubTopic , contentTopic = contentTopic
2022-12-13 09:26:24 +00:00
return ok ( rlnPeer )
proc mountRlnRelayDynamic * ( wakuRelay : WakuRelay ,
ethClientAddr : string = " " ,
ethAccountAddress : Option [ web3 . Address ] = none ( web3 . Address ) ,
ethAccountPrivKeyOpt : Option [ keys . PrivateKey ] ,
memContractAddr : web3 . Address ,
2022-12-14 11:28:09 +00:00
memIdCredential : Option [ IdentityCredential ] = none ( IdentityCredential ) ,
2022-12-13 09:26:24 +00:00
memIndex : Option [ MembershipIndex ] = none ( MembershipIndex ) ,
pubsubTopic : PubsubTopic ,
contentTopic : ContentTopic ,
spamHandler : Option [ SpamHandler ] = none ( SpamHandler ) ,
registrationHandler : Option [ RegistrationHandler ] = none ( RegistrationHandler ) ) : Future [ RlnRelayResult [ WakuRlnRelay ] ] {. async . } =
2022-06-27 19:35:26 +00:00
debug " mounting rln-relay in on-chain/dynamic mode "
# create an RLN instance
2022-11-04 03:00:42 +00:00
let rlnInstance = createRLNInstance ( )
if rlnInstance . isErr ( ) :
return err ( " RLN instance creation failed. " )
let rln = rlnInstance . get ( )
2022-06-27 19:35:26 +00:00
2022-12-14 11:28:09 +00:00
# prepare rln membership credential
2022-12-07 11:30:32 +00:00
var
2022-12-14 11:28:09 +00:00
idCredential : IdentityCredential
2022-06-27 19:35:26 +00:00
rlnIndex : MembershipIndex
2022-12-14 11:28:09 +00:00
if memIdCredential . isNone : # no rln credentials provided
2022-08-30 17:59:02 +00:00
if ethAccountPrivKeyOpt . isSome : # if an ethereum private key is supplied, then create rln credentials and register to the membership contract
2022-07-28 18:45:29 +00:00
trace " no rln-relay key is provided, generating one "
2022-12-14 11:28:09 +00:00
let idCredentialRes = rln . membershipKeyGen ( )
if idCredentialRes . isErr ( ) :
error " failed to generate rln-relay identity credential "
return err ( " failed to generate rln-relay identity credential: " & idCredentialRes . error ( ) )
idCredential = idCredentialRes . value ( )
2022-07-28 18:45:29 +00:00
# register the rln-relay peer to the membership contract
2022-09-30 12:43:42 +00:00
waku_rln_registration_duration_seconds . nanosecondTime :
2022-12-14 11:28:09 +00:00
let regIndexRes = await register ( idComm = idCredential . idCommitment ,
2022-12-07 11:30:32 +00:00
ethAccountAddress = ethAccountAddress ,
ethAccountPrivKey = ethAccountPrivKeyOpt . get ( ) ,
ethClientAddress = ethClientAddr ,
membershipContractAddress = memContractAddr ,
2022-11-04 03:00:42 +00:00
registrationHandler = registrationHandler )
2022-07-28 18:45:29 +00:00
# check whether registration is done
2022-08-30 17:59:02 +00:00
if regIndexRes . isErr ( ) :
debug " membership registration failed " , err = regIndexRes . error ( )
return err ( " membership registration failed: " & regIndexRes . error ( ) )
2022-07-28 18:45:29 +00:00
rlnIndex = regIndexRes . value
debug " peer is successfully registered into the membership contract "
else : # if no eth private key is available, skip registration
debug " running waku-rln-relay in relay-only mode "
2022-06-27 19:35:26 +00:00
else :
2022-08-05 10:48:01 +00:00
debug " Peer is already registered to the membership contract "
2022-12-14 11:28:09 +00:00
idCredential = memIdCredential . get ( )
2022-06-27 19:35:26 +00:00
rlnIndex = memIndex . get ( )
# create the WakuRLNRelay
2022-12-14 11:28:09 +00:00
var rlnPeer = WakuRLNRelay ( identityCredential : idCredential ,
2022-06-27 19:35:26 +00:00
membershipIndex : rlnIndex ,
membershipContractAddress : memContractAddr ,
ethClientAddress : ethClientAddr ,
2022-10-12 02:18:11 +00:00
ethAccountAddress : ethAccountAddress ,
2022-07-28 18:45:29 +00:00
ethAccountPrivateKey : ethAccountPrivKeyOpt ,
2022-06-27 19:35:26 +00:00
rlnInstance : rln ,
pubsubTopic : pubsubTopic ,
contentTopic : contentTopic )
2022-11-01 02:45:34 +00:00
asyncSpawn rlnPeer . handleGroupUpdates ( )
2022-06-27 19:35:26 +00:00
debug " dynamic group management is started "
# adds a topic validator for the supplied pubsub topic at the relay protocol
# messages published on this pubsub topic will be relayed upon a successful validation, otherwise they will be dropped
# the topic validator checks for the correct non-spamming proof of the message
2022-12-13 09:26:24 +00:00
rlnPeer . addRLNRelayValidator ( wakuRelay , pubsubTopic , contentTopic , spamHandler )
2022-06-27 19:35:26 +00:00
debug " rln relay topic validator is mounted successfully " , pubsubTopic = pubsubTopic , contentTopic = contentTopic
2022-12-13 09:26:24 +00:00
return ok ( rlnPeer )
2022-06-27 19:35:26 +00:00
2022-12-07 11:30:32 +00:00
proc writeRlnCredentials * ( path : string ,
credentials : RlnMembershipCredentials ,
2022-11-04 03:00:42 +00:00
password : string ) : RlnRelayResult [ void ] =
# Returns RlnRelayResult[void], which indicates the success of the call
2022-10-28 09:13:05 +00:00
info " Storing RLN credentials "
var jsonString : string
jsonString . toUgly ( % credentials )
let keyfile = createKeyFileJson ( toBytes ( jsonString ) , password )
if keyfile . isErr ( ) :
return err ( " Error while creating keyfile for RLN credentials " )
if saveKeyFile ( path , keyfile . get ( ) ) . isErr ( ) :
return err ( " Error while saving keyfile for RLN credentials " )
return ok ( )
2022-12-07 11:30:32 +00:00
# Attempts decryptions of all keyfiles with the provided password.
2022-10-28 09:13:05 +00:00
# If one or more credentials are successfully decrypted, the max(min(index,number_decrypted),0)-th is returned.
2022-12-07 11:30:32 +00:00
proc readRlnCredentials * ( path : string ,
password : string ,
2022-11-04 03:00:42 +00:00
index : int = 0 ) : RlnRelayResult [ Option [ RlnMembershipCredentials ] ] =
# Returns RlnRelayResult[Option[RlnMembershipCredentials]], which indicates the success of the call
2022-10-28 09:13:05 +00:00
info " Reading RLN credentials "
2022-08-05 10:48:01 +00:00
# With regards to printing the keys, it is purely for debugging purposes so that the user becomes explicitly aware of the current keys in use when nwaku is started.
# Note that this is only until the RLN contract being used is the one deployed on Goerli testnet.
# These prints need to omitted once RLN contract is deployed on Ethereum mainnet and using valuable funds for staking.
2022-09-30 12:43:42 +00:00
waku_rln_membership_credentials_import_duration_seconds . nanosecondTime :
2022-08-05 10:48:01 +00:00
2022-10-28 09:13:05 +00:00
try :
var decodedKeyfiles = loadKeyFiles ( path , password )
2022-12-07 11:30:32 +00:00
2022-10-28 09:13:05 +00:00
if decodedKeyfiles . isOk ( ) :
var decodedRlnCredentials = decodedKeyfiles . get ( )
debug " Successfully decrypted keyfiles for the provided password " , numberKeyfilesDecrypted = decodedRlnCredentials . len
# We should return the index-th decrypted credential, but we ensure to not overflow
let credentialIndex = max ( min ( index , decodedRlnCredentials . len - 1 ) , 0 )
debug " Picking credential with (adjusted) index " , inputIndex = index , adjustedIndex = credentialIndex
let jsonObject = parseJson ( string . fromBytes ( decodedRlnCredentials [ credentialIndex ] . get ( ) ) )
2022-12-07 11:30:32 +00:00
let deserializedRlnCredentials = to ( jsonObject , RlnMembershipCredentials )
2022-10-28 09:13:05 +00:00
debug " Deserialized RLN credentials " , rlnCredentials = deserializedRlnCredentials
return ok ( some ( deserializedRlnCredentials ) )
else :
debug " Unable to decrypt RLN credentials with provided password. " , error = decodedKeyfiles . error
return ok ( none ( RlnMembershipCredentials ) )
except :
return err ( " Error while loading keyfile for RLN credentials at " & path )
2022-12-13 09:26:24 +00:00
proc mount ( wakuRelay : WakuRelay ,
2022-11-08 11:53:47 +00:00
conf : WakuRlnConfig ,
2022-09-30 12:43:42 +00:00
spamHandler : Option [ SpamHandler ] = none ( SpamHandler ) ,
registrationHandler : Option [ RegistrationHandler ] = none ( RegistrationHandler )
2022-12-13 09:26:24 +00:00
) : Future [ RlnRelayResult [ WakuRlnRelay ] ] {. async . } =
2023-01-16 12:56:18 +00:00
if not conf . rlnRelayDynamic :
2022-08-17 23:55:49 +00:00
info " setting up waku-rln-relay in off-chain mode... "
2022-06-27 19:35:26 +00:00
# set up rln relay inputs
2022-11-04 03:00:42 +00:00
let staticSetupRes = rlnRelayStaticSetUp ( MembershipIndex ( conf . rlnRelayMembershipIndex ) )
if staticSetupRes . isErr ( ) :
return err ( " rln relay static setup failed: " & staticSetupRes . error ( ) )
2022-12-14 11:28:09 +00:00
let ( groupOpt , idCredentialOpt , memIndexOpt ) = staticSetupRes . get ( )
2022-12-13 09:26:24 +00:00
if memIndexOpt . isNone ( ) :
2022-06-27 19:35:26 +00:00
error " failed to mount WakuRLNRelay "
2022-11-04 03:00:42 +00:00
return err ( " failed to mount WakuRLNRelay " )
2022-06-27 19:35:26 +00:00
else :
# mount rlnrelay in off-chain mode with a static group of users
2022-12-13 09:26:24 +00:00
let mountRes = mountRlnRelayStatic ( wakuRelay ,
2023-01-16 12:56:18 +00:00
group = groupOpt . get ( ) ,
2022-12-14 11:28:09 +00:00
memIdCredential = idCredentialOpt . get ( ) ,
2023-01-16 12:56:18 +00:00
memIndex = memIndexOpt . get ( ) ,
2022-12-13 09:26:24 +00:00
pubsubTopic = conf . rlnRelayPubsubTopic ,
2023-01-16 12:56:18 +00:00
contentTopic = conf . rlnRelayContentTopic ,
2022-12-13 09:26:24 +00:00
spamHandler = spamHandler )
2022-11-04 03:00:42 +00:00
if mountRes . isErr ( ) :
return err ( " Failed to mount WakuRLNRelay: " & mountRes . error ( ) )
2022-06-27 19:35:26 +00:00
2022-12-13 09:26:24 +00:00
let rlnRelay = mountRes . get ( )
2022-12-14 11:28:09 +00:00
info " membership id key " , idkey = idCredentialOpt . get ( ) . idSecretHash . inHex ( )
info " membership id commitment key " , idCommitmentkey = idCredentialOpt . get ( ) . idCommitment . inHex ( )
2022-06-27 19:35:26 +00:00
# check the correct construction of the tree by comparing the calculated root against the expected root
# no error should happen as it is already captured in the unit tests
2022-12-07 11:30:32 +00:00
# TODO have added this check to account for unseen corner cases, will remove it later
let
2022-12-13 09:26:24 +00:00
rootRes = rlnRelay . rlnInstance . getMerkleRoot ( )
2022-09-30 12:43:42 +00:00
expectedRoot = StaticGroupMerkleRoot
2022-12-07 11:30:32 +00:00
2022-09-20 13:08:05 +00:00
if rootRes . isErr ( ) :
return err ( rootRes . error ( ) )
2022-12-07 11:30:32 +00:00
2022-09-20 13:08:05 +00:00
let root = rootRes . value ( )
2022-11-10 16:58:31 +00:00
if root . inHex ( ) ! = expectedRoot :
2022-06-27 19:35:26 +00:00
error " root mismatch: something went wrong not in Merkle tree construction "
debug " the calculated root " , root
info " WakuRLNRelay is mounted successfully " , pubsubtopic = conf . rlnRelayPubsubTopic , contentTopic = conf . rlnRelayContentTopic
2022-12-13 09:26:24 +00:00
return ok ( rlnRelay )
2022-08-18 17:35:02 +00:00
else : # mount the rln relay protocol in the on-chain/dynamic mode
2022-10-12 02:18:11 +00:00
debug " setting up waku-rln-relay in on-chain mode... "
2022-12-07 11:30:32 +00:00
2022-10-12 02:18:11 +00:00
debug " on-chain setup parameters " , contractAddress = conf . rlnRelayEthContractAddress
2022-06-27 19:35:26 +00:00
# read related inputs to run rln-relay in on-chain mode and do type conversion when needed
2022-12-07 11:30:32 +00:00
let
2022-06-27 19:35:26 +00:00
ethClientAddr = conf . rlnRelayEthClientAddress
2022-11-04 03:00:42 +00:00
var ethMemContractAddress : web3 . Address
try :
2022-10-03 20:25:56 +00:00
ethMemContractAddress = web3 . fromHex ( web3 . Address , conf . rlnRelayEthContractAddress )
2022-11-04 03:00:42 +00:00
except ValueError as err :
return err ( " invalid eth contract address: " & err . msg )
2022-07-28 18:45:29 +00:00
var ethAccountPrivKeyOpt = none ( keys . PrivateKey )
2022-10-12 02:18:11 +00:00
var ethAccountAddressOpt = none ( Address )
var credentials = none ( RlnMembershipCredentials )
2022-12-13 09:26:24 +00:00
var rlnRelayRes : RlnRelayResult [ WakuRlnRelay ]
var rlnRelayCredPath : string
var persistCredentials : bool = false
2022-10-12 02:18:11 +00:00
2022-10-03 20:25:56 +00:00
if conf . rlnRelayEthAccountPrivateKey ! = " " :
ethAccountPrivKeyOpt = some ( keys . PrivateKey ( SkSecretKey . fromHex ( conf . rlnRelayEthAccountPrivateKey ) . value ) )
2022-12-07 11:30:32 +00:00
2022-10-12 02:18:11 +00:00
if conf . rlnRelayEthAccountAddress ! = " " :
2022-11-04 03:00:42 +00:00
var ethAccountAddress : web3 . Address
try :
ethAccountAddress = web3 . fromHex ( web3 . Address , conf . rlnRelayEthAccountAddress )
except ValueError as err :
return err ( " invalid eth account address: " & err . msg )
ethAccountAddressOpt = some ( ethAccountAddress )
2022-12-07 11:30:32 +00:00
2022-08-18 17:35:02 +00:00
# if the rlnRelayCredPath config option is non-empty, then rln-relay credentials should be persisted
# if the path does not contain any credential file, then a new set is generated and pesisted in the same path
2022-12-07 11:30:32 +00:00
# if there is a credential file, then no new credentials are generated, instead the content of the file is read and used to mount rln-relay
if conf . rlnRelayCredPath ! = " " :
2022-12-13 09:26:24 +00:00
rlnRelayCredPath = joinPath ( conf . rlnRelayCredPath , RlnCredentialsFilename )
2022-10-03 09:48:01 +00:00
debug " rln-relay credential path " , rlnRelayCredPath
2022-12-07 11:30:32 +00:00
2022-08-18 17:35:02 +00:00
# check if there is an rln-relay credential file in the supplied path
2022-10-28 09:13:05 +00:00
if fileExists ( rlnRelayCredPath ) :
2022-12-07 11:30:32 +00:00
2022-10-28 09:13:05 +00:00
info " A RLN credential file exists in provided path " , path = rlnRelayCredPath
2022-12-07 11:30:32 +00:00
2022-08-18 17:35:02 +00:00
# retrieve rln-relay credential
2022-10-28 09:13:05 +00:00
let readCredentialsRes = readRlnCredentials ( rlnRelayCredPath , conf . rlnRelayCredentialsPassword )
2022-12-07 11:30:32 +00:00
2022-10-28 09:13:05 +00:00
if readCredentialsRes . isErr ( ) :
2022-12-13 09:26:24 +00:00
return err ( " RLN credentials cannot be read: " & readCredentialsRes . error ( ) )
2022-10-28 09:13:05 +00:00
credentials = readCredentialsRes . get ( )
2022-08-18 17:35:02 +00:00
else : # there is no credential file available in the supplied path
2022-12-07 11:30:32 +00:00
# mount the rln-relay protocol leaving rln-relay credentials arguments unassigned
2022-08-18 17:35:02 +00:00
# this infroms mountRlnRelayDynamic proc that new credentials should be generated and registered to the membership contract
info " no rln credential is provided "
2022-12-07 11:30:32 +00:00
2022-10-12 02:18:11 +00:00
if credentials . isSome ( ) :
# mount rln-relay in on-chain mode, with credentials that were read or generated
2022-12-13 09:26:24 +00:00
rlnRelayRes = await mountRlnRelayDynamic ( wakuRelay ,
2023-01-16 12:56:18 +00:00
memContractAddr = ethMemContractAddress ,
2022-12-13 09:26:24 +00:00
ethClientAddr = ethClientAddr ,
2023-01-16 12:56:18 +00:00
ethAccountAddress = ethAccountAddressOpt ,
ethAccountPrivKeyOpt = ethAccountPrivKeyOpt ,
2022-12-13 09:26:24 +00:00
pubsubTopic = conf . rlnRelayPubsubTopic ,
2023-01-16 12:56:18 +00:00
contentTopic = conf . rlnRelayContentTopic ,
spamHandler = spamHandler ,
2022-12-13 09:26:24 +00:00
registrationHandler = registrationHandler ,
2022-12-14 11:28:09 +00:00
memIdCredential = some ( credentials . get ( ) . identityCredential ) ,
2023-01-16 12:56:18 +00:00
memIndex = some ( credentials . get ( ) . rlnIndex ) )
2022-10-12 02:18:11 +00:00
else :
2022-12-07 11:30:32 +00:00
# mount rln-relay in on-chain mode, with the provided private key
2022-12-13 09:26:24 +00:00
rlnRelayRes = await mountRlnRelayDynamic ( wakuRelay ,
2023-01-16 12:56:18 +00:00
memContractAddr = ethMemContractAddress ,
2022-12-13 09:26:24 +00:00
ethClientAddr = ethClientAddr ,
2023-01-16 12:56:18 +00:00
ethAccountAddress = ethAccountAddressOpt ,
ethAccountPrivKeyOpt = ethAccountPrivKeyOpt ,
2022-12-13 09:26:24 +00:00
pubsubTopic = conf . rlnRelayPubsubTopic ,
2023-01-16 12:56:18 +00:00
contentTopic = conf . rlnRelayContentTopic ,
spamHandler = spamHandler ,
2022-12-13 09:26:24 +00:00
registrationHandler = registrationHandler )
persistCredentials = true
2022-08-18 17:35:02 +00:00
2022-06-27 19:35:26 +00:00
else :
2022-08-18 17:35:02 +00:00
# do not persist or use a persisted rln-relay credential
# a new credential will be generated during the mount process but will not be persisted
info " no need to persist or use a persisted rln-relay credential "
2022-12-13 09:26:24 +00:00
rlnRelayRes = await mountRlnRelayDynamic ( wakuRelay ,
2023-01-16 12:56:18 +00:00
memContractAddr = ethMemContractAddress ,
2022-12-13 09:26:24 +00:00
ethClientAddr = ethClientAddr ,
2023-01-16 12:56:18 +00:00
ethAccountAddress = ethAccountAddressOpt ,
ethAccountPrivKeyOpt = ethAccountPrivKeyOpt ,
2022-12-13 09:26:24 +00:00
pubsubTopic = conf . rlnRelayPubsubTopic ,
2023-01-16 12:56:18 +00:00
contentTopic = conf . rlnRelayContentTopic ,
spamHandler = spamHandler ,
2022-12-13 09:26:24 +00:00
registrationHandler = registrationHandler )
if rlnRelayRes . isErr ( ) :
return err ( " dynamic rln-relay could not be mounted: " & rlnRelayRes . error ( ) )
let wakuRlnRelay = rlnRelayRes . get ( )
if persistCredentials :
# persist rln credential
2023-01-16 12:56:18 +00:00
credentials = some ( RlnMembershipCredentials ( rlnIndex : wakuRlnRelay . membershipIndex ,
2022-12-14 11:28:09 +00:00
identityCredential : wakuRlnRelay . identityCredential ) )
2022-12-13 09:26:24 +00:00
if writeRlnCredentials ( rlnRelayCredPath , credentials . get ( ) , conf . rlnRelayCredentialsPassword ) . isErr ( ) :
return err ( " error in storing rln credentials " )
return ok ( wakuRlnRelay )
proc new * ( T : type WakuRlnRelay ,
wakuRelay : WakuRelay ,
conf : WakuRlnConfig ,
spamHandler : Option [ SpamHandler ] = none ( SpamHandler ) ,
registrationHandler : Option [ RegistrationHandler ] = none ( RegistrationHandler )
) : Future [ RlnRelayResult [ WakuRlnRelay ] ] {. async . } =
2022-11-04 03:00:42 +00:00
## Mounts the rln-relay protocol on the node.
## The rln-relay protocol can be mounted in two modes: on-chain and off-chain.
## Returns an error if the rln-relay protocol could not be mounted.
2022-12-13 09:26:24 +00:00
# check whether inputs are provided
# relay protocol is the prerequisite of rln-relay
if wakuRelay . isNil ( ) :
return err ( " WakuRelay protocol is not mounted " )
# check whether the pubsub topic is supported at the relay level
if conf . rlnRelayPubsubTopic notin wakuRelay . defaultPubsubTopics :
return err ( " The relay protocol does not support the configured pubsub topic " )
debug " rln-relay input validation passed "
2023-01-16 12:56:18 +00:00
waku_rln_relay_mounting_duration_seconds . nanosecondTime :
2022-12-13 09:26:24 +00:00
let rlnRelayRes = await mount (
wakuRelay ,
2022-09-30 12:43:42 +00:00
conf ,
spamHandler ,
registrationHandler
)
2023-01-16 12:56:18 +00:00
return rlnRelayRes