diff --git a/go.mod b/go.mod index a77a34e..40a69db 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/stretchr/testify v1.7.2 - github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b + github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442 github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 ) diff --git a/go.sum b/go.sum index 11a68f9..4112990 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,10 @@ github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116092850-ad40ef83a1b5 h1:yqco0Gy1zdBDkHMAPqJ95jSlPVUxAsxK5xVeiUdxQN8= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116092850-ad40ef83a1b5/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442 h1:xDs9CAyhgOq9PXo06zTnd8VsPjngAyTGujeDTfsTwGg= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 h1:4HSdWMFMufpRo3ECTX6BrvA+VzKhXZf7mS0rTa5cCWU= diff --git a/rln/link/apple.go b/rln/link/apple.go index bd3f694..6f3d80e 100644 --- a/rln/link/apple.go +++ b/rln/link/apple.go @@ -83,6 +83,10 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) { return i.ffi.GetLeaf(index) } +func (i RLNWrapper) GetMerkleProof(index uint) ([]byte, error) { + return i.ffi.GetMerkleProof(index) +} + func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } diff --git a/rln/link/arm.go b/rln/link/arm.go index ec78c45..74aff53 100644 --- a/rln/link/arm.go +++ b/rln/link/arm.go @@ -82,6 +82,10 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) { return i.ffi.GetLeaf(index) } +func (i RLNWrapper) GetProof(index uint) ([]byte, error) { + return i.ffi.GetProof(index) +} + func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } diff --git a/rln/link/x86_64.go b/rln/link/x86_64.go index 9c73af3..db583a1 100644 --- a/rln/link/x86_64.go +++ b/rln/link/x86_64.go @@ -83,6 +83,10 @@ func (i RLNWrapper) GetLeaf(index uint) ([]byte, error) { return i.ffi.GetLeaf(index) } +func (i RLNWrapper) GetProof(index uint) ([]byte, error) { + return i.ffi.GetProof(index) +} + func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } diff --git a/rln/rln.go b/rln/rln.go index a4ff145..78b3860 100644 --- a/rln/rln.go +++ b/rln/rln.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "github.com/waku-org/go-zerokit-rln/rln/link" ) @@ -393,6 +394,62 @@ func (r *RLN) GetLeaf(index MembershipIndex) (IDCommitment, error) { return result, nil } +// GetMerkleProof returns the Merkle proof for the element at the specified index +// The output should be parsed as: num_elements<8>|path_elements|num_indexes<8>|path_indexes +// where num_elements indicate var1 array size and num_indexes indicate var2 array size. +// Both num_elements and num_indexes shall be equal and match the tree depth. +// A tree with depth 20 has 676 bytes = 8 + 32 * 20 + 8 + 20 * 1 +// Proof elements are stored as little endian +func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) { + proofBytes, err := r.w.GetMerkleProof(index) + if err != nil { + return MerkleProof{}, err + } + + var result MerkleProof + var numElements big.Int + var numIndexes big.Int + + offset := 0 + + // Get amounf of elements in the proof + numElements.SetBytes(revert(proofBytes[offset : offset+8])) + offset += 8 + + result.PathElements = make([]MerkleNode, numElements.Uint64()) + + for i := uint64(0); i < numElements.Uint64(); i++ { + copy(result.PathElements[i][:], proofBytes[offset:offset+32]) + offset += 32 + } + + // Get amount of indexes in the path + numIndexes.SetBytes(revert(proofBytes[offset : offset+8])) + offset += 8 + + // Both numElements and numIndexes shall be equal and match the tree depth. + if numIndexes.Uint64() != numElements.Uint64() { + return MerkleProof{}, errors.New(fmt.Sprintf("amount of values in path and indexes do not match: %s vs %s", + numElements.String(), numIndexes.String())) + } + + // TODO: Depth check, but currently not accesible + + result.PathIndexes = make([]uint8, numIndexes.Uint64()) + + for i := uint64(0); i < numIndexes.Uint64(); i++ { + result.PathIndexes[i] = proofBytes[offset] + offset += 1 + } + + if offset != len(proofBytes) { + return MerkleProof{}, errors.New( + fmt.Sprintf("error parsing proof read: %d, length; %d", offset, len(proofBytes))) + } + + return result, nil +} + // AddAll adds members to the Merkle tree func (r *RLN) AddAll(list []IDCommitment) error { for _, member := range list { diff --git a/rln/rln_test.go b/rln/rln_test.go index bf60a33..3a3fc65 100644 --- a/rln/rln_test.go +++ b/rln/rln_test.go @@ -312,6 +312,44 @@ func (s *RLNSuite) TestInvalidProof() { s.False(verified) } +func (s *RLNSuite) TestGetMerkleProof() { + for _, treeDepth := range []TreeDepth{TreeDepth15, TreeDepth19, TreeDepth20} { + treeDepthInt := int(treeDepth) + + rln, err := NewWithConfig(treeDepth, nil) + s.NoError(err) + + leaf0 := [32]byte{0x00} + leaf1 := [32]byte{0x01} + leaf5 := [32]byte{0x05} + + rln.InsertMemberAt(0, leaf0) + rln.InsertMemberAt(1, leaf1) + rln.InsertMemberAt(5, leaf5) + + b1, err := rln.GetMerkleProof(0) + s.NoError(err) + s.Equal(treeDepthInt, len(b1.PathElements)) + s.Equal(treeDepthInt, len(b1.PathIndexes)) + // First path is right leaf [0, 1] + s.EqualValues(leaf1, b1.PathElements[0]) + + b2, err := rln.GetMerkleProof(4) + s.NoError(err) + s.Equal(treeDepthInt, len(b2.PathElements)) + s.Equal(treeDepthInt, len(b2.PathIndexes)) + // First path is right leaf [4, 5] + s.EqualValues(leaf5, b2.PathElements[0]) + + b3, err := rln.GetMerkleProof(10) + s.NoError(err) + s.Equal(treeDepthInt, len(b3.PathElements)) + s.Equal(treeDepthInt, len(b3.PathIndexes)) + // First path is right leaf. But its empty + s.EqualValues([32]byte{0x00}, b3.PathElements[0]) + } +} + func (s *RLNSuite) TestEpochConsistency() { // check edge cases var epoch uint64 = math.MaxUint64 diff --git a/rln/types.go b/rln/types.go index 1d20b22..02eed31 100644 --- a/rln/types.go +++ b/rln/types.go @@ -63,6 +63,11 @@ type RateLimitProof struct { RLNIdentifier RLNIdentifier `json:"rlnIdentifier"` } +type MerkleProof struct { + PathElements []MerkleNode `json:"pathElements"` + PathIndexes []uint8 `json:"pathIndexes"` +} + type TreeDepth int const (