From 4c93f5564f72a6b34bd3fab6b2984008f1ad0fb6 Mon Sep 17 00:00:00 2001 From: alrevuelta Date: Wed, 17 Jan 2024 13:29:26 +0100 Subject: [PATCH] Wrap proof with custom witness --- go.mod | 2 +- go.sum | 6 +++ rln/link/apple.go | 4 ++ rln/link/arm.go | 4 ++ rln/link/x86_64.go | 4 ++ rln/rln.go | 60 ++++++++++++++++++++++++++++++ rln/rln_test.go | 58 +++++++++++++++++++++++++++++ rln/serialize.go | 86 ++++++++++++++++++++++++++++++++++++++++++- rln/serialize_test.go | 56 ++++++++++++++++++++++++++++ rln/types.go | 8 ++++ rln/utils.go | 8 ++++ rln/utils_test.go | 2 + 12 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 rln/serialize_test.go diff --git a/go.mod b/go.mod index 5908417..f3414a0 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-20240116135015-f6f595c7b8ef + github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117094748-68b4162e8fd7 github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80 github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc ) diff --git a/go.sum b/go.sum index 65546fe..6652c4e 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,12 @@ github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442 h1:x github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116121347-ee5a1d931442/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef h1:MAkZryAeRhiH3TKHRK2h+WztZI1VqfQ/oeXMIxKZNy0= github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240116135015-f6f595c7b8ef/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117093624-7ba5a338d490 h1:1OivqdMCBCRIp2qzUggSpZPhcaRkcFl0U5UoPfGWB9g= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117093624-7ba5a338d490/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117093914-f79c87b06466 h1:Baxd/BPVGKgaVvLoI0//UtSVNovlY/IOROQpfni8pbE= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117093914-f79c87b06466/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117094748-68b4162e8fd7 h1:MOpAZITkW2EkI7aO1uUGWsGuUnQH3K/Mk7WuqLpFQGo= +github.com/waku-org/go-zerokit-rln-apple v0.0.0-20240117094748-68b4162e8fd7/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-arm v0.0.0-20240116134931-a8b8c6ab4b80 h1:3KObRaYJnTI41U0reNBk7DYr5PVCTq8T9gJLXGfPfaY= diff --git a/rln/link/apple.go b/rln/link/apple.go index 6f3d80e..c09ae08 100644 --- a/rln/link/apple.go +++ b/rln/link/apple.go @@ -91,6 +91,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } +func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { + return i.ffi.GenerateRLNProofWithWitness(input) +} + func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { return i.ffi.VerifyWithRoots(input, roots) } diff --git a/rln/link/arm.go b/rln/link/arm.go index b0313ef..f7db9a7 100644 --- a/rln/link/arm.go +++ b/rln/link/arm.go @@ -90,6 +90,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } +func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { + return i.ffi.GenerateRLNProofWithWitness(input) +} + func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { return i.ffi.VerifyWithRoots(input, roots) } diff --git a/rln/link/x86_64.go b/rln/link/x86_64.go index 320d482..d151518 100644 --- a/rln/link/x86_64.go +++ b/rln/link/x86_64.go @@ -91,6 +91,10 @@ func (i RLNWrapper) GenerateRLNProof(input []byte) ([]byte, error) { return i.ffi.GenerateRLNProof(input) } +func (i RLNWrapper) GenerateRLNProofWithWitness(input []byte) ([]byte, error) { + return i.ffi.GenerateRLNProofWithWitness(input) +} + func (i RLNWrapper) VerifyWithRoots(input []byte, roots []byte) (bool, error) { return i.ffi.VerifyWithRoots(input, roots) } diff --git a/rln/rln.go b/rln/rln.go index 282227a..e71596d 100644 --- a/rln/rln.go +++ b/rln/rln.go @@ -233,6 +233,64 @@ func (r *RLN) GenerateProof(data []byte, key IdentityCredential, index Membershi }, nil } +// input : +/* +identity_secret: Fr, +path_elements: Vec, +identity_path_index: Vec, +x: Fr, +epoch: Fr, +rln_identifier: Fr, +*/ +// todo: output same as other function. +// output [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ] +func (r *RLN) GenerateRLNProofWithWitness(witness RLNWitnessInput) (*RateLimitProof, error) { + + proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize()) + if err != nil { + return nil, err + } + + if len(proofBytes) != 320 { + return nil, errors.New("invalid proof generated") + } + + // TODO: maybe move this into a common function (used by the other generateRlnproof function) + // parse the proof as [ proof<128> | root<32> | epoch<32> | share_x<32> | share_y<32> | nullifier<32> | rln_identifier<32> ] + proofOffset := 128 + rootOffset := proofOffset + 32 + epochOffset := rootOffset + 32 + shareXOffset := epochOffset + 32 + shareYOffset := shareXOffset + 32 + nullifierOffset := shareYOffset + 32 + rlnIdentifierOffset := nullifierOffset + 32 + + var zkproof ZKSNARK + var proofRoot, shareX, shareY MerkleNode + var epochR Epoch + var nullifier Nullifier + var rlnIdentifier RLNIdentifier + + 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]) + copy(rlnIdentifier[:], proofBytes[nullifierOffset:rlnIdentifierOffset]) + + return &RateLimitProof{ + Proof: zkproof, + MerkleRoot: proofRoot, + Epoch: epochR, + ShareX: shareX, + ShareY: shareY, + Nullifier: nullifier, + RLNIdentifier: rlnIdentifier, + }, nil + +} + func serialize32(roots [][32]byte) []byte { var result []byte for _, r := range roots { @@ -406,6 +464,8 @@ func (r *RLN) GetMerkleProof(index MembershipIndex) (MerkleProof, error) { return MerkleProof{}, err } + // TODO: Take this from the function + // Check if we can read the first byte if len(proofBytes) < 8 { return MerkleProof{}, errors.New(fmt.Sprintf("wrong output size: %d", len(proofBytes))) diff --git a/rln/rln_test.go b/rln/rln_test.go index 3a3fc65..2d8c530 100644 --- a/rln/rln_test.go +++ b/rln/rln_test.go @@ -350,6 +350,64 @@ func (s *RLNSuite) TestGetMerkleProof() { } } +func (s *RLNSuite) TestGenerateRLNProofWithWitness() { + rln, err := NewRLN() + s.NoError(err) + + memKeys, err := rln.MembershipKeyGen() + s.NoError(err) + + //peer's index in the Merkle Tree + index := 5 + + // Create a Merkle tree with random members + for i := 0; i < 10; i++ { + if i == index { + // insert the current peer's pk + err := rln.InsertMember(memKeys.IDCommitment) + s.NoError(err) + } else { + // create a new key pair + memberKeys, err := rln.MembershipKeyGen() + s.NoError(err) + + err = rln.InsertMember(memberKeys.IDCommitment) + s.NoError(err) + } + } + + root, err := rln.GetMerkleRoot() + s.NoError(err) + + // prepare the message + msg := []byte("Hello") + + // prepare the epoch + var epoch Epoch + + badIndex := 4 + + merkleProof, err := rln.GetMerkleProof(uint(badIndex)) + s.NoError(err) + + rlnWitness := RLNWitnessInput{ + IdentityCredential: *memKeys, + MerkleProof: merkleProof, + Data: msg, + Epoch: epoch, + RlnIdentifier: [32]byte{0x00, 0x00, 0x00}, // TODO + } + + // generate proof + proofRes, err := rln.GenerateRLNProofWithWitness(rlnWitness) + s.NoError(err) + + // verify the proof (should not be verified) + verified, err := rln.Verify(msg, *proofRes, root) + s.NoError(err) + s.False(verified) +} + func (s *RLNSuite) TestEpochConsistency() { // check edge cases var epoch uint64 = math.MaxUint64 diff --git a/rln/serialize.go b/rln/serialize.go index 87e3093..dee40f0 100644 --- a/rln/serialize.go +++ b/rln/serialize.go @@ -1,6 +1,11 @@ package rln -import "encoding/binary" +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" +) // serialize converts a RateLimitProof and the data to a byte seq // this conversion is used in the proofGen function @@ -42,3 +47,82 @@ func (r RateLimitProof) serialize() []byte { proofBytes = append(proofBytes, r.RLNIdentifier[:]...) return proofBytes } + +func (r *RLNWitnessInput) serialize() []byte { + output := make([]byte, 0) + + output = append(output, r.IdentityCredential.IDSecretHash[:]...) + output = append(output, r.MerkleProof.serialize()...) + output = append(output, appendLength(r.Data)...) + output = append(output, r.Epoch[:]...) + output = append(output, r.RlnIdentifier[:]...) + + return output +} + +func (r *MerkleProof) serialize() []byte { + output := make([]byte, 0) + + output = append(output, appendLength(Flatten(r.PathElements))...) + output = append(output, appendLength(r.PathIndexes)...) + + return output +} + +func (r *MerkleProof) deserialize(b []byte) error { + + // Check if we can read the first byte + if len(b) < 8 { + return errors.New(fmt.Sprintf("wrong output size: %d", len(b))) + } + + var numElements big.Int + var numIndexes big.Int + + offset := 0 + + // Get amounf of elements in the proof + numElements.SetBytes(revert(b[offset : offset+8])) + offset += 8 + + // With numElements we can determine the expected length of the proof. + expectedLen := 8 + int(32*numElements.Uint64()) + 8 + int(numElements.Uint64()) + if len(b) != expectedLen { + return errors.New(fmt.Sprintf("wrong output size expected: %d, current: %d", + expectedLen, + len(b))) + } + + r.PathElements = make([]MerkleNode, numElements.Uint64()) + + for i := uint64(0); i < numElements.Uint64(); i++ { + copy(r.PathElements[i][:], b[offset:offset+32]) + offset += 32 + } + + // Get amount of indexes in the path + numIndexes.SetBytes(revert(b[offset : offset+8])) + offset += 8 + + // Both numElements and numIndexes shall be equal and match the tree depth. + if numIndexes.Uint64() != numElements.Uint64() { + return 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 + + r.PathIndexes = make([]uint8, numIndexes.Uint64()) + + for i := uint64(0); i < numIndexes.Uint64(); i++ { + r.PathIndexes[i] = b[offset] + offset += 1 + } + + if offset != len(b) { + return errors.New( + fmt.Sprintf("error parsing proof read: %d, length; %d", offset, len(b))) + } + + return nil +} diff --git a/rln/serialize_test.go b/rln/serialize_test.go new file mode 100644 index 0000000..3ebc930 --- /dev/null +++ b/rln/serialize_test.go @@ -0,0 +1,56 @@ +package rln + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func random32() [32]byte { + var randomBytes [32]byte + _, _ = rand.Read(randomBytes[:]) + return randomBytes +} + +func TestMerkleProofSerDe(t *testing.T) { + + mProof := MerkleProof{ + PathElements: []MerkleNode{}, + PathIndexes: []uint8{}, + } + + ser := mProof.serialize() + //require.Equal(t, []byte{0, 0, 0, 0}, ser, ) + require.Equal(t, 16, len(ser)) + + mProof = MerkleProof{ + PathElements: []MerkleNode{[32]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0}}, + PathIndexes: []uint8{0}, + } + + ser = mProof.serialize() + //require.Equal(t, []byte{0, 0, 0, 0}, ser, ) + require.Equal(t, 49, len(ser)) + + mProof = MerkleProof{} + + for i := 0; i < 16; i++ { + mProof.PathElements = append(mProof.PathElements, random32()) + mProof.PathIndexes = append(mProof.PathIndexes, uint8(i%2)) + } + + ser = mProof.serialize() + fmt.Println(ser) + + desProof := MerkleProof{} + err := desProof.deserialize(ser) + require.NoError(t, err) + + // TODO test for errors. eg different size. +} + +func TestRLNWitnessInputSerDe(t *testing.T) { + +} diff --git a/rln/types.go b/rln/types.go index 02eed31..ccffdf3 100644 --- a/rln/types.go +++ b/rln/types.go @@ -68,6 +68,14 @@ type MerkleProof struct { PathIndexes []uint8 `json:"pathIndexes"` } +type RLNWitnessInput struct { + IdentityCredential IdentityCredential `json:"identityCredential"` + MerkleProof MerkleProof `json:"merkleProof"` + Data []byte `json:"data"` + Epoch Epoch `json:"epoch"` + RlnIdentifier RLNIdentifier `json:"rlnIdentifier"` // what is this? TOOD: app specific. which one is ours? +} + type TreeDepth int const ( diff --git a/rln/utils.go b/rln/utils.go index 42e767d..f2c8c79 100644 --- a/rln/utils.go +++ b/rln/utils.go @@ -56,6 +56,14 @@ func Bytes128(b []byte) [128]byte { return result } +func Flatten(b [][32]byte) []byte { + var result []byte + for _, v := range b { + result = append(result, v[:]...) + } + return result +} + func ToBytes32LE(hexStr string) ([32]byte, error) { b, err := hex.DecodeString(hexStr) diff --git a/rln/utils_test.go b/rln/utils_test.go index f44440e..96c9b23 100644 --- a/rln/utils_test.go +++ b/rln/utils_test.go @@ -19,3 +19,5 @@ func TestBigInt(t *testing.T) { newValue := Bytes32ToBigInt(b32Value) require.True(t, bytes.Equal(newValue.Bytes(), value.Bytes())) } + +// TODO: Test Flatten