2022-08-19 16:34:07 +00:00
package rln
import "C"
import (
"encoding/binary"
2023-08-22 10:32:01 +00:00
"encoding/json"
2022-08-19 16:34:07 +00:00
"errors"
2023-04-07 18:23:07 +00:00
"fmt"
2022-10-05 22:12:51 +00:00
2023-04-07 18:23:07 +00:00
"github.com/waku-org/go-zerokit-rln/rln/link"
2022-08-19 16:34:07 +00:00
)
// RLN represents the context used for rln.
type RLN struct {
2023-04-07 18:23:07 +00:00
w * link . RLNWrapper
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
func getResourcesFolder ( depth TreeDepth ) string {
return fmt . Sprintf ( "tree_height_%d" , depth )
}
2022-10-05 22:12:51 +00:00
// NewRLN generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. It uses a depth of 20 by default
func NewRLN ( ) ( * RLN , error ) {
2023-08-22 10:32:01 +00:00
return NewWithConfig ( DefaultTreeDepth , nil )
}
2022-10-05 22:12:51 +00:00
2023-08-22 10:32:01 +00:00
// NewRLNWithParams generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. The parameter `depth“ indicates the depth of Merkle tree
func NewRLNWithParams ( depth int , wasm [ ] byte , zkey [ ] byte , verifKey [ ] byte , treeConfig * TreeConfig ) ( * RLN , error ) {
2022-10-05 22:12:51 +00:00
r := & RLN { }
2023-08-22 10:32:01 +00:00
var err error
2022-10-05 22:12:51 +00:00
2023-08-22 10:32:01 +00:00
treeConfigBytes := [ ] byte { }
if treeConfig != nil {
treeConfigBytes , err = json . Marshal ( treeConfig )
if err != nil {
return nil , err
}
}
2022-10-05 22:12:51 +00:00
2023-08-22 10:32:01 +00:00
r . w , err = link . NewWithParams ( depth , wasm , zkey , verifKey , treeConfigBytes )
2023-04-07 18:23:07 +00:00
if err != nil {
return nil , err
2022-10-05 22:12:51 +00:00
}
return r , nil
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
// NewWithConfig generates an instance of RLN. An instance supports both zkSNARKs logics
// and Merkle tree data structure and operations. The parameter `depth` indicates the depth of Merkle tree
func NewWithConfig ( depth TreeDepth , treeConfig * TreeConfig ) ( * RLN , error ) {
2022-08-19 16:34:07 +00:00
r := & RLN { }
2023-04-07 18:23:07 +00:00
var err error
2022-08-19 16:34:07 +00:00
2023-08-22 10:32:01 +00:00
configBytes , err := json . Marshal ( config {
ResourcesFolder : getResourcesFolder ( depth ) ,
TreeConfig : treeConfig ,
} )
2023-04-07 18:23:07 +00:00
if err != nil {
return nil , err
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
r . w , err = link . New ( int ( depth ) , configBytes )
2023-04-07 18:23:07 +00:00
if err != nil {
return nil , err
2022-08-19 16:34:07 +00:00
}
return r , nil
}
2023-08-22 10:32:01 +00:00
func ( r * RLN ) SetTree ( treeHeight uint ) error {
success := r . w . SetTree ( treeHeight )
if ! success {
return errors . New ( "could not set tree height" )
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
return nil
}
// Initialize merkle tree with a list of IDCommitments
func ( r * RLN ) InitTreeWithMembers ( idComms [ ] IDCommitment ) error {
idCommBytes := serializeCommitments ( idComms )
initSuccess := r . w . InitTreeWithLeaves ( idCommBytes )
if ! initSuccess {
return errors . New ( "could not init tree" )
}
return nil
}
2022-08-19 16:34:07 +00:00
2023-08-22 10:32:01 +00:00
func toIdentityCredential ( generatedKeys [ ] byte ) ( * IdentityCredential , error ) {
2023-04-07 18:23:07 +00:00
key := & IdentityCredential {
IDTrapdoor : [ 32 ] byte { } ,
IDNullifier : [ 32 ] byte { } ,
IDSecretHash : [ 32 ] byte { } ,
2022-08-19 16:34:07 +00:00
IDCommitment : [ 32 ] byte { } ,
}
2023-04-07 18:23:07 +00:00
if len ( generatedKeys ) != 32 * 4 {
return nil , errors . New ( "generated keys are of invalid length" )
2022-08-19 16:34:07 +00:00
}
2023-04-07 18:23:07 +00:00
copy ( key . IDTrapdoor [ : ] , generatedKeys [ : 32 ] )
copy ( key . IDNullifier [ : ] , generatedKeys [ 32 : 64 ] )
copy ( key . IDSecretHash [ : ] , generatedKeys [ 64 : 96 ] )
copy ( key . IDCommitment [ : ] , generatedKeys [ 96 : 128 ] )
2022-08-19 16:34:07 +00:00
return key , nil
}
2023-08-22 10:32:01 +00:00
// MembershipKeyGen generates a IdentityCredential that can be used for the
// registration into the rln membership contract. Returns an error if the key generation fails
func ( r * RLN ) MembershipKeyGen ( ) ( * IdentityCredential , error ) {
generatedKeys := r . w . ExtendedKeyGen ( )
if generatedKeys == nil {
return nil , errors . New ( "error in key generation" )
}
return toIdentityCredential ( generatedKeys )
}
// SeededMembershipKeyGen generates a deterministic IdentityCredential using a seed
// that can be used for the registration into the rln membership contract.
// Returns an error if the key generation fails
func ( r * RLN ) SeededMembershipKeyGen ( seed [ ] byte ) ( * IdentityCredential , error ) {
generatedKeys := r . w . ExtendedSeededKeyGen ( seed )
if generatedKeys == nil {
return nil , errors . New ( "error in key generation" )
}
return toIdentityCredential ( generatedKeys )
}
2022-08-19 16:34:07 +00:00
// appendLength returns length prefixed version of the input with the following format
// [len<8>|input<var>], the len is a 8 byte value serialized in little endian
2023-04-07 18:23:07 +00:00
2022-08-19 16:34:07 +00:00
func appendLength ( input [ ] byte ) [ ] byte {
inputLen := make ( [ ] byte , 8 )
binary . LittleEndian . PutUint64 ( inputLen , uint64 ( len ( input ) ) )
return append ( inputLen , input ... )
}
2023-04-07 18:23:07 +00:00
func ( r * RLN ) Sha256 ( data [ ] byte ) ( MerkleNode , error ) {
lenPrefData := appendLength ( data )
2022-08-19 16:34:07 +00:00
2023-04-07 18:23:07 +00:00
b , err := r . w . Hash ( lenPrefData )
if err != nil {
return MerkleNode { } , err
2022-08-19 16:34:07 +00:00
}
2023-04-07 18:23:07 +00:00
var result MerkleNode
copy ( result [ : ] , b )
return result , nil
2022-08-19 16:34:07 +00:00
}
2023-04-07 18:23:07 +00:00
func ( r * RLN ) Poseidon ( input ... [ ] byte ) ( MerkleNode , error ) {
data := serializeSlice ( input )
2022-08-19 16:34:07 +00:00
2023-04-07 18:23:07 +00:00
inputLen := make ( [ ] byte , 8 )
binary . LittleEndian . PutUint64 ( inputLen , uint64 ( len ( input ) ) )
2022-08-19 16:34:07 +00:00
2023-04-07 18:23:07 +00:00
lenPrefData := append ( inputLen , data ... )
2022-08-19 16:34:07 +00:00
2023-04-07 18:23:07 +00:00
b , err := r . w . PoseidonHash ( lenPrefData )
if err != nil {
return MerkleNode { } , err
2022-08-19 16:34:07 +00:00
}
var result MerkleNode
copy ( result [ : ] , b )
return result , nil
}
2023-04-07 18:23:07 +00:00
func ( r * RLN ) ExtractMetadata ( proof RateLimitProof ) ( ProofMetadata , error ) {
externalNullifierRes , err := r . Poseidon ( proof . Epoch [ : ] , proof . RLNIdentifier [ : ] )
if err != nil {
return ProofMetadata { } , fmt . Errorf ( "could not construct the external nullifier: %w" , err )
}
return ProofMetadata {
Nullifier : proof . Nullifier ,
ShareX : proof . ShareX ,
ShareY : proof . ShareY ,
ExternalNullifier : externalNullifierRes ,
} , nil
}
2022-08-19 16:34:07 +00:00
// GenerateProof generates a proof for the RLN given a KeyPair and the index in a merkle tree.
2022-10-05 22:12:51 +00:00
// The output will containt the proof data and should be parsed as |proof<128>|root<32>|epoch<32>|share_x<32>|share_y<32>|nullifier<32>|
2022-08-19 16:34:07 +00:00
// integers wrapped in <> indicate value sizes in bytes
2023-04-07 18:23:07 +00:00
func ( r * RLN ) GenerateProof ( data [ ] byte , key IdentityCredential , index MembershipIndex , epoch Epoch ) ( * RateLimitProof , error ) {
input := serialize ( key . IDSecretHash , index , epoch , data )
proofBytes , err := r . w . GenerateRLNProof ( input )
if err != nil {
return nil , err
2022-08-19 16:34:07 +00:00
}
2022-10-05 22:12:51 +00:00
if len ( proofBytes ) != 320 {
2022-08-19 16:34:07 +00:00
return nil , errors . New ( "invalid proof generated" )
}
2022-10-05 22:12:51 +00:00
// parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ]
proofOffset := 128
2022-08-19 16:34:07 +00:00
rootOffset := proofOffset + 32
epochOffset := rootOffset + 32
shareXOffset := epochOffset + 32
shareYOffset := shareXOffset + 32
nullifierOffset := shareYOffset + 32
2022-10-05 22:12:51 +00:00
rlnIdentifierOffset := nullifierOffset + 32
2022-08-19 16:34:07 +00:00
var zkproof ZKSNARK
var proofRoot , shareX , shareY MerkleNode
var epochR Epoch
var nullifier Nullifier
2022-10-05 22:12:51 +00:00
var rlnIdentifier RLNIdentifier
2022-08-19 16:34:07 +00:00
copy ( zkproof [ : ] , proofBytes [ 0 : proofOffset ] )
copy ( proofRoot [ : ] , proofBytes [ proofOffset : rootOffset ] )
copy ( epochR [ : ] , proofBytes [ rootOffset : epochOffset ] )
copy ( shareX [ : ] , proofBytes [ epochOffset : shareXOffset ] )
copy ( shareY [ : ] , proofBytes [ shareXOffset : shareYOffset ] )
copy ( nullifier [ : ] , proofBytes [ shareYOffset : nullifierOffset ] )
2022-10-05 22:12:51 +00:00
copy ( rlnIdentifier [ : ] , proofBytes [ nullifierOffset : rlnIdentifierOffset ] )
2022-08-19 16:34:07 +00:00
return & RateLimitProof {
2022-10-05 22:12:51 +00:00
Proof : zkproof ,
MerkleRoot : proofRoot ,
Epoch : epochR ,
ShareX : shareX ,
ShareY : shareY ,
Nullifier : nullifier ,
RLNIdentifier : rlnIdentifier ,
2022-08-19 16:34:07 +00:00
} , nil
}
2023-04-07 18:23:07 +00:00
func serialize32 ( roots [ ] [ 32 ] byte ) [ ] byte {
var result [ ] byte
for _ , r := range roots {
result = append ( result , r [ : ] ... )
2022-08-19 16:34:07 +00:00
}
2023-04-07 18:23:07 +00:00
return result
2022-11-04 13:57:20 +00:00
}
2023-04-07 18:23:07 +00:00
func serializeSlice ( roots [ ] [ ] byte ) [ ] byte {
2022-11-04 13:57:20 +00:00
var result [ ] byte
for _ , r := range roots {
result = append ( result , r [ : ] ... )
}
return result
}
2022-11-28 13:40:21 +00:00
func serializeCommitments ( commitments [ ] IDCommitment ) [ ] byte {
// serializes a seq of IDCommitments to a byte seq
// the serialization is based on https://github.com/status-im/nwaku/blob/37bd29fbc37ce5cf636734e7dd410b1ed27b88c8/waku/v2/protocol/waku_rln_relay/rln.nim#L142
// the order of serialization is |id_commitment_len<8>|id_commitment<var>|
var result [ ] byte
inputLen := make ( [ ] byte , 8 )
binary . LittleEndian . PutUint64 ( inputLen , uint64 ( len ( commitments ) ) )
result = append ( result , inputLen ... )
for _ , idComm := range commitments {
result = append ( result , idComm [ : ] ... )
}
return result
}
2023-08-22 10:32:01 +00:00
func serializeIndices ( indices [ ] MembershipIndex ) [ ] byte {
var result [ ] byte
inputLen := make ( [ ] byte , 8 )
binary . LittleEndian . PutUint64 ( inputLen , uint64 ( len ( indices ) ) )
result = append ( result , inputLen ... )
for _ , index := range indices {
result = binary . LittleEndian . AppendUint64 ( result , uint64 ( index ) )
}
return result
}
2023-04-07 18:23:07 +00:00
// proof [ proof<128>| root<32>| epoch<32>| share_x<32>| share_y<32>| nullifier<32> | signal_len<8> | signal<var> ]
// validRoots should contain a sequence of roots in the acceptable windows.
// As default, it is set to an empty sequence of roots. This implies that the validity check for the proof's root is skipped
func ( r * RLN ) Verify ( data [ ] byte , proof RateLimitProof , roots ... [ 32 ] byte ) ( bool , error ) {
2023-08-22 10:32:01 +00:00
proofBytes := proof . serializeWithData ( data )
2023-04-07 18:23:07 +00:00
rootBytes := serialize32 ( roots )
2022-11-04 13:57:20 +00:00
2023-04-07 18:23:07 +00:00
res , err := r . w . VerifyWithRoots ( proofBytes , rootBytes )
if err != nil {
return false , err
2022-11-04 13:57:20 +00:00
}
return bool ( res ) , nil
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
// RecoverIDSecret returns an IDSecret having obtained before two proofs
func ( r * RLN ) RecoverIDSecret ( proof1 RateLimitProof , proof2 RateLimitProof ) ( IDSecretHash , error ) {
proof1Bytes := proof1 . serialize ( )
proof2Bytes := proof2 . serialize ( )
secret , err := r . w . RecoverIDSecret ( proof1Bytes , proof2Bytes )
if err != nil {
return IDSecretHash { } , err
}
var result IDSecretHash
copy ( result [ : ] , secret )
return result , nil
}
2022-08-19 16:34:07 +00:00
// InsertMember adds the member to the tree
2022-10-05 22:12:51 +00:00
func ( r * RLN ) InsertMember ( idComm IDCommitment ) error {
2023-04-07 18:23:07 +00:00
insertionSuccess := r . w . SetNextLeaf ( idComm [ : ] )
2022-10-05 22:12:51 +00:00
if ! insertionSuccess {
return errors . New ( "could not insert member" )
}
return nil
2022-08-19 16:34:07 +00:00
}
2022-11-28 13:40:21 +00:00
// Insert multiple members i.e., identity commitments starting from index
// This proc is atomic, i.e., if any of the insertions fails, all the previous insertions are rolled back
func ( r * RLN ) InsertMembers ( index MembershipIndex , idComms [ ] IDCommitment ) error {
idCommBytes := serializeCommitments ( idComms )
2023-08-22 10:32:01 +00:00
indicesBytes := serializeIndices ( nil )
insertionSuccess := r . w . AtomicOperation ( index , idCommBytes , indicesBytes )
2022-11-28 13:40:21 +00:00
if ! insertionSuccess {
return errors . New ( "could not insert members" )
}
return nil
}
2023-08-22 10:32:01 +00:00
// Insert a member in the tree at specified index
func ( r * RLN ) InsertMemberAt ( index MembershipIndex , idComm IDCommitment ) error {
insertionSuccess := r . w . SetLeaf ( index , idComm [ : ] )
if ! insertionSuccess {
return errors . New ( "could not insert member" )
}
return nil
}
2022-08-19 16:34:07 +00:00
// DeleteMember removes an IDCommitment key from the tree. The index
// parameter is the position of the id commitment key to be deleted from the tree.
// The deleted id commitment key is replaced with a zero leaf
2022-10-05 22:12:51 +00:00
func ( r * RLN ) DeleteMember ( index MembershipIndex ) error {
2023-04-07 18:23:07 +00:00
deletionSuccess := r . w . DeleteLeaf ( index )
2022-10-05 22:12:51 +00:00
if ! deletionSuccess {
return errors . New ( "could not delete member" )
}
return nil
2022-08-19 16:34:07 +00:00
}
2023-08-22 10:32:01 +00:00
// Delete multiple members
func ( r * RLN ) DeleteMembers ( indices [ ] MembershipIndex ) error {
idCommBytes := serializeCommitments ( nil )
indicesBytes := serializeIndices ( indices )
insertionSuccess := r . w . AtomicOperation ( 0 , idCommBytes , indicesBytes )
if ! insertionSuccess {
return errors . New ( "could not insert members" )
}
return nil
}
2022-08-19 16:34:07 +00:00
// GetMerkleRoot reads the Merkle Tree root after insertion
func ( r * RLN ) GetMerkleRoot ( ) ( MerkleNode , error ) {
2023-04-07 18:23:07 +00:00
b , err := r . w . GetRoot ( )
if err != nil {
return MerkleNode { } , err
2022-08-19 16:34:07 +00:00
}
if len ( b ) != 32 {
return MerkleNode { } , errors . New ( "wrong output size" )
}
var result MerkleNode
copy ( result [ : ] , b )
return result , nil
}
2023-08-22 10:32:01 +00:00
// GetLeaf reads the value stored at some index in the Merkle Tree
func ( r * RLN ) GetLeaf ( index MembershipIndex ) ( IDCommitment , error ) {
b , err := r . w . GetLeaf ( index )
if err != nil {
return IDCommitment { } , err
}
if len ( b ) != 32 {
return IDCommitment { } , errors . New ( "wrong output size" )
}
var result IDCommitment
copy ( result [ : ] , b )
return result , nil
}
2022-08-19 16:34:07 +00:00
// AddAll adds members to the Merkle tree
2022-10-05 22:12:51 +00:00
func ( r * RLN ) AddAll ( list [ ] IDCommitment ) error {
2022-08-19 16:34:07 +00:00
for _ , member := range list {
2022-10-05 22:12:51 +00:00
if err := r . InsertMember ( member ) ; err != nil {
return err
2022-08-19 16:34:07 +00:00
}
}
2022-10-05 22:12:51 +00:00
return nil
2022-08-19 16:34:07 +00:00
}
// CalcMerkleRoot returns the root of the Merkle tree that is computed from the supplied list
2022-10-05 22:12:51 +00:00
func CalcMerkleRoot ( list [ ] IDCommitment ) ( MerkleNode , error ) {
rln , err := NewRLN ( )
2022-08-19 16:34:07 +00:00
if err != nil {
return MerkleNode { } , err
}
2023-08-22 10:32:01 +00:00
if err := rln . InsertMembers ( 0 , list ) ; err != nil {
return MerkleNode { } , err
2022-08-19 16:34:07 +00:00
}
return rln . GetMerkleRoot ( )
}
// CreateMembershipList produces a list of membership key pairs and also returns the root of a Merkle tree constructed
// out of the identity commitment keys of the generated list. The output of this function is used to initialize a static
// group keys (to test waku-rln-relay in the off-chain mode)
2023-04-07 18:23:07 +00:00
func CreateMembershipList ( n int ) ( [ ] IdentityCredential , MerkleNode , error ) {
2022-08-19 16:34:07 +00:00
// initialize a Merkle tree
2022-10-05 22:12:51 +00:00
rln , err := NewRLN ( )
2022-08-19 16:34:07 +00:00
if err != nil {
return nil , MerkleNode { } , err
}
2023-04-07 18:23:07 +00:00
var output [ ] IdentityCredential
2022-08-19 16:34:07 +00:00
for i := 0 ; i < n ; i ++ {
// generate a keypair
keypair , err := rln . MembershipKeyGen ( )
if err != nil {
return nil , MerkleNode { } , err
}
output = append ( output , * keypair )
// insert the key to the Merkle tree
2022-10-05 22:12:51 +00:00
if err := rln . InsertMember ( keypair . IDCommitment ) ; err != nil {
return nil , MerkleNode { } , err
2022-08-19 16:34:07 +00:00
}
}
root , err := rln . GetMerkleRoot ( )
if err != nil {
return nil , MerkleNode { } , err
}
return output , root , nil
}
2023-08-22 10:32:01 +00:00
// SetMetadata stores serialized data
func ( r * RLN ) SetMetadata ( metadata [ ] byte ) error {
success := r . w . SetMetadata ( metadata )
if ! success {
return errors . New ( "could not set metadata" )
}
return nil
}
// GetMetadata returns the stored serialized metadata
func ( r * RLN ) GetMetadata ( ) ( [ ] byte , error ) {
return r . w . GetMetadata ( )
}
// AtomicOperation can be used to insert and remove elements into the merkle tree
func ( r * RLN ) AtomicOperation ( index MembershipIndex , idCommsToInsert [ ] IDCommitment , indicesToRemove [ ] MembershipIndex ) error {
idCommBytes := serializeCommitments ( idCommsToInsert )
indicesBytes := serializeIndices ( indicesToRemove )
execSuccess := r . w . AtomicOperation ( index , idCommBytes , indicesBytes )
if ! execSuccess {
return errors . New ( "could not execute atomic_operation" )
}
return nil
}
// Flush
func ( r * RLN ) Flush ( ) error {
success := r . w . Flush ( )
if ! success {
return errors . New ( "cannot flush db" )
}
return nil
}