From 85a45dc73a0bd1062dc272b2e9d0bc2fe61146b5 Mon Sep 17 00:00:00 2001 From: alrevuelta Date: Fri, 19 Jan 2024 18:08:26 +0100 Subject: [PATCH] Proofs now verify ok --- go.mod | 9 ++++- go.sum | 42 +++++++++---------- rln/rln.go | 13 ++++++ rln/rln_test.go | 41 +++++++++++++++++-- rln/types.go | 9 +++-- rln/utils.go | 39 ++++++++++++++++++ rln/utils_test.go | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 221 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index f3414a0..abccbfc 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,21 @@ module github.com/waku-org/go-zerokit-rln go 1.19 require ( - github.com/stretchr/testify v1.7.2 + github.com/ethereum/go-ethereum v1.13.10 + github.com/stretchr/testify v1.8.4 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 + golang.org/x/crypto v0.18.0 ) require ( + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.16.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6652c4e..01357c5 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,36 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= +github.com/ethereum/go-ethereum v1.13.10/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= -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-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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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= github.com/waku-org/go-zerokit-rln-arm v0.0.0-20240116134931-a8b8c6ab4b80/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4= -github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 h1:4HSdWMFMufpRo3ECTX6BrvA+VzKhXZf7mS0rTa5cCWU= -github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc h1:YjIgIrqlNVC+hXNy2ykYy3JvBKima64J9jXbKM/ULpw= github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20240116135046-2875fec12afc/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/rln/rln.go b/rln/rln.go index 4a4d749..d3bd8bd 100644 --- a/rln/rln.go +++ b/rln/rln.go @@ -255,6 +255,19 @@ rln_identifier: Fr, // 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) { + // TODO: this shouldn go here but i think there is an issue in zerokit + fmt.Println("len data before ", len(witness.Data)) + // Remove its not a poseidon hash but kekkack + hashedData, err := r.Poseidon(witness.Data[:]) + if err != nil { + return nil, err + } + + //witness.Data = hashedData[:] + _ = hashedData + + fmt.Println("len data after ", len(witness.Data)) + proofBytes, err := r.w.GenerateRLNProofWithWitness(witness.serialize()) if err != nil { return nil, err diff --git a/rln/rln_test.go b/rln/rln_test.go index 554b77b..fb6e6db 100644 --- a/rln/rln_test.go +++ b/rln/rln_test.go @@ -380,26 +380,54 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness() { fmt.Println("root from zerokit: ", root) // Inputs for proof generation - msg := [32]byte{0x00, 0x00, 0x01} + msg := [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0} + //msg := [32]byte{69, 7, 140, 46, 26, 131, 147, 30, 161, 68, 2, 5, 234, 195, 227, 223, 119, 187, 116, 97, 153, 70, 71, 254, 60, 149, 54, 109, 77, 79, 105, 20} var epoch = Epoch([32]byte{0x00, 0x00, 0x00, 0x00, 0x01}) // We provide out custom witness merkleProof, err := rln.GetMerkleProof(memberIndex) s.NoError(err) + //hashMsg, err := rln.Poseidon(msg[:]) + //s.NoError(err) + hashMsg := [32]byte{69, 7, 140, 46, 26, 131, 147, 30, 161, 68, 2, 5, 234, 195, 227, 223, 119, 187, 116, 97, 153, 70, 71, 254, 60, 149, 54, 109, 77, 79, 105, 20} + rlnWitness := RLNWitnessInput{ // memberIndex key IdentityCredential: *memKeys, MerkleProof: merkleProof, - Data: msg[:], + Data: hashMsg[:], Epoch: epoch, RlnIdentifier: [32]byte{166, 140, 43, 8, 8, 22, 206, 113, 151, 128, 118, 40, 119, 197, 218, 174, 11, 117, 84, 228, 96, 211, 212, 140, 145, 104, 146, 99, 24, 192, 217, 4}, // TODO } + fmt.Println("Witness secrethash ", rlnWitness.IdentityCredential.IDSecretHash) + fmt.Println("Witness merkle path", rlnWitness.MerkleProof.PathElements) + fmt.Println("Witness merkle indexes", rlnWitness.MerkleProof.PathIndexes) + fmt.Println("Witness data", rlnWitness.Data) + fmt.Println("Witness epoch", rlnWitness.Epoch) + fmt.Println("Witness rln identifier", rlnWitness.RlnIdentifier) + // generate proof proofRes, err := rln.GenerateRLNProofWithWitness(rlnWitness) s.NoError(err) + //proofRes.ShareX = dataToReplace + + fmt.Println("Proof Epoch: ", proofRes) + + // TODO: for testing. proof without witness (are proofs deterministic? maybe not) + proofRes2, err := rln.GenerateProof(msg[:], *memKeys, MembershipIndex(memberIndex), epoch) + s.NoError(err) + + fmt.Println("Proof1 Epoch: ", proofRes2.Epoch) + fmt.Println("Proof1 Nullifier: ", proofRes2.Nullifier) + fmt.Println("Proof1 ShareX: ", proofRes2.ShareX) + fmt.Println("Proof1 ShareY: ", proofRes2.ShareY) + fmt.Println("Proof1 MerkleRoot: ", proofRes2.MerkleRoot) + fmt.Println("Proof1 RlnIdentifier: ", proofRes2.RLNIdentifier) + fmt.Println("Proof Epoch: ", proofRes.Epoch) fmt.Println("Proof Nullifier: ", proofRes.Nullifier) fmt.Println("Proof ShareX: ", proofRes.ShareX) @@ -407,7 +435,12 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness() { fmt.Println("Proof MerkleRoot: ", proofRes.MerkleRoot) fmt.Println("Proof RlnIdentifier: ", proofRes.RLNIdentifier) - // verify the proof + // Verifty old proofs + verified1, err := rln.Verify(msg[:], *proofRes2, root) + s.NoError(err) + s.True(verified1) + + // verify the proof with the witness //msg := [32]byte{0x00, 0x00, 0x01} //verified, err := rln.Verify([]byte{0x00, 0x00, 0x01}, *proofRes, root) verified, err := rln.Verify(msg[:], *proofRes, root) @@ -415,7 +448,7 @@ func (s *RLNSuite) TestGenerateRLNProofWithWitness() { _ = verified // TODO: Not working - //s.True(verified) + s.True(verified) // TODO: test a proof that shall not be verified } diff --git a/rln/types.go b/rln/types.go index ccffdf3..d329621 100644 --- a/rln/types.go +++ b/rln/types.go @@ -69,11 +69,14 @@ type MerkleProof struct { } type RLNWitnessInput struct { + // TODO: Maybe dont store the whole IdentityCredential and just the secret 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? + + // This is not the data but the hashed version of it "x"..TODO rename and reconsider + 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 diff --git a/rln/utils.go b/rln/utils.go index f2c8c79..14fe231 100644 --- a/rln/utils.go +++ b/rln/utils.go @@ -2,7 +2,10 @@ package rln import ( "encoding/hex" + "fmt" "math/big" + + "github.com/ethereum/go-ethereum/crypto" ) func ToIdentityCredentials(groupKeys [][]string) ([]IdentityCredential, error) { @@ -106,3 +109,39 @@ func Bytes32ToBigInt(value [32]byte) *big.Int { result.SetBytes(b) return result } + +func EndianConvertTODO(data []byte) [32]byte { + + hashGoEth := crypto.Keccak256(data[:]) + _ = hashGoEth + //if len(hashGoEth) != 32 { + // fmt.Println("errorrrrrr") + // } + myHash32 := [32]byte{} + // copy(myHash32[:], hashGoEth) + + // el hash esta controlado por ahora + copy(myHash32[:], data) + + fmt.Println("inpit is: ", data) + fmt.Println("hash is: ", myHash32) + + var uintVals [4]uint64 + + for i := 0; i < 4; i++ { + chunk := make([]byte, 8) + copy(chunk, myHash32[i*8:(i+1)*8]) + fmt.Println("chunk is: ", chunk) + + myBig := new(big.Int) + myBig.SetBytes(revert(chunk)) + fmt.Println("big is: ", myBig) + uintVals[i] = myBig.Uint64() + } + + fmt.Println("uintVals is: ", uintVals) + + returnthis := [32]byte{} + + return returnthis +} diff --git a/rln/utils_test.go b/rln/utils_test.go index 3c69506..1f924fa 100644 --- a/rln/utils_test.go +++ b/rln/utils_test.go @@ -2,7 +2,10 @@ package rln import ( "bytes" + "fmt" "math/big" + "strconv" + "strings" "testing" "github.com/stretchr/testify/require" @@ -42,3 +45,100 @@ func TestFlatten(t *testing.T) { out3 := Flatten(in3) require.Equal(t, expected3, out3) } + +func TestTODO(t *testing.T) { + // Inputs for proof generation + msg := []byte{72, 7, 140, 254, 213, 99, 57, 234, 84, 150, 46, 114, 195, 124, 127, 88, 143, 196, 248, 229, 188, 23, 56, 39, 186, 117, 203, 16, 166, 58, 150, 165} + + conv := EndianConvertTODO(msg) + + fmt.Println(conv) + + ints := []uint64{16877630849297418056, 6376952776256034388, 2826034866254562447, 11931788747685459386} + + fmt.Println(ints[0]) + + str := "" + for i, _ := range ints { + str = str + padBinaryString(uint64ToBinaryString(ints[4-i-1]), 64) + } + + fmt.Println("-- ", str) + + byteArray, err := binaryStringToBytes(str) + if err != nil { + fmt.Println("Error:", err) + return + } + + myBigInt := new(big.Int) + myBigInt.SetBytes(byteArray[:]) + + fmt.Println("mybigint", myBigInt.String()) + + fmt.Println(byteArray) + + // Expected + // in := [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0} + // hashed: []byte{72, 7, 140, 254, 213, 99, 57, 234, 84, 150, 46, 114, 195, 124, 127, 88, 143, 196, 248, 229, 188, 23, 56, 39, 186, 117, 203, 16, 166, 58, 150, 165} + // expected := [32]byte{69, 7, 140, 46, 26, 131, 147, 30, 161, 68, 2, 5, 234, 195, 227, 223, 119, 187, 116, 97, 153, 70, 71, 254, 60, 149, 54, 109, 77, 79, 105, 20} +} + +func reverseString(input string) string { + // Convert string to a slice of runes + runes := []rune(input) + + // Reverse the order of runes + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + + // Convert the slice of runes back to a string + reversedString := string(runes) + + return reversedString +} + +func binaryStringToBytes(binaryString string) ([32]byte, error) { + var byteArray [32]byte + + // Ensure the binary string has 256 bits (32 bytes) + if len(binaryString) != 256 { + return byteArray, fmt.Errorf("binary string must have exactly 256 bits") + } + + // Iterate over 32 chunks of 8 bits each and parse them to bytes + for i := 0; i < 32; i++ { + startIndex := i * 8 + endIndex := startIndex + 8 + bits := binaryString[startIndex:endIndex] + + // Parse the 8-bit chunk to a byte + byteValue, err := strconv.ParseUint(bits, 2, 8) + if err != nil { + return byteArray, err + } + + byteArray[i] = byte(byteValue) + } + + return byteArray, nil +} + +func padBinaryString(binaryString string, length int) string { + // Calculate padding length + paddingLength := length - len(binaryString) + + // Pad with zeros + paddedBinaryString := strings.Repeat("0", paddingLength) + binaryString + + return paddedBinaryString +} + +func bigIntToBinaryString(num *big.Int) string { + return fmt.Sprintf("%b", num) +} + +func uint64ToBinaryString(num uint64) string { + return strconv.FormatUint(num, 2) +}