go-rln/rln/waku_rln_relay.go
Richard Ramos 134f023897
test: rln
2022-07-02 10:38:11 -04:00

297 lines
8.8 KiB
Go

package rln
import (
"bytes"
"encoding/hex"
"errors"
"math"
"github.com/decanus/go-rln/rln/pb"
)
type WakuRLNRelay struct {
MembershipKeyPair MembershipKeyPair
// membershipIndex denotes the index of a leaf in the Merkle tree
// that contains the pk of the current peer
// this index is used to retrieve the peer's authentication path
MembershipIndex MembershipIndex
// MembershipContractAddress*: Address
//ethClientAddress*: string
//ethAccountAddress*: Address
// this field is required for signing transactions
// TODO may need to erase this ethAccountPrivateKey when is not used
// TODO may need to make ethAccountPrivateKey mandatory
//ethAccountPrivateKey*: PrivateKey
RLNInstance *RLN
// the pubsub topic for which rln relay is mounted
PubsubTopic string
// contentTopic should be of type waku_message.ContentTopic, however, due to recursive module dependency, the underlying type of ContentTopic is used instead
// TODO a long-term solution is to place types with recursive dependency inside one file
ContentTopic string
// the log of nullifiers and Shamir shares of the past messages grouped per epoch
NullifierLog map[Epoch][]ProofMetadata
}
func CalcMerkleRoot(list []IDCommitment) (MerkleNode, error) {
// returns the root of the Merkle tree that is computed from the supplied list
rln, err := NewRLN()
if err != nil {
return MerkleNode{}, err
}
// create a Merkle tree
for _, c := range list {
if !rln.InsertMember(c) {
return MerkleNode{}, errors.New("could not add member")
}
}
return rln.GetMerkleRoot()
}
func createMembershipList(n int) ([][]string, string, error) {
// createMembershipList produces a sequence of membership key pairs in the form of (identity key, id commitment keys) in the hexadecimal format
// 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)
// initialize a Merkle tree
rln, err := NewRLN()
if err != nil {
return nil, "", err
}
var output [][]string
for i := 0; i < n; i++ {
// generate a keypair
keypair, err := rln.MembershipKeyGen()
if err != nil {
return nil, "", err
}
output = append(output, []string{hex.EncodeToString(keypair.IDKey[:]), hex.EncodeToString(keypair.IDCommitment[:])})
// insert the key to the Merkle tree
if !rln.InsertMember(keypair.IDCommitment) {
return nil, "", errors.New("could not insert member")
}
}
root, err := rln.GetMerkleRoot()
if err != nil {
return nil, "", err
}
return output, hex.EncodeToString(root[:]), nil
}
func RLNRelayStaticSetUp(rlnRelayMemIndex MembershipIndex) ([]IDCommitment, MembershipKeyPair, MembershipIndex, error) {
// static group
groupKeys := STATIC_GROUP_KEYS
groupSize := STATIC_GROUP_SIZE
// validate the user-supplied membership index
if rlnRelayMemIndex < MembershipIndex(0) || rlnRelayMemIndex >= MembershipIndex(groupSize) {
return nil, MembershipKeyPair{}, 0, errors.New("wrong membership index")
}
// prepare the outputs from the static group keys
// create a sequence of MembershipKeyPairs from the group keys (group keys are in string format)
groupKeyPairs, err := toMembershipKeyPairs(groupKeys)
if err != nil {
return nil, MembershipKeyPair{}, 0, errors.New("invalid data on group keypairs")
}
// extract id commitment keys
var groupOpt []IDCommitment
for _, c := range groupKeyPairs {
groupOpt = append(groupOpt, c.IDCommitment)
}
// user selected membership key pair
memKeyPairOpt := groupKeyPairs[rlnRelayMemIndex]
memIndexOpt := rlnRelayMemIndex
return groupOpt, memKeyPairOpt, memIndexOpt, nil
}
func (rln *WakuRLNRelay) HasDuplicate(msg *pb.WakuMessage) (bool, error) {
// returns true if there is another message in the `nullifierLog` of the `rlnPeer` with the same
// epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares
// otherwise, returns false
if msg == nil {
return false, errors.New("nil message")
}
msgProof := ToRateLimitProof(msg)
// extract the proof metadata of the supplied `msg`
proofMD := ProofMetadata{
Nullifier: msgProof.Nullifier,
ShareX: msgProof.ShareX,
ShareY: msgProof.ShareY,
}
proofs, ok := rln.NullifierLog[msgProof.Epoch]
// check if the epoch exists
if !ok {
return false, nil
}
for _, p := range proofs {
if p.Equals(proofMD) {
// there is an identical record, ignore rhe mag
return false, nil
}
}
// check for a message with the same nullifier but different secret shares
matched := false
for _, it := range proofs {
if bytes.Equal(it.Nullifier[:], proofMD.Nullifier[:]) && (!bytes.Equal(it.ShareX[:], proofMD.ShareX[:]) || !bytes.Equal(it.ShareY[:], proofMD.ShareY[:])) {
matched = true
break
}
}
return matched, nil
}
func (rln *WakuRLNRelay) UpdateLog(msg *pb.WakuMessage) (bool, error) {
// extracts the `ProofMetadata` of the supplied messages `msg` and
// saves it in the `nullifierLog` of the `rlnPeer`
if msg == nil {
return false, errors.New("nil message")
}
msgProof := ToRateLimitProof(msg)
proofMD := ProofMetadata{
Nullifier: msgProof.Nullifier,
ShareX: msgProof.ShareX,
ShareY: msgProof.ShareY,
}
proofs, ok := rln.NullifierLog[msgProof.Epoch]
// check if the epoch exists
if !ok {
rln.NullifierLog[msgProof.Epoch] = []ProofMetadata{proofMD}
return true, nil
}
// check if an identical record exists
for _, p := range proofs {
if p.Equals(proofMD) {
return true, nil
}
}
// add proofMD to the log
proofs = append(proofs, proofMD)
rln.NullifierLog[msgProof.Epoch] = proofs
return true, nil
}
// TODO: change optionalTime data type
func (rln *WakuRLNRelay) ValidateMessage(msg *pb.WakuMessage, optionalTime float64) (MessageValidationResult, error) {
// validate the supplied `msg` based on the waku-rln-relay routing protocol i.e.,
// the `msg`'s epoch is within MAX_EPOCH_GAP of the current epoch
// the `msg` has valid rate limit proof
// the `msg` does not violate the rate limit
// `timeOption` indicates Unix epoch time (fractional part holds sub-seconds)
// if `timeOption` is supplied, then the current epoch is calculated based on that
if msg == nil {
return MessageValidationResult_Unknown, errors.New("nil message")
}
// checks if the `msg`'s epoch is far from the current epoch
// it corresponds to the validation of rln external nullifier
var epoch Epoch
if optionalTime != 0 {
epoch = CalcEpoch(optionalTime)
} else {
// get current rln epoch
epoch = GetCurrentEpoch()
}
msgProof := ToRateLimitProof(msg)
// calculate the gaps
gap := Diff(epoch, msgProof.Epoch)
// validate the epoch
if int64(math.Abs(float64(gap))) >= MAX_EPOCH_GAP {
// message's epoch is too old or too ahead
// accept messages whose epoch is within +-MAX_EPOCH_GAP from the current epoch
//debug "invalid message: epoch gap exceeds a threshold", gap = gap, payload = string.fromBytes(msg.payload)
return MessageValidationResult_Invalid, nil
}
// verify the proof
contentTopicBytes := []byte(msg.ContentTopic)
input := append(msg.Payload, contentTopicBytes...)
if !rln.RLNInstance.Verify(input, *msgProof) {
// invalid proof
//debug "invalid message: invalid proof", payload = string.fromBytes(msg.payload)
return MessageValidationResult_Invalid, nil
}
// check if double messaging has happened
hasDup, err := rln.HasDuplicate(msg)
if err != nil {
return MessageValidationResult_Unknown, err
}
if hasDup {
// debug "invalid message: message is a spam", payload = string.fromBytes(msg.payload)
return MessageValidationResult_Spam, nil
}
// insert the message to the log
// the result of `updateLog` is discarded because message insertion is guaranteed by the implementation i.e.,
// it will never error out
_, err = rln.UpdateLog(msg)
if err != nil {
return MessageValidationResult_Unknown, err
}
//debug "message is valid", payload = string.fromBytes(msg.payload)
return MessageValidationResult_Valid, nil
}
// TODO: change senderEpochTIme datatype
func (rln *WakuRLNRelay) AppendRLNProof(msg *pb.WakuMessage, senderEpochTime float64) error {
// returns error if it could not create and append a `RateLimitProof` to the supplied `msg`
// `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()`)
if msg == nil {
return errors.New("nil message")
}
input := ToRLNSignal(msg)
proof, err := rln.RLNInstance.GenerateProof(input, rln.MembershipKeyPair, rln.MembershipIndex, CalcEpoch(senderEpochTime))
if err != nil {
return err
}
msg.RateLimitProof = &pb.RateLimitProof{
Proof: proof.Proof[:],
MerkleRoot: proof.MerkleRoot[:],
Epoch: proof.Epoch[:],
ShareX: proof.ShareX[:],
ShareY: proof.ShareY[:],
Nullifier: proof.Nullifier[:],
}
return nil
}