diff --git a/.gitignore b/.gitignore index fbe7f4d62..65c4ae4f1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,13 @@ keystore **/*.bin **/target **/build +**/data **/__pycache__ **/Verifier.sol **/FunctionVerifier.sol -.idea/ \ No newline at end of file +.idea/ +proof.json +output.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b807e0a55..a13a82f82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,17 @@ dependencies = [ "regex", ] +[[package]] +name = "addchain" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2e69442aa5628ea6951fa33e24efe8313f4321a91bd729fc2f75bdfc858570" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -1328,10 +1339,29 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", + "byteorder", + "ff_derive", "rand_core", "subtle", ] +[[package]] +name = "ff_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f54704be45ed286151c5e11531316eaef5b8f5af7d597b806fdb8af108d84a" +dependencies = [ + "addchain", + "cfg-if", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fiat-crypto" version = "0.1.20" @@ -2153,7 +2183,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ - "num-bigint", + "num-bigint 0.4.4", "num-complex", "num-integer", "num-iter", @@ -2161,6 +2191,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -2211,7 +2252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.4.4", "num-integer", "num-traits", ] @@ -2649,8 +2690,10 @@ dependencies = [ "dotenv", "env_logger", "ethers", + "ff", "hex", "itertools 0.10.5", + "lazy_static", "log", "num", "plonky2", @@ -3354,7 +3397,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ - "num-bigint", + "num-bigint 0.4.4", "num-traits", "thiserror", "time", diff --git a/README.md b/README.md index 7746fb179..590d713bc 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ succinct prove To create a new SDK release: ```sh -./build/release.sh +./scripts/release.sh ``` ## Building ABIs and Bindings @@ -49,13 +49,13 @@ To create a new SDK release: To build the ABIs: ```sh -./build/abi.sh +./scripts/abi.sh ``` Then to build the bindings: ```sh -./build/binding.sh +./scripts/binding.sh ``` -If you need to add a binding for a different contract, edit `build/binding.sh` and modify the `CONTRACTS` array. \ No newline at end of file +If you need to add a binding for a different contract, edit `scripts/binding.sh` and modify the `CONTRACTS` array. \ No newline at end of file diff --git a/gnarkx/types/proof.go b/gnarkx/types/proof.go index 27351af3c..0fa1abd1a 100644 --- a/gnarkx/types/proof.go +++ b/gnarkx/types/proof.go @@ -13,8 +13,8 @@ type Groth16Proof struct { A [2]*big.Int `json:"a"` B [2][2]*big.Int `json:"b"` C [2]*big.Int `json:"c"` - Input hexutil.Bytes `json:"input"` - Output hexutil.Bytes `json:"output"` + Input hexutil.Bytes `json:"input,omitempty"` + Output hexutil.Bytes `json:"output,omitempty"` } // Export saves the proof to a file. @@ -42,3 +42,8 @@ func (g *Groth16Proof) Export(file string) error { return nil } + +type FunctionResult struct { + Proof hexutil.Bytes `json:"proof"` + Output hexutil.Bytes `json:"output"` +} diff --git a/go.mod b/go.mod index 4c8ad4967..4b475fa73 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/consensys/gnark v0.8.1-0.20230803132917-bd4a39719a96 - github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b + github.com/consensys/gnark-crypto v0.11.2 github.com/ethereum/go-ethereum v1.12.0 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 @@ -20,10 +20,10 @@ require ( github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect + github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect @@ -35,11 +35,13 @@ require ( github.com/rs/zerolog v1.30.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/succinctlabs/gnark-plonky2-verifier v0.0.0-20230724231837-7bd0035e65e3 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/sys v0.11.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/go.sum b/go.sum index dac1ee031..3a216bc5e 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/consensys/gnark v0.8.1-0.20230803132917-bd4a39719a96 h1:BtDmnURu+BjTQ github.com/consensys/gnark v0.8.1-0.20230803132917-bd4a39719a96/go.mod h1:Y70OuyMjYNvmTUL/sNKhehLOgByRYSOR8X6WMo7GmGQ= github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b h1:tlxRLAcCOcdC3TcP5uqrKaK+OF7eBy1YB5AYUUhOayM= github.com/consensys/gnark-crypto v0.11.1-0.20230724160225-800ddb59f51b/go.mod h1:6C2ytC8zmP8uH2GKVfPOjf0Vw3KwMAaUxlCPK5WQqmw= +github.com/consensys/gnark-crypto v0.11.2 h1:GJjjtWJ+db1xGao7vTsOgAOGgjfPe7eRGPL+xxMX0qE= +github.com/consensys/gnark-crypto v0.11.2/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -38,6 +40,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= @@ -52,6 +56,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXi github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 h1:ZgoomqkdjGbQ3+qQXCkvYMCDvGDNg2k5JJDjjdTB6jY= github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= +github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -104,6 +110,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= 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/succinctlabs/gnark-plonky2-verifier v0.0.0-20230724231837-7bd0035e65e3 h1:2UL4wMVXtuGnE7f+Bt7gTtMg3bU8Bh3xjNMiR7wzrT0= +github.com/succinctlabs/gnark-plonky2-verifier v0.0.0-20230724231837-7bd0035e65e3/go.mod h1:5jUXJKCi/CwL0U2YWvwKR39mvLH5yV9f7ql+skd0eC8= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= @@ -114,7 +122,11 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -124,7 +136,10 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 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= diff --git a/plonky2x-verifier/circuit.go b/plonky2x-verifier/circuit.go new file mode 100644 index 000000000..700e16dab --- /dev/null +++ b/plonky2x-verifier/circuit.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/consensys/gnark/logger" + "github.com/consensys/gnark/test" + "github.com/succinctlabs/gnark-plonky2-verifier/types" + "github.com/succinctlabs/gnark-plonky2-verifier/verifier" +) + +type Plonky2xVerifierCircuit struct { + ProofWithPis types.ProofWithPublicInputs + VerifierData types.VerifierOnlyCircuitData + + // A digest of the verifier information of the plonky2x circuit. + VerifierDigest frontend.Variable `gnark:"verifierDigest,public"` + + // The input hash is the hash of all onchain inputs into the function. + InputHash frontend.Variable `gnark:"inputHash,public"` + + // The output hash is the hash of all outputs from the function. + OutputHash frontend.Variable `gnark:"outputHash,public"` + + verifierChip *verifier.VerifierChip `gnark:"-"` + CircuitPath string `gnark:"-"` +} + +func (c *Plonky2xVerifierCircuit) Define(api frontend.API) error { + // load the common circuit data + commonCircuitData := verifier.DeserializeCommonCircuitData(c.CircuitPath + "/common_circuit_data.json") + // initialize the verifier chip + c.verifierChip = verifier.NewVerifierChip(api, commonCircuitData) + // verify the plonky2 proof + c.verifierChip.Verify(c.ProofWithPis.Proof, c.ProofWithPis.PublicInputs, c.VerifierData, commonCircuitData) + + publicInputs := c.ProofWithPis.PublicInputs + + verifierDigestBytes := make([]frontend.Variable, 32) + for i := range verifierDigestBytes { + pubByte := publicInputs[i].Limb + verifierDigestBytes[i] = pubByte + } + verifierDigest := frontend.Variable(0) + for i := range verifierDigestBytes { + verifierDigest = api.Add(verifierDigest, api.Mul(verifierDigestBytes[i], frontend.Variable(1<<(8*i)))) + } + c.VerifierDigest = verifierDigest + + inputDigestBytes := make([]frontend.Variable, 32) + for i := range inputDigestBytes { + pubByte := publicInputs[i+32].Limb + inputDigestBytes[i] = pubByte + } + inputDigest := frontend.Variable(0) + for i := range inputDigestBytes { + inputDigest = api.Add(inputDigest, api.Mul(inputDigestBytes[i], frontend.Variable(1<<(8*i)))) + } + c.InputHash = inputDigest + + outputDigestBytes := make([]frontend.Variable, 32) + for i := range outputDigestBytes { + pubByte := publicInputs[i+64].Limb + outputDigestBytes[i] = pubByte + } + outputDigest := frontend.Variable(0) + for i := range outputDigestBytes { + outputDigest = api.Add(outputDigest, api.Mul(outputDigestBytes[i], frontend.Variable(1<<(8*i)))) + } + c.OutputHash = outputDigest + + return nil +} + +func VerifierCircuitTest(circuitPath string, dummyCircuitPath string) error { + verifierOnlyCircuitData := verifier.DeserializeVerifierOnlyCircuitData(dummyCircuitPath + "/verifier_only_circuit_data.json") + proofWithPis := verifier.DeserializeProofWithPublicInputs(dummyCircuitPath + "/proof_with_public_inputs.json") + circuit := Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: new(frontend.Variable), + InputHash: new(frontend.Variable), + OutputHash: new(frontend.Variable), + CircuitPath: dummyCircuitPath, + } + + verifierOnlyCircuitData = verifier.DeserializeVerifierOnlyCircuitData(circuitPath + "/verifier_only_circuit_data.json") + proofWithPis = verifier.DeserializeProofWithPublicInputs(circuitPath + "/proof_with_public_inputs.json") + witness := Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: new(frontend.Variable), + InputHash: new(frontend.Variable), + OutputHash: new(frontend.Variable), + CircuitPath: dummyCircuitPath, + } + return test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) +} + +func CompileVerifierCircuit(dummyCircuitPath string) (constraint.ConstraintSystem, groth16.ProvingKey, groth16.VerifyingKey, error) { + log := logger.Logger() + verifierOnlyCircuitData := verifier.DeserializeVerifierOnlyCircuitData(dummyCircuitPath + "/verifier_only_circuit_data.json") + proofWithPis := verifier.DeserializeProofWithPublicInputs(dummyCircuitPath + "/proof_with_public_inputs.json") + circuit := Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: new(frontend.Variable), + InputHash: new(frontend.Variable), + OutputHash: new(frontend.Variable), + CircuitPath: dummyCircuitPath, + } + r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to compile circuit: %w", err) + } + + log.Info().Msg("Running circuit setup") + start := time.Now() + pk, vk, err := groth16.Setup(r1cs) + if err != nil { + return nil, nil, nil, err + } + elapsed := time.Since(start) + log.Info().Msg("Successfully ran circuit setup, time: " + elapsed.String()) + + return r1cs, pk, vk, nil +} + +func SaveVerifierCircuit(path string, r1cs constraint.ConstraintSystem, pk groth16.ProvingKey, vk groth16.VerifyingKey) error { + log := logger.Logger() + os.MkdirAll(path, 0755) + log.Info().Msg("Saving circuit constraints to " + path + "/r1cs.bin") + r1csFile, err := os.Create(path + "/r1cs.bin") + if err != nil { + return fmt.Errorf("failed to create r1cs file: %w", err) + } + start := time.Now() + r1cs.WriteTo(r1csFile) + r1csFile.Close() + elapsed := time.Since(start) + log.Debug().Msg("Successfully saved circuit constraints, time: " + elapsed.String()) + + log.Info().Msg("Saving proving key to " + path + "/pk.bin") + pkFile, err := os.Create(path + "/pk.bin") + if err != nil { + return fmt.Errorf("failed to create pk file: %w", err) + } + start = time.Now() + pk.WriteRawTo(pkFile) + pkFile.Close() + elapsed = time.Since(start) + log.Debug().Msg("Successfully saved proving key, time: " + elapsed.String()) + + log.Info().Msg("Saving verifying key to " + path + "/vk.bin") + vkFile, err := os.Create(path + "/vk.bin") + if err != nil { + return fmt.Errorf("failed to create vk file: %w", err) + } + start = time.Now() + vk.WriteRawTo(vkFile) + vkFile.Close() + elapsed = time.Since(start) + log.Info().Msg("Successfully saved verifying key, time: " + elapsed.String()) + + return nil +} diff --git a/plonky2x-verifier/cli.go b/plonky2x-verifier/cli.go new file mode 100644 index 000000000..e5cdb8abf --- /dev/null +++ b/plonky2x-verifier/cli.go @@ -0,0 +1,127 @@ +package main + +import ( + _ "embed" + "flag" + "fmt" + "os" + "time" + + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/logger" +) + +func main() { + circuitPath := flag.String("circuit", "", "circuit data directory") + dataPath := flag.String("data", "", "data directory") + proofFlag := flag.Bool("prove", false, "create a proof") + verifyFlag := flag.Bool("verify", false, "verify a proof") + testFlag := flag.Bool("test", false, "test the circuit") + compileFlag := flag.Bool("compile", false, "Compile and save the universal verifier circuit") + contractFlag := flag.Bool("contract", true, "Generate solidity contract") + flag.Parse() + + log := logger.Logger() + + if *circuitPath == "" { + log.Error().Msg("please specify a path to circuit dir (containing verifier_only_circuit_data and proof_with_public_inputs)") + os.Exit(1) + } + + if *dataPath == "" { + log.Error().Msg("please specify a path to data dir (where the compiled gnark circuit data will be)") + os.Exit(1) + } + + log.Debug().Msg("Circuit path: " + *circuitPath) + log.Debug().Msg("Data path: " + *dataPath) + + if *testFlag { + log.Debug().Msg("testing circuit") + start := time.Now() + err := VerifierCircuitTest(*circuitPath, "./data/dummy") + if err != nil { + fmt.Println("verifier test failed:", err) + os.Exit(1) + } + elasped := time.Since(start) + log.Debug().Msg("verifier test succeeded, time: " + elasped.String()) + } + + if *compileFlag { + log.Info().Msg("compiling verifier circuit") + r1cs, pk, vk, err := CompileVerifierCircuit("./data/dummy") + if err != nil { + log.Error().Msg("failed to compile verifier circuit:" + err.Error()) + os.Exit(1) + } + err = SaveVerifierCircuit(*dataPath, r1cs, pk, vk) + if err != nil { + log.Error().Msg("failed to save verifier circuit:" + err.Error()) + os.Exit(1) + } + + if *contractFlag { + log.Info().Msg("generating solidity contract") + err := ExportIFunctionVerifierSolidity(*dataPath, vk) + if err != nil { + log.Error().Msg("failed to generate solidity contract:" + err.Error()) + os.Exit(1) + } + } + } + + if *proofFlag { + log.Info().Msg("loading the groth16 proving key and circuit data") + r1cs, pk, err := LoadProverData(*dataPath) + if err != nil { + log.Err(err).Msg("failed to load the verifier circuit") + os.Exit(1) + } + log.Info().Msg("creating the groth16 verifier proof") + proof, publicWitness, err := Prove(*circuitPath, r1cs, pk) + if err != nil { + log.Err(err).Msg("failed to create the proof") + os.Exit(1) + } + + log.Info().Msg("loading the proof, verifying key and verifying proof") + vk, err := LoadVerifierKey(*dataPath) + if err != nil { + log.Err(err).Msg("failed to load the verifier key") + os.Exit(1) + } + err = groth16.Verify(proof, vk, publicWitness) + if err != nil { + log.Err(err).Msg("failed to verify proof") + os.Exit(1) + } + log.Info().Msg("Successfully verified proof") + } + + if *verifyFlag { + log.Info().Msg("loading the proof, verifying key and public inputs") + vk, err := LoadVerifierKey(*dataPath) + if err != nil { + log.Err(err).Msg("failed to load the verifier key") + os.Exit(1) + } + publicWitness, err := LoadPublicWitness(*circuitPath) + if err != nil { + log.Err(err).Msg("failed to load the public witness") + os.Exit(1) + } + + proof, err := LoadProof() + if err != nil { + log.Err(err).Msg("failed to load the proof") + os.Exit(1) + } + err = groth16.Verify(proof, vk, publicWitness) + if err != nil { + log.Err(err).Msg("failed to verify proof") + os.Exit(1) + } + log.Info().Msg("Successfully verified proof") + } +} diff --git a/plonky2x-verifier/prover.go b/plonky2x-verifier/prover.go new file mode 100644 index 000000000..d8d173a05 --- /dev/null +++ b/plonky2x-verifier/prover.go @@ -0,0 +1,163 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/logger" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/succinctlabs/gnark-plonky2-verifier/verifier" + "github.com/succinctlabs/sdk/gnarkx/types" +) + +func LoadProverData(path string) (constraint.ConstraintSystem, groth16.ProvingKey, error) { + log := logger.Logger() + r1csFile, err := os.Open(path + "/r1cs.bin") + if err != nil { + return nil, nil, fmt.Errorf("failed to open r1cs file: %w", err) + } + r1cs := groth16.NewCS(ecc.BN254) + start := time.Now() + r1csReader := bufio.NewReader(r1csFile) + _, err = r1cs.ReadFrom(r1csReader) + if err != nil { + return nil, nil, fmt.Errorf("failed to read r1cs file: %w", err) + } + r1csFile.Close() + elapsed := time.Since(start) + log.Debug().Msg("Successfully loaded constraint system, time: " + elapsed.String()) + + pkFile, err := os.Open(path + "/pk.bin") + if err != nil { + return nil, nil, fmt.Errorf("failed to open pk file: %w", err) + } + pk := groth16.NewProvingKey(ecc.BN254) + start = time.Now() + pkReader := bufio.NewReader(pkFile) + _, err = pk.ReadFrom(pkReader) + if err != nil { + return nil, nil, fmt.Errorf("failed to read pk file: %w", err) + } + pkFile.Close() + elapsed = time.Since(start) + log.Debug().Msg("Successfully loaded proving key, time: " + elapsed.String()) + + return r1cs, pk, nil +} + +func Prove(circuitPath string, r1cs constraint.ConstraintSystem, pk groth16.ProvingKey) (groth16.Proof, witness.Witness, error) { + log := logger.Logger() + + verifierOnlyCircuitData := verifier.DeserializeVerifierOnlyCircuitData(circuitPath + "/verifier_only_circuit_data.json") + proofWithPis := verifier.DeserializeProofWithPublicInputs(circuitPath + "/proof_with_public_inputs.json") + + // Circuit assignment + assignment := &Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: frontend.Variable(0), + InputHash: frontend.Variable(0), + OutputHash: frontend.Variable(0), + CircuitPath: circuitPath, + } + + log.Debug().Msg("Generating witness") + start := time.Now() + witness, err := frontend.NewWitness(assignment, ecc.BN254.ScalarField()) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate witness: %w", err) + } + elapsed := time.Since(start) + log.Debug().Msg("Successfully generated witness, time: " + elapsed.String()) + + log.Debug().Msg("Creating proof") + start = time.Now() + proof, err := groth16.Prove(r1cs, pk, witness) + if err != nil { + return nil, nil, fmt.Errorf("failed to create proof: %w", err) + } + elapsed = time.Since(start) + log.Info().Msg("Successfully created proof, time: " + elapsed.String()) + + const fpSize = 4 * 8 + var buf bytes.Buffer + proof.WriteRawTo(&buf) + proofBytes := buf.Bytes() + output := &types.Groth16Proof{} + output.A[0] = new(big.Int).SetBytes(proofBytes[fpSize*0 : fpSize*1]) + output.A[1] = new(big.Int).SetBytes(proofBytes[fpSize*1 : fpSize*2]) + output.B[0][0] = new(big.Int).SetBytes(proofBytes[fpSize*2 : fpSize*3]) + output.B[0][1] = new(big.Int).SetBytes(proofBytes[fpSize*3 : fpSize*4]) + output.B[1][0] = new(big.Int).SetBytes(proofBytes[fpSize*4 : fpSize*5]) + output.B[1][1] = new(big.Int).SetBytes(proofBytes[fpSize*5 : fpSize*6]) + output.C[0] = new(big.Int).SetBytes(proofBytes[fpSize*6 : fpSize*7]) + output.C[1] = new(big.Int).SetBytes(proofBytes[fpSize*7 : fpSize*8]) + + // abi.encode(proof.A, proof.B, proof.C) + uint256Array, err := abi.NewType("uint256[2]", "", nil) + if err != nil { + log.Fatal().AnErr("Failed to create uint256[2] type", err) + } + uint256ArrayArray, err := abi.NewType("uint256[2][2]", "", nil) + if err != nil { + log.Fatal().AnErr("Failed to create uint256[2][2] type", err) + } + args := abi.Arguments{ + {Type: uint256Array}, + {Type: uint256ArrayArray}, + {Type: uint256Array}, + } + encodedProofBytes, err := args.Pack(output.A, output.B, output.C) + if err != nil { + log.Fatal().AnErr("Failed to encode proof", err) + } + + log.Info().Msg("Saving proof to proof.json") + jsonProof, err := json.Marshal(types.FunctionResult{ + // Output will be filled in by plonky2x CLI + Output: []byte{}, + Proof: encodedProofBytes, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal proof: %w", err) + } + proofFile, err := os.Create("proof.json") + if err != nil { + return nil, nil, fmt.Errorf("failed to create proof file: %w", err) + } + _, err = proofFile.Write(jsonProof) + if err != nil { + return nil, nil, fmt.Errorf("failed to write proof file: %w", err) + } + proofFile.Close() + log.Info().Msg("Successfully saved proof") + + publicWitness, err := witness.Public() + if err != nil { + return nil, nil, fmt.Errorf("failed to get public witness: %w", err) + } + + log.Info().Msg("Saving public witness to public_witness.bin") + witnessFile, err := os.Create("public_witness.bin") + if err != nil { + return nil, nil, fmt.Errorf("failed to create public witness file: %w", err) + } + _, err = publicWitness.WriteTo(witnessFile) + if err != nil { + return nil, nil, fmt.Errorf("failed to write public witness file: %w", err) + } + witnessFile.Close() + log.Info().Msg("Successfully saved public witness") + + return proof, publicWitness, nil +} diff --git a/plonky2x-verifier/veifier_test.go b/plonky2x-verifier/veifier_test.go new file mode 100644 index 000000000..19f119332 --- /dev/null +++ b/plonky2x-verifier/veifier_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/test" + "github.com/succinctlabs/gnark-plonky2-verifier/verifier" +) + +func TestPlonky2xVerifierCircuit(t *testing.T) { + assert := test.NewAssert(t) + + testCase := func() error { + dummyCircuitPath := "./data/dummy" + circuitPath := "./data/test_circuit" + + verifierOnlyCircuitData := verifier.DeserializeVerifierOnlyCircuitData(dummyCircuitPath + "/verifier_only_circuit_data.json") + proofWithPis := verifier.DeserializeProofWithPublicInputs(dummyCircuitPath + "/proof_with_public_inputs.json") + circuit := Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: new(frontend.Variable), + InputHash: new(frontend.Variable), + OutputHash: new(frontend.Variable), + CircuitPath: dummyCircuitPath, + } + + verifierOnlyCircuitData = verifier.DeserializeVerifierOnlyCircuitData(circuitPath + "/verifier_only_circuit_data.json") + proofWithPis = verifier.DeserializeProofWithPublicInputs(circuitPath + "/proof_with_public_inputs.json") + witness := Plonky2xVerifierCircuit{ + ProofWithPis: proofWithPis, + VerifierData: verifierOnlyCircuitData, + VerifierDigest: new(frontend.Variable), + InputHash: new(frontend.Variable), + OutputHash: new(frontend.Variable), + CircuitPath: dummyCircuitPath, + } + return test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField()) + } + + assert.NoError(testCase()) +} diff --git a/plonky2x-verifier/verifier.go b/plonky2x-verifier/verifier.go new file mode 100644 index 000000000..f9f3ee5ba --- /dev/null +++ b/plonky2x-verifier/verifier.go @@ -0,0 +1,134 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/logger" +) + +func LoadVerifierKey(path string) (groth16.VerifyingKey, error) { + log := logger.Logger() + vkFile, err := os.Open(path + "/vk.bin") + if err != nil { + return nil, fmt.Errorf("failed to open vk file: %w", err) + } + vk := groth16.NewVerifyingKey(ecc.BN254) + start := time.Now() + _, err = vk.ReadFrom(vkFile) + if err != nil { + return nil, fmt.Errorf("failed to read vk file: %w", err) + } + vkFile.Close() + elapsed := time.Since(start) + log.Debug().Msg("Successfully loaded verifying key, time: " + elapsed.String()) + + return vk, nil +} + +func LoadPublicWitness(circuitPath string) (witness.Witness, error) { + log := logger.Logger() + witnessFile, err := os.Open(circuitPath + "/public_witness.bin") + if err != nil { + return nil, fmt.Errorf("failed to open public witness file: %w", err) + } + publicWitness, err := witness.New(ecc.BN254.ScalarField()) + if err != nil { + return nil, fmt.Errorf("failed to create public witness: %w", err) + } + publicWitness.ReadFrom(witnessFile) + witnessFile.Close() + log.Debug().Msg("Successfully loaded public witness") + + return publicWitness, nil +} + +func LoadProof() (groth16.Proof, error) { + log := logger.Logger() + proofFile, err := os.Open("/proof.json") + if err != nil { + return nil, fmt.Errorf("failed to open proof file: %w", err) + } + proof := groth16.NewProof(ecc.BN254) + jsonProof, err := io.ReadAll(proofFile) + if err != nil { + return nil, fmt.Errorf("failed to read proof file: %w", err) + } + err = json.Unmarshal(jsonProof, proof) + if err != nil { + return nil, fmt.Errorf("failed to read proof file: %w", err) + } + proofFile.Close() + log.Debug().Msg("Successfully loaded proof") + + return proof, nil +} + +func ExportIFunctionVerifierSolidity(path string, vk groth16.VerifyingKey) error { + log := logger.Logger() + // Create a new buffer and export the VerifyingKey into it as a Solidity contract and + // convert the buffer content to a string for further manipulation. + buf := new(bytes.Buffer) + err := vk.ExportSolidity(buf) + if err != nil { + log.Err(err).Msg("failed to export verifying key to solidity") + return err + } + content := buf.String() + + contractFile, err := os.Create(path + "/FunctionVerifier.sol") + if err != nil { + return err + } + w := bufio.NewWriter(contractFile) + + // Custom replacements to make compatible with IFunctionVerifier. + content = strings.ReplaceAll(content, "uint256[2] calldata input", "uint256[2] memory input") + content = strings.ReplaceAll(content, "pragma solidity ^0.8.0;", "pragma solidity ^0.8.16;") + // write the new content to the writer + _, err = w.Write([]byte(content)) + if err != nil { + return err + } + + // Generate the IFunctionVerifier interface and FunctionVerifier contract. + solidityIFunctionVerifier := ` +interface IFunctionVerifier { + function verify(bytes32 _circuitDigest, bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool); + + function verificationKeyHash() external pure returns (bytes32); +} + +contract FunctionVerifier is IFunctionVerifier, Verifier { + function verify(bytes32 _circuitDigest, bytes32 _inputHash, bytes32 _outputHash, bytes memory _proof) external view returns (bool) { + (uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) = + abi.decode(_proof, (uint256[2], uint256[2][2], uint256[2])); + + uint256[3] memory input = [uint256(_circuitDigest), uint256(_inputHash), uint256(_outputHash)]; + input[0] = input[0] & ((1 << 253) - 1); + input[1] = input[1] & ((1 << 253) - 1); + input[2] = input[2] & ((1 << 253) - 1); + + return verifyProof(a, b, c, input); + } + + function verificationKeyHash() external pure returns (bytes32) { + return keccak256(abi.encode(verifyingKey())); + } +} +` + // write the IFunctionVerifier and FunctionVerifier to the writer + + _, err = w.Write([]byte(solidityIFunctionVerifier)) + contractFile.Close() + return err +} diff --git a/plonky2x/Cargo.toml b/plonky2x/Cargo.toml index 23628db51..2b3e31a2d 100644 --- a/plonky2x/Cargo.toml +++ b/plonky2x/Cargo.toml @@ -19,6 +19,7 @@ curta = { git = "https://github.com/succinctlabs/curta.git", branch = "john/nigh num = { version = "0.4", default-features = false } sha2 = "0.10.7" curve25519-dalek = { git = "https://github.com/succinctlabs/curve25519-dalek.git", branch = "feature/edwards-point-getters" } +ff = {package="ff" , version="0.13", features = ["derive"]} ethers = { version = "2.0"} @@ -34,6 +35,7 @@ reqwest = { version = "0.11.4", features = ["json"] } array-macro = "2.1.5" env_logger = "0.10.0" clap = { version = "4.4.0", features = ["derive"] } +lazy_static = "1.4.0" dotenv = "0.15.0" serde_with = "3.3.0" bincode = "1.3.3" diff --git a/plonky2x/examples/circuit_function_evm_input.json b/plonky2x/examples/circuit_function_evm_input.json index 6294e9ec8..fbbbb7d6e 100644 --- a/plonky2x/examples/circuit_function_evm_input.json +++ b/plonky2x/examples/circuit_function_evm_input.json @@ -1,3 +1,7 @@ { - "bytes": "0107" + "type": "req_bytes", + "releaseId": "", + "data": { + "input": "0x0107" + } } \ No newline at end of file diff --git a/plonky2x/output.json b/plonky2x/output.json deleted file mode 100644 index f66b90dd8..000000000 --- a/plonky2x/output.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "bytes": null, - "elements": [ - 25 - ], - "proof": "fba7e0553cb319de23f6109cc14418d99ad101ec37b6de911c2f494d47fcb68e3d60d28d2423c9166ba16d3c81bee8438aff8beccf73322d091ba47ccb47e9477433d400e03ca62b30df91593afcb4e71f012661f1583f01ff7e1330cab6458e3f8fa6a49aab02c6e506dd6355642b8adc76137944c0f178c789cd51c842f856fbe285830a14828c83a928dbd2eb0d8a5c8b2575e76e51553a26a0641299a84fb9b58ec188c7464697c60e4960582cedd2e01a07bfc667eb138f8b19dd4614eb168ed67f7225b6beb9c8bc26463dcdce60749f1f47922f3cde47271d0703a1bfa7a7045f8926e598d1269457376813c38a70f54956c11f5ad1487fd254e2e4208d966d71a87940fc46b359485dd8c3fcce558ccf5a4067fc8e0caa6dcf765d7309a3f1b6712409ecb22a18a72ffd79e2b8de80d243ce4a01e2bd2578dff2bd77d5f02dcc7cb3a513f4a6cc3fb30d91ae8ca4f4b410f7cd4e15427de2f562469f072137ebf618cef0bd1cd0b8076799e8516d67a315e9163cda727fc13c8f859c0dc9c4a0ec2b466f12f33d1761dca482eb60c8aeeba2c41b091fb71d243b5a3febff3122513efbcf263fd17ca5dda39f0ec1e5be506fd0ce99eacd57d01f0015e5aed52b9a917c876194ea9ee23bdb78d02304c50977056e92d67bc862cd90564e97031395fe8c20da1d1acf9fc603b971657a9b16a4863a4f48aab8af48ae24d1babc7bb6ce44f6589831f96c324687f90fdbb25552873b7845003d8facb91603cfa996a4bba087bbfd151a7a731c14041d67ccfc968527fe5bbf06ed03c4f29cdd9e9b27750a072897bbb8036002524135d33f622d7f8bcb93ff90e704ea9e7d542994fc94b57f5b1c1a91ea6b6a9e3e3dfe466693b4468adbe8c6fe54e32b39e5229e2b0abbf9cec4aa8b6e5a47a15a813cc33f9bc95e88db30bbd20128c621acd647c8d0e5d8795a37b80fab5cb6d7b4c5bdb5181a48755cf367ee8bb567e7b82084e0c55a230aa3444680206ea7a5ec0df00fd9c330faac0da53a794081a2487b8dceb13d0155667ce659a34a524a4ef04457b1921da766f33323a403eb192a10bf85bd79fd9d6e78874a3e8042ced947a26d81d5dd746794a05d50a95f530fe814db439b6363e17917101a8f4dc5893f7377ffbee273981326ef81149a5d917aa0672575ab04a755aad42f78128f76cd3c3cd4cdb9e29c8025cfefc361412bdadd1ba69e1a4e855e11521bb025610719538819c7c19373959cef5b61369d73686a49e5dea5c64610e8531d2ea1e8dd2e25ad1ee83fd14dc9d130f89c6c5fe6535755820fb8c345c87fa10034082b220a78adf1827052c1e8e3b8c39eb2f2bd4f60669b00066d274f0bf980469565f9d0a77ccc5c2e05d3dda0f063f826a5d88e036280f0c10409c66ceb0de786954252aa7c8a4ec5c658e72606beb36d438729d286e63b4e2c2aea98b0fd45df1a6f86a29ed5d30e17faa9eb28e4b94a78200a5f848837c07950937b69bbe26469c41e5ffa04c322e6bc516c13937a7e64f9c4addfdca6a6d2093f34c2574bf851213d54271c418fdf3becfaf0b76bd10a62f086e0db547195fbac541ce51e47c102fa84b3f42f26f8256b49bc296bd0df430ed999341cfe6a604a9d98223a44901e73d48aba78b066fff34e51ef064a6a3ef872f5d8a3d9ae9c947333aeb21f507c9d898a3839680c351306e0a306d9fc17c7c9ca39499de0d1819b3ab2d890291396096ac1c0d63c5a44e1dd4399e068b9fbd56ce2187d52a5876e8c755c7763ba27b9c7fff184cb37f7654409fd6da244ed2de97dcafc8e5a6299d1c68f07faa2f4ee2d5b59439bd404655de14502104fe77a010e709a2cb1f61c1ed66ae10d34e0eafaa99e1a43114ba576e2fa006595df61ee19b1f919c600df6ea46198e7e661e09959c9ff48ef4ca848485f0d781304ea9d122f89cec1b362a142620bdeae8f488b36d9399ca51045c8c4c37d77121281155b0d8f2af5a6c59b6f89e703bed03b96d43d2bbe0dabbe77e94b9ef9ebd6e7aac275f9f54097c7758056f3473773611481705137c824c34e4f3ef5d8211dea781d77f2350713a9a8d915de0e09a906d1ea365411260b58174e8f3d598144af20f20f19d723e5a1799ba7e3b0b2972e2f6e70f955ffff16d2644b4d963c74fcc8cd9e90e09fa6da94f791d8da6f109931628a42ea13cd6d756c768c84cc036f9c4a681fa8e0b94b8a5b77b9e35feb84cc7726ac2f7c4c1d3bc04eb024a52f297f5165525d20b971573bc640183fe9975e79a222e829f9221aa19b81a87e1d4ac9c2bf35019a2d177d95586c8917cd392216df2c594179a681f59bd61957d04ec46c8ab7070db2eb0c8075f6aa61b22763f9c8042b5bde71608036bda6abe015b6d17e21287e141da8827d2c8ab12499fabb77ea17738fcb99926e37c0da0030e0234669a025ec90350206843efb055023fbeae05d0775f6790f2a9cacde2930fddd6c26873333bd596c2645de7abd3ef13b9c67abb47486756c9c7b4779635b90fa44885b3ba33e43271f67ee50b87ff5d9e2b97b9f76b6d812dad17d360a7ebbf53315585c3efdf183f6bc6b7d477220b86b946386b4eca29bbd2a473c5e8ac10aae6e67c3ef31dc16c4abeea593c84d4bc206cd578e5d09a05cb28289097c200f502f9766e48e456088e08dc24264e469304b1bce3ee8eca1ba23d951d3bd40e5520ebda2b7597a6d1afaef3cc730c5444464257305745efeb8d687aa725566e1ddbdfe25262c9af40edfaca92256d1291131ec090b353db06819fdc9ffffd7b802453dc7fb71e6665fd0e379a27c1a01496428e61c27bbbb09dd33557168bb07ffbd124bca112422440b605419db2436f9317e0d887cfceedc4e9f4eb1fd027bd05d705eb867e9880a285726d9ef165db3900f950ad662be49185e0cf08ea38be7f469134adab33404aa8c5690e87ed154b2db8706f8ee701da6d05df25b7cba51e0f9b62dc88c16ce8aaa90a0836c193c22cc004079dc9da2cba6f46399f7b1a4ef8f05c0500c51729184b0bb31cbdd808de6264035593720fa99d3215c911086de4410c1747683e1d629c9ec84ff73aa15d771473142982ae01f8079a2fc2ba997db1cf458d5292a21d5805271ec33a025fdc9ac6ed52427e8d2854119786b6107e585b906dc011259bea8cab048f0a93341a9122f060d7e6f319d8ad5fd91a569c4a082492e5b720c554ccad6f2fd86e35c659202447e20574d1688dfa9f1b03882c50012de73e3611a9cb81ca69bd68c8c66057e142b38adb3440cc98d41ded9d0cd2572932d89bce0e0557fe5ca13f5aba0081f0d3fc0271a26597b4c8c8ab3ac643cd95cb94116b20a705f1bd6c9e8b4c0a6f08911cc9bd84a109cc5da845de8448f94c97a9442e60b724469fba18e57e2ea0f845b0fd247531edfe3df6de65c306c6fa1806bbeeb46d419411f014d7f52f50b6f84f0346bf0cdb4c9da071b7841b5530a9f9372ed92a1f183fb36bd45caf4494b59092276046b9ba0e0802ce387b1050f7141ee391fed3f652486372fb7da276f17ca838eda7bbfc5feaa844602fb160da4869adff9623c6bf7aca0e60fdda05f7cae3915d5b4a6f2c3ba64496f0b669e66c59391d3f18ea45b1bc1fb0a50ca58ce650af2c89ce88481bf47dd4c30886da4c8489d7e495ca38a3cf60a1a52b9ff7e7cfd457602867dcaa7bb40b63e11047967eede3b11aa73899621bafbb678224fd48412a378a629c21deb13e2004df429cea178754c8d294fd06d812e061bb225a36c453617dd252ab20085452bbde00776f8e37ba20b0927df049de62e2c2b373acb356371513f111a22464e48353382978e73b6193abb78b6eee723fa74698f24e61ffdb396244dfd864bfbd632e8ebff4ad6ebeb1e031cedb00bdfe0635b73ff0cd67273d819c47bd64d1926bb7f27fc5ad52328ebb95c62dd19b10a1e8014e57ccdfa186e1b89b00dacd74ad2838f436a9bdbae02c0bfd35fb2e50bc09cecd8e73b01c812423eca9ed84753404f78ee5699087883d2b38757e6f646c22c4a8560efed043b570c900d4fb0aef6aa2a502c36a4ca0870becd73c801d78aaad86bb4895d39558cc3245ecb2f957ccf013d91861f395ff04ae9ccdefb5105b83c62d1f6650f7e7986c5745e69923330fe4d3faeb7526c2b4bf30a341febcc7bc3f93fbde7fba7a519e163db952538d64c0bb986ee64df4e21d61e582795d70c30842c6a83b09747f480535e286c0def664ca097a97236f37f9c467c6af15a8e5092f5279104f0a525d58e775c51c74f624252d1ff591aeb6f09807638f35237c164c45dee8e31ea85a3bf0f0f4c54bc48639c57242d65ac4d3a78682d4d736e89f5b6fe9acf8b7afc9a52e82d19b59bf564155ba44471ca64c06e2930f8ad4d99310793940e8eccc1c5f76b000bf36ba1736480fa91dbd90bcedd0ed2f8fa28ff43ab49083894cf6641c959cc5ec5fc1d39bebdee21f0e1c4aec9b4f2605c07162815eebcdab2e90c46e03edc89662bfbaed5ad46b8ba34dce15bf617ce9442ced140c098ccc0249e67a576cfe4c29e2a85b2d1d4b65eda91e434c2b3d59b7840a71df2ddf70876efca2c0b1bf6c044ef24e9a2df3e6faff1cbc5374da5d3e336963f8a0b9969bb8e0e3ca8e567b8a0e193571cd1957d23fc593bbd670e10dcc69617a9261ac7f9d66d55ff1626f92eee613ffb0001e4ab858cab48583068161733d5527e8da457c6e864d85d6bfc7e6d8ed364b2d3d5a358c5ec0cc6f54805f443430178e157b235286a0670881a88d808b472af08c6ca7ed9f62f3adeff931b0f639faf9994bba476946ffa9ed8862498e4241f1329e652f098f748c549fa0619ec818c67e8505aaf9f2c1dafe7bab703a37f10f6ef76a23259c1398f00914b3d3dd33a07f9eb3af087bff97468bb9dafb2cbb9b4493354643a7a63ebcc38c2685b6e1ad917fe54ff393139a96cb5dc6073efde498bc629589a26c24233226ecf040fcae54aff6c8cf72c98e4ca29be86d2182a8ab088a81e6878ea91c2cd26e90c49cf87ab8f2ac67c8d1605f97eb65d6731d09c8a6578c0fde03293d2bba2bf02916951b04479c75cc3e6c12ac22ad0a5968cd056f567fe7872bdf261ed9a8bb4dcd989277c79f759e89f17b27b2a7103ed46d58fa1dd8495b6ab760a17fa39991821b755db4796e5a34381746aa7077feb456d4e74f6f9324baceb3751c9365ecc109c1673860f2ac746598460d9777fe1c2d0e43cc8de47d8d4b443551a63795d0b3840e5fb596cc8b0dad111c3d407b0ebf3894e0a788047e4cf80c11e056dd3907e77c29641ff6976eb74591b6608a6e8d5032ff5ce251623eece33cbc87e906a7b0416f58e25a22df1252d567cce5325fed9c67e8256e9de419a8facefc36eaa11c2d2b88327763d38b66edf9a27a92111f6354425447ac79370caa19715b589e1512e6eb020a9cf388ddaf5d90808586789d13ece96c959b47c9b71bcd4a2cfcc8b819c2da4696b6b27185c8412f4f4dc0488331b7d926c1c42d48ccbbf74eb897eafab0453587704c7212a7cd625b762ed73342022b722c337313aebc1a70c3ebd82089969199b169c8ee660a2f61189aef5cd324d13c6f85a9f0412c7fcd44fe8cd7722d77c993c67869a658d47d9bf4735d500ed54cf3e70e9185af5eb4752ff6cfeb7c08c1d3e6c6954cc6dcaffbb40e79b30d0398bd4cd883a6cbe172feb0071d81e718fb84184091ca49cb7a5b364c4b89f08dcdec0ede895ad0b6a2c777ec12878b0ee178c65bc4ec49842279c3bd3d15587d1d67d2abc8007c8def752eecd770b302963e4dcb842a6cd83982c9d775b8eb4dd38bed4663358afc7e06c299c1f867c639b1787fe7876135c147343b713b353e7efb9822e74ff49b28a05c29f4b169e71410d6e5551c9a0966e8f1157b018275cef9ed9d1f0aaa08bf5cc7bda02d2ee14b0f65167e3575f544eb239c7dffdb28f6c82a1b07094e1c253f53362433d7c19f9d98d7961e791ddeb2c2c5dcdb1632af839d90693dfd87adda673fcabf68d22252d4cd6ef4224ab1892ccd5d7da7cec43c58c7dfd7e22eb260bcff27ccbc2c4e19bcbd65cce569ad9b6c8acab71078e92c26b6e49340fb71d1339d863a245268fba48d30e722e17dca8054c8abb6c7524f976eca5b3b56008c1b161b120b8d380b89f1d2736f723d7e8937d435234e21e9c661ffd1e1124c2d4902129d81d58a1ce626af2c5022478de85140c1bcbb668c1306b24a0c3140352c99fb3d04e3b75c873f028eea003a5d433a185f16c9eea80221fdb20786c6228b30701b58ceda485c7e7676fd72ceb7490f7cdaa873a5c043bbaa8af57f88ac3e590100918ed6e7e69de68922298852a72b5790d8f7f20a77084f609dc473ed08ed1b5ec0cc7946ab004c3c8ae574af1e27f2e8cb1baf6f6735f68eaab41878659ac66d3a807d63b9865c085e556f5fbbc47903889d70ae0d073f9cc0c4dcd81864cb51ee7f64aaf9873c685d479bbfdd4e61177776c907d97f684deff4404bb7b2d0168ae8140c9f7ed688cd14774f8efb116c3e3578170099b696bf2d68a2652eb63585d1effc125288632e9fe22ca9e175900eb20f4df3956f2f6e930006ce04923c68f8a7626faf435d86e3ff891a4dd8fa42430a88f6a23a3d3b59549a557cf6381007e30f8db0120d9ee70c76fa30f6380dbf2e9512363026a8e6f03f3a777f5b62f831d0c6e5cb120fe61aca6f13b42f66a1833f5bcb136c29b533822d3f58193bb3fa806d12dacabd4190cfafad41cd3d197c58f2f483660b00e0e75c36ddd5551912af650865f61c6636c7f7fe7bebfed180387c9333a4cf1bc293445b837184924791f7376e853e4b251ca29dd86988cfd5c5e92f63b06fa856a8aefb1af3468d6aa4f88354aca6c9fdfece239e0df3451f8a247199f7a7c709c3c1f07e3b4376b98526ddc9cf700fa05b54713b0a2512356f5ecf661db488cb5ea2e32a96978cccdc8958977c103a69e11752f81e20bd6884d84928eb9e6b41d16d33ad39f442e644a8cc98ee7b75657386c2263a558aa5f69ab79abd13608ba818587f4f848b962a6741029574127d37fd4eeba83e3117e9c2faa8fba00091091f254c253fdf39cf1f531f77f073185f1f4b56f95a08f8486f17a968d570f013e4c3f042b644bde3377d4242106b9ab435fb16da346d4e514ebeb3610b038ec0dbc60bfe8887096154c6129651738ec0dbc60bfe8887096154c612965173185f1f4b56f95a08f8486f17a968d573185f1f4b56f95a08f8486f17a968d573185f1f4b56f95a08f8486f17a968d573185f1f4b56f95a08f8486f17a968d573185f1f4b56f95a08f8486f17a968d573185f1f4b56f95a08f8486f17a968d57c8c866549ec5f3c4ad4f756b44c60b5205a66401c63fac9ff92b83eb544fd83105a66401c63fac9ff92b83eb544fd8310f013e4c3f042b644bde3377d42421060f013e4c3f042b644bde3377d42421060f013e4c3f042b644bde3377d42421060f013e4c3f042b644bde3377d42421060f013e4c3f042b644bde3377d42421060f013e4c3f042b644bde3377d42421062a52a3ad647a7f38a6cf2f270695a9e6940f43cb68167b7f379d91b0a42c277292ad081d7888444b37acb4530484e05a4372b212f2a93c6b626df5330baa2be0d166cb4337b4e34ec3c8c8aa78a5d9c013e8946a47c420375442e1b45f7f4c070000000000000000000000000000000000000000000000000000000000000000368c3324b3df63fc691b1185ffcba9cfa2c9fb0bdc25df8bc178a645f203637062609093add0bdf549e22f20a1f5e7bbfdbb7027dd42a89ee4fb75d70bdf9b88d6f6831146d2431e8a32ce360008b3ca3f699b200b3e4cddadefb64f179ad2320000000000000000000000000000000000000000000000000000000000000000ec766159ff8fdae516f9b37d40eb0f930170d947ff8f26b801701517ea96ea781456f46a36bc5fddd3ea1a15d1a3db12bafb25bce6349a4b04703e81fdd194478c62a3cf670a433ed3b177add748d5b3c1dc45bee9fdd4ea4109e9336af1d26bc5405f6be999c4f25dc59aef673560a387653b8ddb75a17780e1b7fd76ee4f46bf6cfac365c4ea6237f9d85bca5e6bb47dd0ee828c97efee65b38794dd248d88bb990c6a4d0300e4173458e62317003ca06c694cfca100a45cf8e116ea6d047c81ca2da069011f6485894061e509d9bc9ec2c3a84a45ef2951525a9d0be58a2540de6fa21d9c7fcc7ac1491e40d7952f554a04d4c1e2184d51081ecc4e33ae1b373ad2942767c3c17c97bf111ad2574c62243d7cb8be6616aefeab650b37cf9cbef6b3c75381aa4930bfeb754c89a903503a723917c1a21930981f92a24773b34c29ddfe75f526e80e210cf83fb6105960e754c8c1fb746f9d53527a4fe2320c4b4940582c3064550b01c2693851bd554b074ee48c382d580b33223edc8b3c694b65efb207d3a7e007c58be43bc596243063d23fa4641f0050b6c0be7dc0db0030fc44377043020650e5e28211d80f2a2f4534947be86e2648e46d0d625b080df83d015eae7f3a5bc6b10892c67d997e67dc3cfe70703276ce06aaf31913613ba12fa6a9b685a79f634d8ba302a8945db31dcf781498108fe2cfa94d922874e928afa41f061c2d6216ca80dd2cc43baf9686850e3e5da2ca15aea665b78c708a90c28ec706d913c9eb51e77434ef8a7f6a3d5332728acc7ce3ad466022c9976933c1eea1f27f26e35f48876da87f0d3698fab2fe9b7d5e7a25dae4f6466f955801f741c0f20a166c05c1cd41a54c9af41d47a0cc8a1838b0c7f16198cfab88d16c9cad2ab2b2bcbaef46bf2ae4e2281b89f03a2b3d341ebeba939c2eb16dd33201754ecadbe8a2488a63f887e13483b7b6676a95cc66096d10e31269ed379f909567559caf985e9c024f231369da3c457c7f4352fc3d97c63eb50a2eaa714b9504b9dc368a1923d9039b91bb990946aa3e7eb7269733c034d04df5999343fc325089246558a70dd42f446d07e5d71973835cbb49814f5f3f8df76fa11d94ecb6215105e05f36b8cda1a3e9a784ff52df6af96935e3dd15a479b0910cf15b384eec85143523b6ea0f9703a0f360329427ce8d54f9b80bbe9ddec4c9eb0b12260d63f4e767d2cae5a882493a146b4e47e11d6b99c20144c5de2b9cba03bebe418247596651bfa2d58f84e240f3efd3c23a0558ef5a0f5be9bf864b8669191fe087e6065d249f8abb35837500c5bc6b0f47939da7b445e3e24689e01cfef6b964b13ef0d12c04c4c4c62406ee591dace039a9e8273c5ad540b85362fa92bea88fd10f43af97f03ac1a8061b0b4285951a1accf33ba3f27015a2e1cb524f917a49c7b8a2811917c23aa2942f373aa4e5a8edb1551110f74d3018f4ae26b1d1ef19c6bd13bab2395e5b705d0324a0f5aa74e3fe5d73f1be4d3854febc3a146997e550a3cf8654b8cba36bacb4080b259bc4265303de4f881765cecff7f483281286b6dac6559cffab3a452d27a1fe94356997d143c2e21547315f04d8d4bca4269cb6ca87a13c94285f49c7143e65234e4fcfc877a17ece5b1a0e6c59e9f2a77423f5b52d3e6a8aee03b29275f9e27b227eb2923a66d6507c9aa472f0b6c9aa2db217273e474783ce23daeb8381ba6d4f28b56e1d16bda0988e379f3835f7a3ddbfdc03b7e1fa2656acfa40021b85e002eeb0cd33846d3b559935a50a97851f239c92dfb258bc1b25e2a9618bc9f116dfc6e5da290d5705a9eb3841da21be2442eb870d13540bcdbf663a927789d0d341a49960dc718c1011c9304eae6fcaa4d700910834db4248a1a18db068b6595979a6552de12e5dfad4cf978c87c79d5450e7dd7c523a8a9d73a9444182b6f6f8609f2e19ace476ad95d648d0bc6b722dd9047cd1ca6c6854c631c832d4009f5d1f4d2b5fb69d7f2587c283e53255b0718889979cda04f73056b1ad1b2a30aacafa9fbf9aa664c4077a724e8052fab5f2ed67db566ba4e222bc3b6a73651c3f80160dfc789b08d567e64f8486c7b1ddb52292221774c18d20d4d4dc35e996dc2fe7bd484c93ead10e3d78e648832079d1f4f7918312d99dd362f4703bb3a12088735a0a98a450ebae20c5154457e7cd3cac9e3d9b5a0c4dbe504bb4dee3d96254dc9f6abc4d2209015b6b20f2aae046ae999a3f93c9ae3f9844bb9ad3ec3925f55a65a57a137a5b46ec551d4b5fc2fec855d872c0cafd111a38e4d83590998c927591aa90f0f12a9b28f72702b0bda7e7f6cd1459e2566cfcb5b620c17b3152541a4eeb5dce0eca35c3d810686739f842f2ff84d35178cc02e8b4a3e5e141fbdbe61e0935ba52bd7edd74be9ec356e7a49884b73635eff46c706d6ee33b7cfd71714359e506863193b61979d323b6766065104d9d91f3d32e28b744e45034ccadc361f9975623dcdc5eacfee20163912a80850f0839d014c5d43e70d47c6d5486cf582819bc2c0aeec2d8995f04d54b1d69687daceaae956f8416fcde6d844a05406c8a21a51d67f8cf8336cb4e1d67f8cf8336cb4e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167ebcaf8024aadcd8ff50b08f590311a4fc50b08f590311a4fcc0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a201b2bae4d8acd6dfce8fd47016987e93b6bf9ffb102fb0e4eab3f2eb451ea682e810c752931cef361c43087d78fa305eb859895ba642655ef1ba6ce2acbf3e0246b50cfce482bf93f43a0aceacbde24aeb000000000000000000000000000000009f52d90631358e8f6cdb52b75c37fd08c71bf7b21e94e6e7cd2f644a93d18e94af486223b4745a4c081d73d9052b4b740000000000000000000000000000000001591f586ef31ff7be0f516fbe368ef5fac22dffa2895525d57930b547686301d45d21a489ffe39b75a58afff0eb0bc0f30100642effff5ba9011ca47a5b055c6989e2ba49d214def7be0fa5e0752768a46838e592399b375c7741742437a5a6e8f0b176943dcf9a618edd3e0fb1aa3babde0eb86adbaaa1ae0e6808eb03ac6bc65dd83a6d20b4f16c89ea9bfce4ec9bfab96943e8487a43dadc7effcdbf24ea4cc88e077030f76a4c76e7341055c2ec163a547271535079a0924d201a4c325163227432381799cd57ec2c6189a42f9f66723aa8c1814d5ace1999994b911e78a4ab2f3311fdd5487faa4d6678eed9fd7aa31fcc4a88f5f05c73dd940bbcb6968ac762e52a8f67c47be9627e3718fad8865cb47484acd6eeaf80ee309fbbde87cf7b85565a2617b7ac59a65d7810a201b96a8c8f4a786e0b0fe6d6ec094a05506948e07945082530e1f22155e63f0351289ded534cc016371a4b7f4b16429f81b7087b109cd35a8b04385d7344c97bcf1f888c27df8062acdeb2d7141a8cb1b616dee591b6d4dafe9e0e49fdfdd4fbf75860ffecf1d4e2c76e9dfb7a9dd53377074be15c4ed76a42340c298a24e4ebd06d511fc7ff3f73b60037db71febf26fd0481fe1cf53f0fec2287f5cab3bf6a74f4abb68cea43eb2eafa9fed869df6e48ca9af6eee42008fb8733be8842e938ddb76033bdd1658e0c07a0672cbcc8e457315fd536257e41675997d57f0475cad2711ed77e1f3689c31cd2e177dc7ac058c9b92c470761436d810c39f232aad7fc89518f9f64aae5e9c532eb5cc0ad4765695c6e8a42c2f5c4e17e04c9d155b8622c711f7fbc590ab33616dc79277548e57e9704551437fb44781e20538e84dee249d0e045e4a1153405b025e93d6d976c24d00760b1fc23f8feaa36a0d9eefbc8f8a07e61f38de37ecd5b76aaa7e638789e793ca995528e49554da7a01744e40255199364a5de3d1453ad05c08518b18d4501876618048f7be61c9884b82e86968114ccdb755b2e4597a75ec353219d8e99dc2599695db3bde5e4c122db0baecc0c756fdfe3d7536207030dcade01851ae0cf7999dc6137fbf9f467ce26589a5b7433311c9c3cda049a846d5cd087241b48265f2995df017f50d9d49148163169029479f1461457a30bc27270b358c7c8776765c6c86ba7c3dfb1e58ba854e49157f142ef373f1b979ea39a6304fb8215acd374e92f57b48a791f5d65e054cd956f3168368a8079eff778fc655ee3e0a44ab5a7a9713748eae30092eced3c356d03b4da866ed1421bf32e4cffafda212b1fe27fa5b9454a82b424775180e36b294957439f348925c45d910fb05edafce01e7a84f6791aa0afc2386932b1f4b8485cc5f7e3c8b0a4c48ff3b4769f398d1f37209718f212fd51294d2864821120d35bbb5e65d1aa3a570c9508a6ef4da0232d933aabf83bcad49e17672997fc865e3289f23e4a0a2262460331ee800ce03f95dc59a8c229c5d536780800c0befd0cae02cbc4296f840117b83e0fee50f29d2846e35cea4abf0884a97b1260642d2c62f25df02f0a8cf259d3afa846058f2ace5d0206f16c61d353266c464c214ff25f2c7e26be778a9b0aeb208c01c7e39befeaac045d755842c57379c18bd3ac7291193fd2b733c0e3c2b82bcca2e270edc6796fa507a6c3acae54e0d90f10f09a46e1a6445dd942c9943196ff777aab5f23c3e120550d9199e57223933b54548bd9787c30044d6229f2888efb900411f968591e4cac72e47e8289c6c29b8d12be27590539c7a0f90ae7ac024f8ade615f0507855f1e5c1e489126b20944c336d3cf5e4960a517ef39a2369d63784432e34bea85a1bba8e60399e75e186286735df14ff553ed2f25250248d757febbe031ae34aac3f1bff1468f7353e073f1b455eb6eeeaa6966ca66fa1e9b7c33d10deb3d287afffcefe207ce4335d14b331812f6ba3aad169b10a9c2188b65f51ef9be19aa090e4a03d1b2ffe6996bc3b6852cb58d0a89c7c9b52a4226d1e9351a37651466352b9fb873930e85949d18d39f7d323e805acb00d0a26525ce509d70b398c5f7be8b8dbc7d321b3c5d6fe7c22ac4a245842e05ead507b750de5740d9c75a0c8318396f5ca13cb63605aa0a96c44aa7b7f3e06df9abfe8e4ee8f2946bb7aafb95b4dc72b1a34d9bbec5353dbab023aa030e7401f9203238cf3e072ef1ce110dcb10398a23b6b0c19fa0e3445c390a3c5f546a0153c271b186963218fe9344d44b5d9a166e39f5560493be0e13a2bf64f58229c1e6749e427e2d80397a0d3b047b5c2e222028ca1bc50a4f6eea4400a87f45b684e4d6328dbe05d1ae4be29004dd405d8a3bd355116d79ff6e3a92a633accd5f887123790e672e2c92eb5786bab6218888a0b1feb91a7a202e6c8c2b6f8bd251b1c36cbe3e5a03f3c4921fd6b50c20c6c4352ed374744ae9c24810c715ceec318ab86fbcfb4686d7f51eb715f587f6fdf6ce9812df05664f37a86881530960f598b73fa4363f624f71914efe63d5ed97595cee5147ad37602dc9a40f364a417de59ccd18b52801b67a3c3369058f172b8fb0305bdf19a36cb307ac7103b0ee9a0f3fded8d3319ebdf68c68139180ca7290abebe77a5ad344500450eafb1c8cadbda23370d4c6daadbda23370d4c6dac69de4d2e8fec50ec69de4d2e8fec50ec69de4d2e8fec50ec69de4d2e8fec50ec69de4d2e8fec50ec69de4d2e8fec50e2655a4c828547627bb15d9702814409dbb15d9702814409de7bcdcb119cd0dc0e7bcdcb119cd0dc0e7bcdcb119cd0dc0e7bcdcb119cd0dc0e7bcdcb119cd0dc0e7bcdcb119cd0dc001395aa215169b0403d4f7a0cd5a59b7cf60983b5c29d7d94cbc33aab58ffed723cdf4ff398ffec0f99dc6e67bc65892f16a42ddb0999e991e3d0c7dc4e8b42dd5278ce3cefc248cc39f35acbeea0bff61000000000000000000000000000000003187f551242f49d3dbb5abd6aa172ac5f7e627a2bc696578cb83bcf1a153852cb077dd74de5573fbf8355e1e26b6226700000000000000000000000000000000017da25dae2365af82e36e0e802f5d19d3272d364a54fe1728316e51967635ce9d55ebf8bfb417906ae85400c05fe8ffff0100f93fc30090aa98eaf87ff8ff8f6a557bf97830c1c31b681fa129ef64fab2e4a6b591e5a7ae8f55bfcba7b0c4511175fb1185b384195531e07da3eaa0b253552171786c66e24951e9174bf9cc30053761a70dd19a552481a8935fb73b57fe819b099d09a262f40bcfe4a01c4f3290571fa6dee639719e5ddb8a165495185589ffcb9d4e15ac53bdfc93502895b4490400e44562f3b1b118003ce9b3a7dddba200a460f1950f036e047ca499196d15021f647f33b3fb950ad9bc7b6ce6e11946ef2962f74c2db5e68a25afc71a3df47972ea75f70f4b16d8952f866620b2bfe3184dabd2e2de3d34ae1bafc33318b168c3c1c95d6aa9d7d3574c8495e8a1e5bf66169e1c5c6d4738cf9c52c984fdf382aa494287a1eeab8aa903d0b66a86b3c2a219b003ebace84873b3d01f6dba5cf626e8b4e0fb1889b71059f227e3aebffc746fa01c36c83de3320c63c97a79b0316455b5855b52d352bd55f5ac8040c7392d58b5bf84c3728c3c69f540a15823d4a7e0b5c6686cf7c69624f974ddf6c3651f00d0370ec05bc1db00b088634082440206d0bfb8c28fd90f2ab0410d53eee96e26d1d15c45845c080db8bf89e59d803a5b0842c446517e997e3ad05def3871327699b2908b8e14613b32e5f4d0e586a79f5f4ab2b648a9945d9d09e0fefc99108f4d4920f8ea2974e91e07e2c86c1d2d62d8332e7ef9c53bafea704373d25ea2ca6a1bd826c18d708aebc4e80f48da13c970635d6ff8f08a7f15be8d0bcb8bcc7c9637e0508dca97691d882136dd8026e3cdbeea7a0c800d36a1376b5c577e5e7a6887ee8663709558dbb585b0b80b166cfffda7d30c4d9af4fbf197c9591938b0e39f278374ac88d1396215962fb3bcba94b0951a4de3281b11d617ba1b351ebe77daa616c26ed33246fe8f9e4e01053a64bf2a607f9ef34b038f3cc1a34cf2663e6e28e2c66d606187b3104443e03f6f8fe09300c1cd70325e11cd4c5bff105668bdf7909c2d9615bb53c01300553753b1f4d0c66aff0387c16790431b33b1592a7aa52a28fa1d4fb9180459efa5cff71cd07f8f6aea48170ea0c7a72a074f10ce38d7a5d0dd840f87e33e6903420e589046cacabefab7b154080f8bb2a97e6e373f6d27608e73294facbeb7f5c0a7f1b98e16977b93895606f95031b3f9e4ad620172a9127d1a5db05525d73ac0973f9bbff67cc36e727f57df82eaa29b86b96b962bed9a957e55d1903c6099b0383dc46cea31f33640678fa2d304e2d56804401d05ac576880badd5a2699f93c727e97d68df08aedc0b67d1229119451fde7907320e9222e3ea93da57ac90d960e38f16c946c0ad60454438306621f851611939f90597143a5713528927ab1ece09dcd8b3ec43635b3f880b743e56325884cc4fa8c60287cb0729fe54fdee489f807b66e5e592d758484ddf4a96123e63929827f09c1fa4d31f32da199edea42a892d5b1b3ed194ec6af9c855a18538e981676c1f6761b4fa655a974d949e8339dc010ebf14d09fa22170080d40d1581e9ce544db03e59b7d654c4e1e23f3c8b76ecc0f9b895a14b52741cf36f155498b272cce642f91a373c3273afd6a6257737825f70c9bc03e2d799c2eb9098b3b84b1daacb7f73a73704e046ec52504dba169b666903bb05956d746a1d63c579503d428f53f4be89ec1f28d4a6e4e1c7c4c2dcfd491a24c86ec12ab97edb73a5486d26bf61168d6828385e1fa94ef4aa4c037a51ef453746822920e76245302b9f9d43cb0be3c72c2bf639b3afc1b21c90968711791d7c4eee0dd3fbf1c225e90f7ed8539273320c4c931f0676d2bf6c58b86f3c069076391363b9cbc69890674e9ca7d09cd2e7ffa903e0aed2b28e1acf79e77d89cfd872380bf1255c48991645af861cb457d98e1005556e8e73fae60b34ce101d91df324cf77ac9e235c8a2c46add186f02f28194c60b6c3602e1dc7eb041550152493080662c17716e9c7665976d5a792a930846dc271cfd75befa84284c97003885aa1706cf51d1a4782b15b4396a91c3da28eddee7e8b726f6a8463d28892f744bd0667b4f2b9e8754b882ba8a310fa8a7adbc26133e9c5873038b21e6d4a3797f15ae4cebab832e5f9245a0d4b38841ba2000aa77737a2fe9b874b27afee2fb22111335cca1f435097370244b60b6ee0ad27587d41c5880fdeba59d9e7dc05e77de2bd3543cf402576869a832aa53b9d19faa95d9bc18a0604dd3e7c369695eddd07c186d0b1370e431818e578d3959ef65e9ee88c1f5708c168d8b8faa2465c796c69ba3f8f9f8f28f0103fa715e581583a8cda7dc234c871e6bd1792dac7b2c4ce6245e19fd59bab35fc5a7be9daa075dcea9209fc013827d9cccc5125615629a0dd686d34e69c9e454ed9481718c529076d18f45434024e032340a7cba66f577fc8c742ce0b37d76219bf871f7e8d03d431a83c50604ccda65fbe7d8e673241c232c2f1da968fe000d9cc22f3de6118c45017cfead49a7200830674d685c1569fd384b1a80334daa16a47d8ba2ffd6ae3add8822cfda89b1a6f14fb37e2721601f88016e4e440b3b0e14044459c8ddd4e8f2044459c8ddd4e8f2afb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641e3cb26d1e064b76c1fc98034d51d5de87fc98034d51d5de879564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505950169e4f82604ace5b95e7dd1deec02942942e6fdc51a89f22537ebd424a4878621ccd1092b6397440be69fc1e470114fda454d67f4c098dc7c506e018e91be117fa90820f545e01b7fdc399593c02306320000000000000000000000000000000005585c1b9305733b8ebcfa020ca502857f72bbcbe8365a1e2881a8d548ad06f6343bf43732244eeefcdc90467d7a00ce0000000000000000000000000000000001540d700dd2b53a1fe73dcb0f1dde1e4eccba2954533bf93cf84052ef3f3c90537110cac1a83aff7ffceb8cbf5bc9b0fe71c45741ffffffbfc8015781a8fe6f80e4730af65c767305729e3069c49f5c5a8061d25e60ab9a1fb99e08ce7457f6f576335f9a9e54117637689a385950793a80d9388c713251997cf28dd51e61383163a1e1d6d8a78a58b3692be0ef96ca6be3e32f2191208af248084a6d7646b9e7ac4342be93505daeb0d9cf330e348dc4cbf3ae6a686cdc5f8baac8eadcf6069fc0a19bc9e0e43400406c418325427201c0f5c99606cf1f0a40b8851f2ea9de46bf09a8dc43a016f033449808e1619e9062dd293c2aad54f4a80d25a52dbc50ae9fd6623c13cf2fb0079d179cdd04745d2f4ba54411222c8e460e85e07bee34e3e463a323698572363bbb77f9e0a5217d9a1e46d22989eb6b34d6eabf26c070f366db6b3f154115a8c6fff2bb98c7949866fea4233175112cc9f482f958347a347eb194d26f6e576f6fda10c21105640b09f9754e7c23bc4f3dcf392568f8242eaaaa9404daca0243a5aa1020f78b13d57eaa74e0c6d388d36da9302375cabdc8f6a154f63889307db76d50bc91c0536cffff3226fe434af6f3ff640bf9db07bca0ffc24fd403372460fd542ece1a81fd9aed5244a9bb87ee307f44dea621b6854d7adf1393ebfaa717581c8b0971dc979d68c6cd461707274adc6ca0f0a231110606fa6294745b78272ad6b41130804a0f27dbf17d5081096911fe9c71338942de79f24a1c68c0d10d55a10ccbd842bc5653695892edd3255947e16a017fcb086ff328ec0979903d08a81e75464ff3ae3498d633f12aa7c86729de6a9d2c927cce2113ec5038ff67a0ec8574388afad75b78a92f90c7d9e7774aa24df774f4563f09701fc532af60b74010dc6563caa4fdc47104cdb78881e8621c1f9e06bd8a55b4c6d9552e2bcb4eee6ef45d442e8e1f8408af94de43e3d39c3bc91616db3601343b5ff450876dfea12874acc05a123780b37403a7d6dd5276259dc0b51fa21c31c9b838aac8e82fb50b3e65d21d36caaa85b25490a87145e7b09ed4b9a704b352213ca55c341612ae6ddb15e08ed69d829d2b4107dbc10c1b7838bf3b96bad83b8a63f268726f0dab3aae28c2e1f74dfb45fdcd731b7d5548af1efdaa846f253bb35cb5920654d8bb30be17106b2b9ff149a18d2ee82c5e57688d584dd62474bcfbdc4cc5b9de947d5bd47133d49588948e1ffb385c87287c0aa0546af0161bb2c561ccde61136b50b0ba2b24617fb492cc523c9cc5d3d370adb85fb6465a6486cb7c43bc291395e7c1fdae13a03d96cf4d3895351e256bc55c2377eddb5aba631f0a18796ee6ae22d698ca4351543886dfa48f20aaa6c1234c178801178c35eb6d6001e0fb3ac81355daf3a8222987fc7235dfe890786a22cbeee59da5505408d7a1314d36b943e3e4f7397e5dd3b1aae104524dbce6fe105de6e7bdc7cb3926eb231d5972dc97016fdde5e58dfbe81ebf3f93541b9f791173ba6a870dc74efddcd2ff62f8653374da139d04900252825956ebd05b38a6413d716d34074015c0d1203b28a317fddd91142d60ca79e51c71b9d0fa37053d2b5431f19587cc3d053b4bfc439d4e27934281581649412b4e75612be304cb7fdd3a8227253c7983c48f237a6be2e967a09d7ac7ba535144c852c105c28c3ae7744a2f156e651b8b148c8fbd0d1d04a8f9d3c0446c6238151ea6fcbbd15f99e0b65501d785ab7b7aa14787340e7bf49870f37f82b356e3a1c4a1805d4f773e19ce5e5e5881de6aeafbea63bf920def9464d260e215e64533c44fd5cbbbed7fac50cd4dc8b0132d17b1ca44017908556b717812ce4c513dfb1c27a72c67d03711ce4366c0ff42c4bffb54b079166eb04bfbf73a67cbfe5ad222e541f13776fc2db77278e7ec203985191db35a1722ce03f4765342a7a7e65fa5d5added32b40b5e9698bf2b0a3ae14b1139b6c7146ebd55a6a8b31941142e092d5ee9b720710068b7ad473aeea0f004e8e0f4eda9cf75ef59121d149a2b6522d0beefd6fc5272ec4d02a68390c14c876b65742dfa0fa9912a3fa0121ac2459c92b20070dea85657ba8b2ca23cf679275ae78003bd08055210177762bc1c58efca69e7480d17c801172c1a0b5c81ea76c58b684e5a923674cae15487400bd586274a585e2593e031151e003d6d489c08338bd1ced8d3f6b241c953769a1423233cdf5d41f428e69de32135aa6d371702879306160c07bcddb4d153f79507cb7d331ab62f64c97fcf0f5c6f5d0b731473b3fcb28a287c8cb0cb0456d7c86b6f5872422ae09c73d90d2273d11134d53038459a4660310cc39dd9cd1b53f2be74994c06d5f88ff51ca2e051bb8d68384bf1f08ccfb11c9030ee309d28a38f51c79f6b1379b15910db82f1b0b4ec71a799be827e26f0ecf281fa97e2ad1eba4b414b58c26a22c5b7ce86ee0bc5c7ded0cc1aa6d5f299deef1108388d03af599718e464f047e634b70810be3713f452994ddb5cfd8f3fac1df2b881da4f87b25d857cb6e659b7a2a5cd3010fa05fadf5333923ebcce40e687eb438d64599a9b9f05d90fcf3452c2b9124f93307816be28e6b4bc966ed210a272976750427f28ce4cd2f9c3ce5e95f44977e9c3ce5e95f44977e8657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d2ef5e4e83558e4a375316c72b3f75b4dd5316c72b3f75b4dd677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a49901616453998ac66619c5003dbd391ede2dd2c2d4d7dac101a9456724b2963ac06e87a32b103dd172469cd9af55264bfc6b04247bb8e6dd8cbb4d5013352fa06c6c009d612a4686180e1d16ee95f7a3835c000000000000000000000000000000005725cb126e770126add01a246503a64795df903ec2938838c8dcc0f61ca53b3e85d00a5f110f50003351933b8c58af26000000000000000000000000000000000104feee141ed458082582019d8880ef43dbbfa5eba680bbb8b506e43d97a66b8afd56019d0370002303f9fffbfc3afab3ffffff58000000db0090ff98ffc601d7854b5734650074076601550eb9b39e76d795ff3a4d0bcf5ae9ef10c76701ad1b66e3155508a91144c93799533b9f7bdc79863049a55a61074fad5300857aa93328bd4902a459a269162c04107e7370e394341d70782813380beac0272970ec759762668cc6efc72321b2ccd66d8e77fae1de98df06e544d922182e1d3543e2f01ccbffbf85d0c9e0be8dfe3faeb3842531e0f5bfc4e9a0065721b93f6164662e60e90fbea9becc449f616f32a53699e153ab0b618a7e302b44af51a7c975532e775fadc12d8ee529fc8ba206a18bf9ddded3712e6dd1d21112cb1c45fcb9c37c7b8dc9e3e8155a695bde823a609976e177149499a7313e2a408f0c33965bb327bfea57651c81e715396b67c5c68754998beed36573b64f31cc85cbc828fd2d5992a8907d1fec4170fb9bf46edf74cd11dd43b0081c329e7c08dbd13cc75e536836fdbca9749747da75ec2aa43524f5f72d772c7d7dfdb3c73642376c73eeeb7577cf82f52a85733940ac93b62da42892bdb509fe427d1cff25f843f2da6cc7f9fdc8db9f02fa73d4e67e025f17d62bcf45781199a8da32aadf497a2fa0fa63a71505584c65dabb938f236816c9f8220ae9f8d89c7fcdf4465eceee497e9eb1f08ca487057a55db94d07fb5265a56ff11b07ef60e775cfb7dcd76bd684487df71983f2edde1b21c1d28bd430c2de4c8cb132cda55403d7e928134f758c6ac7301876fc26e6cb9290ab10c5107f7112447d6583733c27dfcf1d46d83665570e79dc80098cd591254517605289f76804c393926185a3f8317918c0ba976be96a4f7ce509f3e3b1f80c59d355bb6a3da806649777efc7bfa85cdfa4275e768d9a99ed2d43454e2f1a456bcd1714d329d825e22bc1c1e624c9295ea24c9d2b21600170120ec000c9195ca8854fb27662ff03ef5c1005edf3fb9417c25d4b034051fffca883e33824670c20f0124933d41bad0a32d02dcea66ddd1c8e1b0f0685d937857754680f45e0d0e0d74b1aaf2700f4d98a57d03853d9e3c825f5e08c32e261e52b9d60b7cc88dda02b9586082f75b8a55db02b17502ee44bdac348aa3a06e5a098e6af4a38df72b5a2e028d2c2a1213d102173f313b18431ab447cb4da2ab4832b4702d89b9ffd2a730d0761cff34979eed64342966f004fe5f6f26ed99c7cccbc8f31cbed09b02facfb6287179adfc327bb088ab2e903ef186e0d09db4e593e96509d1aa296505713c4fe8a8f1fda4cc6fb1488ad9e90bb4948f25712a750c6cf2fd47a84f20947f48ace430c30a86ea48bc94bcfcdb067c49364ddbac4d8d0792a2a20e26b5c41a6656746315e5396611388245ab1b3fb3443c6cdbb25887fdf6609f3cb6dc3409d4e13f359403d7b7ea1c1439e67c67c11017e4f130db1289eac3571890286b1dc846e87d8388c07076314ec67d30082b3829c42e6d4693d39b83c15ec883694b0e431dd95fcd9279ca83da7e248bda9c845dd768c199d69a45edf79ef025b301add01a5b993eb4038087ed1862394e8118630ddb867497271ff408f3f98289f2d1cec82ade0f16f0b47648d5ac8667c069b47ea79eec517919d42c56a8f1eef72b79007c3e8d9f4f39c892ee4f390ce428690d80da07e0eaa31bbd0e65541b2fddf7bcaeb40df507e0174e503980303cfa07b4cdc68515f196e9ff8ecd5477e7952a2328da00e8a525cdca8a8b01006fdb9416aaeabdf10e82d55418ca0469aa2838e376a288cf34aac36a08051a096294f1321a0bd34896123ed07c34ad78728125f80f882c1a4ad896af04dc2547955f2fce456fb73fee831a797b171721a173f0f9cccbcae7424b61df837c4f8708743cd4f9fe48e8fdbf3979402354564945951c666eea9459469ec1f5fb8a5c492f890a54ce1b7c2d1eb7540ce9d22805cac69e3d9c799395c264f14c04310910a179c9e2affa22a9c0f4782feaffe5fc2aa5c68acd8e803b8fe1d4d0b15e82e7a16a667c6873b9b39fb20d253ebf3fbb41a123f12a69b43d26a7e88179541cc7e377ac589aa864506e9469e0d85c64f3c6ea9ec00e3564419f4fc1d829911b6a9240245e85388baba578c5e44b46147b7aec7674a279812a6af607d09e93254c1d4c427007d9e2b35da6b975043690c7f6e40b4da73c98a791f405f1649d3b3031634f5b658155c80535269c8c91f65640b271bcbebf11e1d4dd193528421b54193ea4014deca7de9e99ab00007f4004e43a1149b1a0d167d5601706600a57ec6e03c9efbd1e97908326ae22d9757604b0bc0e4d4b76007b69dc971079ba4089e9ad2e464e462f521351cfa8463913e973e14d07135d9ceb1f55fa27424ea1233c0801f1027603db140e640be8a458ecf6965cfb5c6670acb1cc3f2209eca92084021fd4f49ebf8d568575f1b1f53c971ea8979f5ab0501bd7a0edd60f6da1bc2e6847fe490f817a443e0f2257c8fa62aebd992305daf63e097bfad3c0cd1316010a15b01528a3e7dd25a0bd656bd3101d5d4cf2b313b5a013b2d687f83f54ba6e33067582a98a740496b03b4b69d646dc956c00669994e9097083bf1b2b93f89a7083bf1b2b93f89a8efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce345100300f8043dc6b2afd95489faa27615afd95489faa276154ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b017320469057703d250c93b1494b1d1b944c7db1bce37e1ca892ea0974372df6835ee16f46eea191325dae6d2bd22866e792480290516cec361989cb66a8467879e8e8b774cd6d5c884ad45e7c7edaf059000000000000000000000000000000002152cd8903b11b2185099bc6c0509d5ba24671fc2a9bfb61e6409a08ed6d0a87588f23f0c52b39c31b7de1db253829220000000000000000000000000000000001c972d9cb553d1fbd95dbb0e54c251e68393572c1073221a9510c314f831ad78e41bef31300ec9a81c13e0bc7ff1666fe41febf95bfc1f3bf015400d4ffabff7fcfa33ac69856242a6d7a83838a4d122774dc74223a204d0ff065c5cc610a7c77272dd90ca68eb58e0e3cf0598de6f6e65ca49175e34dc050827efb363a2142358d75e08098e8ce74d83623862e5ca831e77ff6aa46859a5bea034d6e6479a81f6819963f5137fd08d8b11abd3883ec3ee7dcba2b8e9677b84c0a1c32e81d450b7de5bdd8ff866e266a4631edffb0050de6ec587cffd6275b487a6e66fee0167ef55705cdf726a072b067259bc9106122d0d5053e8375a7f0aad828b29c3694940b2b9d3d2a4eaff16e78d1191a745e70ff4abab4b92c9512f90c18f113391482cc5aa8978e8f8d8e917b9a25e9ecdee5f1603907667a184996a69132cb58abff148efb61946daffd86e2e0ad14ffcbefa43126c196f9938e795b0b4822d30be649804ff8f5c5524afd812ccabb694308eb8d378722e4d7396ce184b2f23ce794f029a2e1a6aa521290256f2b90aa4280ed060a30f4a9d28178304650b0a5c28c4553eb31d58752d9de466f5dd9b641f10cf00a8ef7ffcb9850904ce2c8ff932d2ff217307ffe0b3f489fa7507bf553b9f35a953464b64b11a57c1570bdfc11798068961031e97d4f7edb1c745960712c7100ca2c73a21937160386392771b3819715aa921518e88b1e97a6029aa858d3cd218e123b9c6cc796ece281a245f8731778348d74e7c92ba0486fdc305485325ffc0a07574da56197e64c31631d85ab1d4e1a59bacda3b0c722b86f1ca07ad46cf3080ecc605acfefa73e6299a578ab8597b6af35874cb09f24fecd7bb217d25400f3a167e1a5be4702a56dda298936f00f83fff924c07d8d6f95fcd8024170d80c16e8f113c711e8599a589d8b717c5575386c50d11a6751358bf534b9bbd13275ceb6771022bc5934a5ff4a73ee24014f7afba1ed137277037cf3c66e1ca997996d6cfc49439a821c2c936896096175571df42b0a1453795d78909de61639dab142488affde50ad8368982ec912890a343100fd8ae764eaa13ac6394d8f9080f74aae3ad05856a6cbe36426a2237c040af1505bfcd724c6f83c043ff31140bdaa9ddfba17c2cd652a09ab7826f6bd959980b14ec0781d6edd5ffe39155749e34c3ed526397c319a7a1d1232f6d3d8cbbe720f4381b353c63d6d3b3a813fccbacd83efa6f6c17e008bf3b01f0cf8e98a66da034ae81fd912bcab031712aebf94a300ea81c47020fd0ab3b6d78475a56d99e9741cf09477867c5c670c70c7721f2baeaec8ec6429c63ff89c91b7046fd48a0c1d02cbd189d6061aeae2ba33bde83906afaf3ef8fc3827763ab7e007308ad4fda023e412dadfea5b3f8871dd351d33a33ef01382e46bf88a71af9aa782e07e4a9332397756aad4beadf893df28f31ef756ccc305e16c719972528fb026dc60c49b5a9dd83cf45037f5df9fb91d68f5817f52ecb520f97416a166ef10d07e4017be8e5f75d9b6576768b0f999a217efa7d217d2266b08cba17b701ce72ba272efcb7175c66080b7e3983f5d8bccc2904d80206049834d3cb7904977830692da2d446256baa0935abeb05a59ea42a6426242973705c6cbd7780d06b50e287a044652de423bbafc4e3e59e09a468b50e17660ac5d4e3e9b84b383094629d7bbdcf6d5723331eb0c04fae8b1a25abc41e6a24c8739b3370647917395e717796ee6c108e9b04c87e6eff4e453be2e770a7327cfe1d396f033b875cb0ba4805da26e492e3b1f26dd0e7b610cb8f6ce9b794b20edbe8164495d6fa2867315d10dc7acc6d3ba70de3ce68a833993cff9ed93b86d7fc1b0cace37125e9433c01e53a94a14c8b14a9b7a46e8f15d2a7d416ae9ee93e3ab871ab8968ccc539d39f1281c881eab44dc9e1fddbb32e7e545544445b5fbb652d036f2d7cb44246157209fe8088e61cbe1f64b99a04134f9ff1b40b5b5efa6b53b87246e505da805a04eeec6eabff79e276e77424197cbb8dc2e619242fc9fea9b23529c75f73f9300a50aa330f4e9b7aaa1fc3dc8960906fb4a53607d4c1445c316cf38657b33598d40b17310170e1378eb6a3b117624b4bde1a3d6356836974434eb3f8e313743f84bdb05d384afd25e1855ff97653f5297e75c0d9f7ba8fcdb8dac9b45d6186f09f25425efc3614f4a79d0d538af58b953c0ead6ac35abbffacba920fc67b718e41563503691e9a2b324b0cde1b90ba4ac2764ea9bee2d115f913e423442d632bdbae5651372d413778af1b67b86dd21fc563227d4f30fcc20b46d5b9515f5f5c8f71771a0e8b39421323e5ceb6ed13f8fd308551603a4ee256002a5e3b8952ab5a1b45929616966654cbb96458f64d0f14f8a325ae1fcacef3b24ccb2850046f58583e0514f85afc00e6008acf7ea4767cd5566fd6d2f67291150a78a01b63e1a90873924433a70616fc31469a558086415cb4d2b077c57962e359fd718d0ccc3ccf07becfc6df70640e32c0756edd9de56a1d3c58a9ecaa0ff367e018de5a054392e1c1387ab0e20a64145ba9ca29585a22675ffce88d40f9cf1bb250b5c430939a5282d243909910844f3b14264eb5cbfade238cca327d3bcfdd1a9cca327d3bcfdd1a96fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f322045891dd53a5a883043751f6795400ac463751f6795400ac463032e05c110575053032e05c110575053032e05c110575053032e05c110575053032e05c110575053032e05c11057505301890683be6c9cb3106c325abe2dcc514b71ca8d779beb950804af8aca00668a3f63b1f18193dd3cbffb1c77b8f077675888c4d7eed6ccab808ff1c8015e1aa19bd968723447e7dee91b7c4e4bc0e7cb3200000000000000000000000000000000a9cdb8f28530c8e292ce6388bd820063f7ad035735beeb2486b55eeb9038c7ee81f71cc1714bbb1259f7f5a1f0ca2fe0000000000000000000000000000000000152d68696c9806b40e72946fd2f2efc06d7e7f31f6ca40b72dfadb5776df8066fec766159ff8fdae516f9b37d40eb0f930170d947ff8f26b801701517ea96ea781456f46a36bc5fddd3ea1a15d1a3db12bafb25bce6349a4b04703e81fdd194478c62a3cf670a433ed3b177add748d5b3c1dc45bee9fdd4ea4109e9336af1d26bc5405f6be999c4f25dc59aef673560a387653b8ddb75a17780e1b7fd76ee4f46bf6cfac365c4ea6237f9d85bca5e6bb47dd0ee828c97efee65b38794dd248d88bb990c6a4d0300e4173458e62317003ca06c694cfca100a45cf8e116ea6d047c81ca2da069011f6485894061e509d9bc9ec2c3a84a45ef2951525a9d0be58a2540de6fa21d9c7fcc7ac1491e40d7952f554a04d4c1e2184d51081ecc4e33ae1b373ad2942767c3c17c97bf111ad2574c62243d7cb8be6616aefeab650b37cf9cbef6b3c75381aa4930bfeb754c89a903503a723917c1a21930981f92a24773b34c29ddfe75f526e80e210cf83fb6105960e754c8c1fb746f9d53527a4fe2320c4b4940582c3064550b01c2693851bd554b074ee48c382d580b33223edc8b3c694b65efb207d3a7e007c58be43bc596243063d23fa4641f0050b6c0be7dc0db0030fc44377043020650e5e28211d80f2a2f4534947be86e2648e46d0d625b080df83d015eae7f3a5bc6b10892c67d997e67dc3cfe70703276ce06aaf31913613ba12fa6a9b685a79f634d8ba302a8945db31dcf781498108fe2cfa94d922874e928afa41f061c2d6216ca80dd2cc43baf9686850e3e5da2ca15aea665b78c708a90c28ec706d913c9eb51e77434ef8a7f6a3d5332728acc7ce3ad466022c9976933c1eea1f27f26e35f48876da87f0d3698fab2fe9b7d5e7a25dae4f6466f955801f741c0f20a166c05c1cd41a54c9af41d47a0cc8a1838b0c7f16198cfab88d16c9cad2ab2b2bcbaef46bf2ae4e2281b89f03a2b3d341ebeba939c2eb16dd33201754ecadbe8a2488a63f887e13483b7b6676a95cc66096d10e31269ed379f909567559caf985e9c024f231369da3c457c7f4352fc3d97c63eb50a2eaa714b9504b9dc368a1923d9039b91bb990946aa3e7eb7269733c034d04df5999343fc325089246558a70dd42f446d07e5d71973835cbb49814f5f3f8df76fa11d94ecb6215105e05f36b8cda1a3e9a784ff52df6af96935e3dd15a479b0910cf15b384eec85143523b6ea0f9703a0f360329427ce8d54f9b80bbe9ddec4c9eb0b12260d63f4e767d2cae5a882493a146b4e47e11d6b99c20144c5de2b9cba03bebe418247596651bfa2d58f84e240f3efd3c23a0558ef5a0f5be9bf864b8669191fe087e6065d249f8abb35837500c5bc6b0f47939da7b445e3e24689e01cfef6b964b13ef0d12c04c4c4c62406ee591dace039a9e8273c5ad540b85362fa92bea88fd10f43af97f03ac1a8061b0b4285951a1accf33ba3f27015a2e1cb524f917a49c7b8a2811917c23aa2942f373aa4e5a8edb1551110f74d3018f4ae26b1d1ef19c6bd13bab2395e5b705d0324a0f5aa74e3fe5d73f1be4d3854febc3a146997e550a3cf8654b8cba36bacb4080b259bc4265303de4f881765cecff7f483281286b6dac6559cffab3a452d27a1fe94356997d143c2e21547315f04d8d4bca4269cb6ca87a13c94285f49c7143e65234e4fcfc877a17ece5b1a0e6c59e9f2a77423f5b52d3e6a8aee03b29275f9e27b227eb2923a66d6507c9aa472f0b6c9aa2db217273e474783ce23daeb8381ba6d4f28b56e1d16bda0988e379f3835f7a3ddbfdc03b7e1fa2656acfa40021b85e002eeb0cd33846d3b559935a50a97851f239c92dfb258bc1b25e2a9618bc9f116dfc6e5da290d5705a9eb3841da21be2442eb870d13540bcdbf663a927789d0d341a49960dc718c1011c9304eae6fcaa4d700910834db4248a1a18db068b6595979a6552de12e5dfad4cf978c87c79d5450e7dd7c523a8a9d73a9444182b6f6f8609f2e19ace476ad95d648d0bc6b722dd9047cd1ca6c6854c631c832d4009f5d1f4d2b5fb69d7f2587c283e53255b0718889979cda04f73056b1ad1b2a30aacafa9fbf9aa664c4077a724e8052fab5f2ed67db566ba4e222bc3b6a73651c3f80160dfc789b08d567e64f8486c7b1ddb52292221774c18d20d4d4dc35e996dc2fe7bd484c93ead10e3d78e648832079d1f4f7918312d99dd362f4703bb3a12088735a0a98a450ebae20c5154457e7cd3cac9e3d9b5a0c4dbe504bb4dee3d96254dc9f6abc4d2209015b6b20f2aae046ae999a3f93c9ae3f9844bb9ad3ec3925f55a65a57a137a5b46ec551d4b5fc2fec855d872c0cafd111a38e4d83590998c927591aa90f0f12a9b28f72702b0bda7e7f6cd1459e2566cfcb5b620c17b3152541a4eeb5dce0eca35c3d810686739f842f2ff84d35178cc02e8b4a3e5e141fbdbe61e0935ba52bd7edd74be9ec356e7a49884b73635eff46c706d6ee33b7cfd71714359e506863193b61979d323b6766065104d9d91f3d32e28b744e45034ccadc361f9975623dcdc5eacfee20163912a80850f0839d014c5d43e70d47c6d5486cf582819bc2c0aeec2d8995f04d54b1d69687daceaae956f8416fcde6d844a05406c8a21a51d67f8cf8336cb4e1d67f8cf8336cb4e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167e41c16c3a31a7167ebcaf8024aadcd8ff50b08f590311a4fc50b08f590311a4fcc0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a2c0dccaad26d022a201b2bae4d8acd6dfce8fd47016987e93b6bf9ffb102fb0e4eab3f2eb451ea682e810c752931cef361c43087d78fa305eb859895ba642655ef1ba6ce2acbf3e0246b50cfce482bf93f43a0aceacbde24aeb000000000000000000000000000000009f52d90631358e8f6cdb52b75c37fd08c71bf7b21e94e6e7cd2f644a93d18e94af486223b4745a4c081d73d9052b4b740000000000000000000000000000000001591f586ef31ff7be0f516fbe368ef5fac22dffa2895525d57930b547686301d4a61489aabafd0680c4008cd54c0249406a15004042ffffbf6a15f97f0600906aa6a4fcb331896381822f02508b6701e6ac303e40b17097040702ff1072674981af12afe58510ac23c982c947a973b4f9799382f6a629efd34a0892bd95238acb013afe2e1df9c6900496f348cfcf70f5161aa9feb0ae15b64bb9ba9f914961cab3cb145ac6205e4ce39191766ee5921635fdfa3d0546049e6fecdcb128ea1d5296f762f31bba990c1ac5b4a7c3163458b463f1955b9f6c69eab99919835bf8e1601534b39b80ca2d9f956ce6428489405817f84cd59dc2c363a3c81ada50525a1e3a65e929b15c8de2456720d079c1492ce9d2e2b2544a043460c433e450081e6ca15e6a3d363ad2ef6996e8b27b97bf84e51c5ce961243d9b46ca8462adfeab39ee87a1b5bdf6b38b83b76afb2fbfebc79804ebe54f3a726e2d206d4c2f981f023ee1fb164b29dd08b228e3a60d210c38de1c36905fe7548613ca7af39c5352a888865baa4a494097bcad80a90a01c21c28c084a74a074ec21841a1960a33224eadc7681e4a65ef1cbd75ddda06c58bc12b380eff2f63d242328963fe4fb6c0c95fc0b8f92ffc447e9e420dd54fe5e26c55d25cd92e4534f355c089f247e46da25942c4a4f73d016e73d05d81c5b1080228b3908966dc3c0d18e6f4c3cd06aa57a84ab25fa02fa65d9a0ae0a1624d8b88384a2070b21dcfb38b07e215e1cfa9e1d1342e9d27afa423bd71435015ca80f22b1cd8349586859b33c5e87414aea63969645d368fc28e8ce0be8d7fea51e7ce2338e082693d53a0fa882196e2ad465fdabeea1b32c1ee93f8376bc95e488702cc87ee8497fab20a94b685a624dae4400cfea79300f741bf55f2970a04c1cd3458a0274f1c47a0686962152ec6f161d6e1b095446b9cadd62cd617e4ee46bfd539dba64188f03ad294fe8fccb9939c016ec3284dedd01c6f6e91bca62147ea581eea0a665080fa2fda6e2f5a6a2c28132f1762edef57d3829df40051facc26d20f1b9746fedf5f4328f92739bd136e1b48957a69f2bf0bb7e9f3d7ed03a6abcbc3d4a17f552c405ae9c3baed2e11fde87cdaf5e8314372bd6e78d16696316b6ea2844b76d9aaebc91dc6ed55f7a21959018ad1b6c29a8ec7707706ebc5e48dbdab5a703aabdd56ff822f37be624ae3be7c6c80880b939dcef67fcad1b64d0178fa8b19378dbb583918fc3dbce612c132967d690da05dc0c5d7ae5d35dfa388fcfcb845e94582b6eaa805b9dd22aa684bfa6a6a949096a585da5e7e0cae78d2f0b4fff153c9174569ed0e0496af5b4f14a6fb3f7fb5f6edb2da6374a1cfead7841287fc7ea0251e685324d0f5cc841801a5aae2c0605a413db23240c08ebd73d68a2e393a73f119fd68d8f5af7ebe887ea542e571f8e4076ef1cbca7c79bb5f97921d6eb4512a7eef93919f92164eada375e08f36a0563e85b179cf503bab802d35541270ef1142be1eed31fea6f3fa7ca34775cf3346b6aa6edb2e80743c1ac8dc7d787dc7bfb25587f2f653f916f0bfe82f034f71571a766c79ae1ac7e69c56303bb6841c619fb1bfc6f492031e71ecf028784321f80bf585b796ba43a1920ebff9485d2aeaa2c56188644a7ce10e7500eda67b5a09247a1d4017eb3e1eaea115405e88c3f079a609fc1552b68907a128f2afb62c33cf19567dd3c33ead608d58625d21a24f88cfc406709a2687194114a078148796967454434b72e05cef20076d2bc75f5ed42c6abd6092349b5c86f914418a0900047fba1bc834c39fddfb259dae4776c6d5c0cb9058b117d026a18f824a129d43820c23b5631e6b145f043e9b9c2d39cd89109e7da69c7a951ef1c0dcfe8b7debb791527b23304c8bb291faa6cdc030ff14b2b823e9ae697e6ada04f4681501c5c3960860f11f1ed1dac603dbc05cf6ea869a4eae03282e5faa93e9ac5ef6b285d51a1cb0085f014fd8a8f4b573d91640e0bf43b711e4b9a4eb64847a501c23056fffb6c1bc0f0a8167efa153d0b6fe38ac0f8e953528168309eccc6ee14dff9419ed196993ca9b58d92b224f53e3455411c29f49a90a20642bc63625f4d4876f93920b9d24acfbcac1653388ae93eb99f23f289fd59a1bc87695bbe51c880f25689b0a667e2396270d89b225725d6417e3b06afdec2a0c65d895d6f8dfd662ac3899618614c5cfb4b11ec179ea0833c648f773e1bf8d6ac2405429fb9d0869f3284a416dd4d101671a7f13d05003ef74f6503227eb80e4b2d81bb884dc074b5d79e71b9d3058dec499b43d8ffeb9f155466019b3e590f0ebb5d33fcd22a2cbf6605cf01141a495106192003ecd349fda5469237936c4ee574cf62d1445907f52998ee8efa249d8a0b6e088a9e92a0588b1d34f9fd89c11f5fe12a53badcf51f5b45431957cd51d2f6ed80faa37a222fff2b1d947295451759428eb7c13ba10af39414596dfab1497b9a7ef1a1bd1a79272f6ff3e467332ac05ec1d2246725b2c573ed60cde91ca4f02eb88a3b5a343390a2d01e0e10a78396c8238d5f8d3bedccef9770050c2d99b7bfe10d3fbd3a441d46238a4300606b9511238c642b15e93dbd0977d1c646abc21b53ff1f924f8540822e6f1f924f8540822e68822cfda89b1a6f18822cfda89b1a6f18822cfda89b1a6f18822cfda89b1a6f18822cfda89b1a6f18822cfda89b1a6f1a305f551893cd9b1d696ff2dbc4d6041d696ff2dbc4d60414fb37e2721601f884fb37e2721601f884fb37e2721601f884fb37e2721601f884fb37e2721601f884fb37e2721601f8801cbdd020ee721f279967ba9041c31b34a38bad510b643775f8c47839260b16cc9c724b9edb228045341fbf86ad2d8c2f72d3a8425b70310c4f0c9394284a301d25be4028255996c58620cb03abf20cfb00000000000000000000000000000000006affd793f9642ada80a37b502ef975a9b0debc7e5b96d7e0c798caac558e61effbae86190f4ba6ac30eae1087495bba000000000000000000000000000000000136185bde86a7b6d73ec2a0ee1a3e11ce04e1ed8b1985bed76d95de43fdb3ee3a41bef31300ec9a81c13e0bc7ff1666fe41febf95bfc1f3bf015400d4ffabff7fcfa33ac69856242a6d7a83838a4d122774dc74223a204d0ff065c5cc610a7c77272dd90ca68eb58e0e3cf0598de6f6e65ca49175e34dc050827efb363a2142358d75e08098e8ce74d83623862e5ca831e77ff6aa46859a5bea034d6e6479a81f6819963f5137fd08d8b11abd3883ec3ee7dcba2b8e9677b84c0a1c32e81d450b7de5bdd8ff866e266a4631edffb0050de6ec587cffd6275b487a6e66fee0167ef55705cdf726a072b067259bc9106122d0d5053e8375a7f0aad828b29c3694940b2b9d3d2a4eaff16e78d1191a745e70ff4abab4b92c9512f90c18f113391482cc5aa8978e8f8d8e917b9a25e9ecdee5f1603907667a184996a69132cb58abff148efb61946daffd86e2e0ad14ffcbefa43126c196f9938e795b0b4822d30be649804ff8f5c5524afd812ccabb694308eb8d378722e4d7396ce184b2f23ce794f029a2e1a6aa521290256f2b90aa4280ed060a30f4a9d28178304650b0a5c28c4553eb31d58752d9de466f5dd9b641f10cf00a8ef7ffcb9850904ce2c8ff932d2ff217307ffe0b3f489fa7507bf553b9f35a953464b64b11a57c1570bdfc11798068961031e97d4f7edb1c745960712c7100ca2c73a21937160386392771b3819715aa921518e88b1e97a6029aa858d3cd218e123b9c6cc796ece281a245f8731778348d74e7c92ba0486fdc305485325ffc0a07574da56197e64c31631d85ab1d4e1a59bacda3b0c722b86f1ca07ad46cf3080ecc605acfefa73e6299a578ab8597b6af35874cb09f24fecd7bb217d25400f3a167e1a5be4702a56dda298936f00f83fff924c07d8d6f95fcd8024170d80c16e8f113c711e8599a589d8b717c5575386c50d11a6751358bf534b9bbd13275ceb6771022bc5934a5ff4a73ee24014f7afba1ed137277037cf3c66e1ca997996d6cfc49439a821c2c936896096175571df42b0a1453795d78909de61639dab142488affde50ad8368982ec912890a343100fd8ae764eaa13ac6394d8f9080f74aae3ad05856a6cbe36426a2237c040af1505bfcd724c6f83c043ff31140bdaa9ddfba17c2cd652a09ab7826f6bd959980b14ec0781d6edd5ffe39155749e34c3ed526397c319a7a1d1232f6d3d8cbbe720f4381b353c63d6d3b3a813fccbacd83efa6f6c17e008bf3b01f0cf8e98a66da034ae81fd912bcab031712aebf94a300ea81c47020fd0ab3b6d78475a56d99e9741cf09477867c5c670c70c7721f2baeaec8ec6429c63ff89c91b7046fd48a0c1d02cbd189d6061aeae2ba33bde83906afaf3ef8fc3827763ab7e007308ad4fda023e412dadfea5b3f8871dd351d33a33ef01382e46bf88a71af9aa782e07e4a9332397756aad4beadf893df28f31ef756ccc305e16c719972528fb026dc60c49b5a9dd83cf45037f5df9fb91d68f5817f52ecb520f97416a166ef10d07e4017be8e5f75d9b6576768b0f999a217efa7d217d2266b08cba17b701ce72ba272efcb7175c66080b7e3983f5d8bccc2904d80206049834d3cb7904977830692da2d446256baa0935abeb05a59ea42a6426242973705c6cbd7780d06b50e287a044652de423bbafc4e3e59e09a468b50e17660ac5d4e3e9b84b383094629d7bbdcf6d5723331eb0c04fae8b1a25abc41e6a24c8739b3370647917395e717796ee6c108e9b04c87e6eff4e453be2e770a7327cfe1d396f033b875cb0ba4805da26e492e3b1f26dd0e7b610cb8f6ce9b794b20edbe8164495d6fa2867315d10dc7acc6d3ba70de3ce68a833993cff9ed93b86d7fc1b0cace37125e9433c01e53a94a14c8b14a9b7a46e8f15d2a7d416ae9ee93e3ab871ab8968ccc539d39f1281c881eab44dc9e1fddbb32e7e545544445b5fbb652d036f2d7cb44246157209fe8088e61cbe1f64b99a04134f9ff1b40b5b5efa6b53b87246e505da805a04eeec6eabff79e276e77424197cbb8dc2e619242fc9fea9b23529c75f73f9300a50aa330f4e9b7aaa1fc3dc8960906fb4a53607d4c1445c316cf38657b33598d40b17310170e1378eb6a3b117624b4bde1a3d6356836974434eb3f8e313743f84bdb05d384afd25e1855ff97653f5297e75c0d9f7ba8fcdb8dac9b45d6186f09f25425efc3614f4a79d0d538af58b953c0ead6ac35abbffacba920fc67b718e41563503691e9a2b324b0cde1b90ba4ac2764ea9bee2d115f913e423442d632bdbae5651372d413778af1b67b86dd21fc563227d4f30fcc20b46d5b9515f5f5c8f71771a0e8b39421323e5ceb6ed13f8fd308551603a4ee256002a5e3b8952ab5a1b45929616966654cbb96458f64d0f14f8a325ae1fcacef3b24ccb2850046f58583e0514f85afc00e6008acf7ea4767cd5566fd6d2f67291150a78a01b63e1a90873924433a70616fc31469a558086415cb4d2b077c57962e359fd718d0ccc3ccf07becfc6df70640e32c0756edd9de56a1d3c58a9ecaa0ff367e018de5a054392e1c1387ab0e20a64145ba9ca29585a22675ffce88d40f9cf1bb250b5c430939a5282d243909910844f3b14264eb5cbfade238cca327d3bcfdd1a9cca327d3bcfdd1a96fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f3220456fc45c7c6f322045891dd53a5a883043751f6795400ac463751f6795400ac463032e05c110575053032e05c110575053032e05c110575053032e05c110575053032e05c110575053032e05c11057505301890683be6c9cb3106c325abe2dcc514b71ca8d779beb950804af8aca00668a3f63b1f18193dd3cbffb1c77b8f077675888c4d7eed6ccab808ff1c8015e1aa19bd968723447e7dee91b7c4e4bc0e7cb3200000000000000000000000000000000a9cdb8f28530c8e292ce6388bd820063f7ad035735beeb2486b55eeb9038c7ee81f71cc1714bbb1259f7f5a1f0ca2fe0000000000000000000000000000000000152d68696c9806b40e72946fd2f2efc06d7e7f31f6ca40b72dfadb5776df8066f8a00acfff2ffffffcfff53000d00fc00640000000000000059000000ffffabffe123f2009f4c07ce01e59b065f18c3183f35432e99aa95d56fd1d34332aab752b2caedfffcffb75cdc8a80ffecff078901cc83fc7dff37bf02949ae776fc873a0d0c3a5541e7b79957549654cd520734604e1c509e43336c1d31939f808077dcb8f74d4334ad5e6106c621d76fbc96a9266aece112271fa306e7762d8811da75876e268393d30000b1050d9608c90500d7275b1a3c7f2800e1167eb8a47a1b0127a0720b815ac007116122508779423676a7f030b452d17b37949456f042b962180088a388dabe2d755e709212d075e52d95120188b038463a148207b9d38ceb908d8e3415cad970eddee56f9786f4157b18490f24aeaf9959abff6a00c3cd336eaffdec0355a06a00ccef7a1d5362eafa938e5cd445b068d40be687d0e8d1dcc6524ab7b95dbd096a43080314902d44e5d739158df03edd3de79494e193b80cab5212102b0b0c59ab4280702f4e546faad281134f234e0ba6c28c8829f7224f8852d9bb24c2f429b741f122024fb12500cc98f40f29d90700942db06f1ff036ff0b3fd10edc9080f653b9b86a04f683b74b110dee1eba9bfd11795b86d81642ea7d4f80adeb9fce61712c82c3715fa6a319378f5c1c9c8c72b381ea8ac644d819e88b69d16de1e9a958d3e2bf002a659d6cc733410526c446f8736acd240a5de8c92be99f01478b55853260620bf1ce4ea561a1b54f97a81e85ab69fb2d239ccea3b0e3e341f644a17ad4393ccdbbe2615acf94ab9c2233a678ab11b248f265884cb07be0fc9fc9b317d26128ea5f83e2a5beac1d679f972a8936b9d3d15b2525c07d10cbbc8205034170738d29932614c71128df22060e8c717c181af42a62d21a67abb8ac2cafbabbd1af10b938ca1122bcce790f8d8774ee24a7576cdbb401caf83ec61c01fc7a16545731f80ee141bfea65d9e18795a2ab0f6850b7e3ca372d0594093d0524025d3bd9e53d7acf8020e4599ecf14ec62c77f0a2a1845b2ccf06459a630759fe390cf77acc98894c6299a8cb2325c910036abc5fabc8bf5616f219aa6e729958edee9ac542ea7879eaed9cdb69c62787848b1897d8f84b0730fa6976c9996e7026b276f8893c0a43ebf26706176efc7b0b69099e1be7cf3567fb399450749546644759475c8d69196cc6e81253c939ade6242d2b4b2bc7980f02596926c9c1ba74b17356afc9189f3775b738789fea4254e4d472d8051970f722cbd4b52c3efbbfa970d23bffb2e55fb68d96c39ad094091676ced49e02885eca217c0dc70b39c7bc5e92ebb2dc9ba7e88377dbccfcd58263967f1419dcc4ef6001ed971f0d8a4da0a1cf6c3470063d9bf8febb2022ced81b81c822b4ceeb8ad619070a641b3be5a9422ac992769eae9bbc4adc8c29a3dfaac0f076547ba97bc3b638687d1e5d0b857f10689caf83a4987af654de4d0873ec873656d28b39dbe6fe1f792b327e56be2c02653330082b7c74e42524f07e875e906ae95dd392e7cf7a7908eea26ca3eece8eb15507f0990ac7c253e7435874d84e2a2a7d2c98d1e809cd2eca781de8f8d00ef414a672be32c081d41bf40297885220611acaf698baaf8f1e9e94d5a9af97e80dd48ac8f0c5f31024c52337aa4b3be1215905ff4ab6ff0cef29dfec17e3344034d269b1a04d07b0c3640c270f4eeb3faa9f1d553bf35334897c643348bc4f2623dcf54173ddcd2dcb6076e30df213b40f2f59f2787b57f7b7fcb7c0752ca2131ae27aedbf5dd19139b868fde8a7ae5fda3145905229d1f3e8bb20182c437351b914bdfbea78f9a374c73e7c95116d9bfc77b39e27188095e0d6e19237cb1fa409ae0c51284f3f042c81c9d6844ab95e4cebc688e35172863260a97ada3ac3c522474738d866432b93e06734631c2c90e48b2b44f111033ac98fe908c939ae3c3e9a44ee108b0de4d02a1381af722195c07bff09db834093ed9e94b668fdabcf2bf69f72208f922693ccbd30a8186db156704a8389e536eb7886dbb3a3d6f26d6aa9060a487f9498526288d9b2a49c8c34a48c9ae4ed1e9d13ce8580e78186e4551da0045364b51d049417361ce0eb41f45a8a61258b717587761da69c682e6271d2b7ae74daab7bd370fc079b593864ca0427127134303557cc059d4d3f30fc77ec31a0c5f4e68d27ceb6545058840be7b35e0d3423c30b709ad9800ea1922ec271875127b950ac23053f11fddb6776be425fb67dd2fa1e1f7996afba028d54b54f956e9c7ea6f11b85d6ae8a0af29a8fe7e16cd235173daf93705643951288e28a8209ac4a9980fed7251e538c34af57ce1619e5f2691b4677defe43f5ded0635a2a9d08cff3f834c3487d8d780d7eaa8ab7a5c56e949363b03f843669c2c0444ee6eebafbbd94d1eb48f9ff9ebfed6427b869efbc5a33823f722fcd6c4009b4a7e20f0843c52bb867de3dea112ea9ff7c609e01df0f3560f114ed495076e88bf0874409aeca11c55508ac9012601ece548f2f49e8f6f25959992fd7bab111bc9e5d17fe4547b0771c1ab9a0ecbfa9aadb83c26b27d4cc51e7416459d5f0ddc74b3140e0ecce19b8ed06ef285cce19b8ed06ef28521ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a4fba51d0846641ffade719e3fe0d715f3de719e3fe0d715f3628941320cf96666628941320cf96666628941320cf96666628941320cf96666628941320cf96666628941320cf966660168c58acc51a2f54af74786b5706171272dfc2dc41437b4a847295ca2c362419cbbb2c0ee04b53aa2c4a2a281acf0d5db6362d2d09e2813fa00792a260eb93a8edc6d523435febd33834f10953d8fa6f600000000000000000000000000000000088583e2630476c899e45c501085a2682a01206a2b071b92b951bec3b8c7d02c00051d404e0663043a4868cd0bbba6d8000000000000000000000000000000000152477172079dc45dfdf2c7135ae24b7069b65c8a2fe26148802480181c2281c77110cac1a83aff7ffceb8cbf5bc9b0fe71c45741ffffffbfc8015781a8fe6f80e4730af65c767305729e3069c49f5c5a8061d25e60ab9a1fb99e08ce7457f6f576335f9a9e54117637689a385950793a80d9388c713251997cf28dd51e61383163a1e1d6d8a78a58b3692be0ef96ca6be3e32f2191208af248084a6d7646b9e7ac4342be93505daeb0d9cf330e348dc4cbf3ae6a686cdc5f8baac8eadcf6069fc0a19bc9e0e43400406c418325427201c0f5c99606cf1f0a40b8851f2ea9de46bf09a8dc43a016f033449808e1619e9062dd293c2aad54f4a80d25a52dbc50ae9fd6623c13cf2fb0079d179cdd04745d2f4ba54411222c8e460e85e07bee34e3e463a323698572363bbb77f9e0a5217d9a1e46d22989eb6b34d6eabf26c070f366db6b3f154115a8c6fff2bb98c7949866fea4233175112cc9f482f958347a347eb194d26f6e576f6fda10c21105640b09f9754e7c23bc4f3dcf392568f8242eaaaa9404daca0243a5aa1020f78b13d57eaa74e0c6d388d36da9302375cabdc8f6a154f63889307db76d50bc91c0536cffff3226fe434af6f3ff640bf9db07bca0ffc24fd403372460fd542ece1a81fd9aed5244a9bb87ee307f44dea621b6854d7adf1393ebfaa717581c8b0971dc979d68c6cd461707274adc6ca0f0a231110606fa6294745b78272ad6b41130804a0f27dbf17d5081096911fe9c71338942de79f24a1c68c0d10d55a10ccbd842bc5653695892edd3255947e16a017fcb086ff328ec0979903d08a81e75464ff3ae3498d633f12aa7c86729de6a9d2c927cce2113ec5038ff67a0ec8574388afad75b78a92f90c7d9e7774aa24df774f4563f09701fc532af60b74010dc6563caa4fdc47104cdb78881e8621c1f9e06bd8a55b4c6d9552e2bcb4eee6ef45d442e8e1f8408af94de43e3d39c3bc91616db3601343b5ff450876dfea12874acc05a123780b37403a7d6dd5276259dc0b51fa21c31c9b838aac8e82fb50b3e65d21d36caaa85b25490a87145e7b09ed4b9a704b352213ca55c341612ae6ddb15e08ed69d829d2b4107dbc10c1b7838bf3b96bad83b8a63f268726f0dab3aae28c2e1f74dfb45fdcd731b7d5548af1efdaa846f253bb35cb5920654d8bb30be17106b2b9ff149a18d2ee82c5e57688d584dd62474bcfbdc4cc5b9de947d5bd47133d49588948e1ffb385c87287c0aa0546af0161bb2c561ccde61136b50b0ba2b24617fb492cc523c9cc5d3d370adb85fb6465a6486cb7c43bc291395e7c1fdae13a03d96cf4d3895351e256bc55c2377eddb5aba631f0a18796ee6ae22d698ca4351543886dfa48f20aaa6c1234c178801178c35eb6d6001e0fb3ac81355daf3a8222987fc7235dfe890786a22cbeee59da5505408d7a1314d36b943e3e4f7397e5dd3b1aae104524dbce6fe105de6e7bdc7cb3926eb231d5972dc97016fdde5e58dfbe81ebf3f93541b9f791173ba6a870dc74efddcd2ff62f8653374da139d04900252825956ebd05b38a6413d716d34074015c0d1203b28a317fddd91142d60ca79e51c71b9d0fa37053d2b5431f19587cc3d053b4bfc439d4e27934281581649412b4e75612be304cb7fdd3a8227253c7983c48f237a6be2e967a09d7ac7ba535144c852c105c28c3ae7744a2f156e651b8b148c8fbd0d1d04a8f9d3c0446c6238151ea6fcbbd15f99e0b65501d785ab7b7aa14787340e7bf49870f37f82b356e3a1c4a1805d4f773e19ce5e5e5881de6aeafbea63bf920def9464d260e215e64533c44fd5cbbbed7fac50cd4dc8b0132d17b1ca44017908556b717812ce4c513dfb1c27a72c67d03711ce4366c0ff42c4bffb54b079166eb04bfbf73a67cbfe5ad222e541f13776fc2db77278e7ec203985191db35a1722ce03f4765342a7a7e65fa5d5added32b40b5e9698bf2b0a3ae14b1139b6c7146ebd55a6a8b31941142e092d5ee9b720710068b7ad473aeea0f004e8e0f4eda9cf75ef59121d149a2b6522d0beefd6fc5272ec4d02a68390c14c876b65742dfa0fa9912a3fa0121ac2459c92b20070dea85657ba8b2ca23cf679275ae78003bd08055210177762bc1c58efca69e7480d17c801172c1a0b5c81ea76c58b684e5a923674cae15487400bd586274a585e2593e031151e003d6d489c08338bd1ced8d3f6b241c953769a1423233cdf5d41f428e69de32135aa6d371702879306160c07bcddb4d153f79507cb7d331ab62f64c97fcf0f5c6f5d0b731473b3fcb28a287c8cb0cb0456d7c86b6f5872422ae09c73d90d2273d11134d53038459a4660310cc39dd9cd1b53f2be74994c06d5f88ff51ca2e051bb8d68384bf1f08ccfb11c9030ee309d28a38f51c79f6b1379b15910db82f1b0b4ec71a799be827e26f0ecf281fa97e2ad1eba4b414b58c26a22c5b7ce86ee0bc5c7ded0cc1aa6d5f299deef1108388d03af599718e464f047e634b70810be3713f452994ddb5cfd8f3fac1df2b881da4f87b25d857cb6e659b7a2a5cd3010fa05fadf5333923ebcce40e687eb438d64599a9b9f05d90fcf3452c2b9124f93307816be28e6b4bc966ed210a272976750427f28ce4cd2f9c3ce5e95f44977e9c3ce5e95f44977e8657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d28657799d6157f2d2ef5e4e83558e4a375316c72b3f75b4dd5316c72b3f75b4dd677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a499677a87550d26a49901616453998ac66619c5003dbd391ede2dd2c2d4d7dac101a9456724b2963ac06e87a32b103dd172469cd9af55264bfc6b04247bb8e6dd8cbb4d5013352fa06c6c009d612a4686180e1d16ee95f7a3835c000000000000000000000000000000005725cb126e770126add01a246503a64795df903ec2938838c8dcc0f61ca53b3e85d00a5f110f50003351933b8c58af26000000000000000000000000000000000104feee141ed458082582019d8880ef43dbbfa5eba680bbb8b506e43d97a66b8a351607c0e2ec6f9591a9ffbf0813000001000740c30070d56a15078006007095379e3d9bc759d26f7493c89cd38c80e0fd3c66ba7fbf799928777f49a4ebff36e2041ea64b7bd99b2a22d28a155ff24225efbecb9799a0d4fe8938922b3364d0edc58bff3566bdb27769d2fc7dcb2de33be2c0e977904036782af369edccac603433e4291fab04b068663d25dead20d0d3ccad0418c1e4b0c199c020ac4741d6fdff1bba9c0c4e4ee9ffc3164b5822245fff5b9f0d6af0fc93fb835b65e692eaffe09b80cb4c046af726438492191ee6bb10d69d07b3d24a1b75da5037e5c20bac56c9b50e9949f3296ad07998df4d401ee7b2542c1d21c2cd51e4503bcce74e993c3e36a19556282ea8b37b69175e1a4299e961e2a392b8c93063ad357b020c7f55b6bd775e11547756fc2f4895794c3f5de64ffb145317b98c4c2fdf9245a30bd9174b1e04e7764aefa60dd71c5140058b905fe2c937c21ecdf39c3585864fd09baa4a79a4ad2caf42aa0a527fbf38c8d2a74a3f7b3c8d75c3960abe5ea7dc2d581f4a389793083b69db068a22093c9ce0ff2fc7f13fa44024ff4f769cbf7dbdfdf92f3f473d7028f0d54fbdf2ac111891d92e2da3ba7ba5f7f2473f761a6281c5a4f7bc3bb9ae836681c52ea210c790cd89664c6f7471ed9ec4cd190b2f1a7b5860a0b44d49b7586ba262f51f010368ef70b2b5df0715d88b16e1f71d3793e4d29d27cbd181063cc450158ebc8c2da35d3595e327d93e748f75143a17f0b727ec368f9ba29007117580ea4072f43476338369c71faf72376896e276dec92281d91c32401585f381f2c95ec794a3a883a185977711799c916aa724497a4f47f6e9930001582cf3b4650b040d6836a6e8c74f1c5fd87c8b55772ec69cea69d04e43456b4e6ae5b21ed7e4ee28e845e4cce141882459e93d932ccdb9007061b1015a4e2619b595be95f59ff11cdde8f22283e33790d4fd25cbf78e5560f446405aea04e52d6c2950e2dd8712b2cb3e3f62263993167e4e04931fd1b81a8a438e283653f9c027e802dc91c29e5b7aa9e045fa53aa7b194d11e171c2482d763d783098dff7406fc92530746589fb095532602dc06b6ba6ffe4c37cdc9c1359944e858b8815aa750b2a8df275006a53f9700060eb447268ad8c0319f5caff1fabc4750a7da8cd52fd7f33783d2878c154805e1f4d2b7b12020c869284e788b5e249d6a8375b8f19221126a710f8415e117affdf74168d733803cdb72c6a1641bbec02990f060669d1b4d958080f6e67924c4ba3f631a8522198f2a4610d474b3c6ce4e63912ad89cedd06c2e1f55355848b97b33b7fa71e9e26203fa393c1f25afb7aec4d068c176f74e3b5109c4abecc21de491168219e5f7920edd6c645800eaec479280e67a680747add36683eba53df6c07268b6d6e8f750991e038ec331097633575693e0c06d66656f8cb7fc95abfdf2bfb0380e65f5f2f572b359c4b2d6ec1b940ce4b173a9c5c1ac56c12698dd507b336e01e11608faa18d2920af8e2fcb5420ecfb6dfcf43b4da5b5e89f461b44b71fd2bd26cd6eafc8d3087c640076d3c8a55599d91eebff75d7179511e38507bb975fea4b01014fae4df929a3d1863d5893a6b005516029063ee023da67ebb162d1dd900757fbff84d76df541ca1f575a30e60bb93bb390d879fc45a6e1b8847850af09db3403a636f1120b9cebf35c56711508162161ddb1dc6574d9ac341b83be3e7b9f26be31dd31253869cbf59e2b4af40ab2ba63fbe010b3e06818edf6179a853932ab7c3e7e5428987e662be4e783e4c45b213f2d737152bbe14ea5c27ce25fc45a361e84b5574fa206e738c790b8f882a8b7d1d22478e1b5cf7e051d4f2f17fd10c266fe616b525c1890f08acddb066d115a3041356e95521028f713b8e8fe1ef44f37dc86c44f675e616713d47478d8d857c35c76420c08b04f1f7c8d2c42e9f1e2a04e3fafee001e58165488813587cae601b4bcc0a655b959d76ae4f29c7cdfdd4ebede99b67712509126464b1230ac3da24c466eb085e30143881d18fa7a3722ca6444007345a8fae3d53b894988e2dae4c88e03ee71f64fb0d377975f0468c4335d184023924d3ad11e42d904d94054c1b8137d33728d5ae4378996fdfaee5c43912d522b349c75699f6e09746762cca7380b521d624c6c850373c1415a055c8cd09a12caf674bce277c70c9c99af45a17053e52895d34db42c1ac864b32d4847207f9da4203041e0a4fe91991c9e3cd18aa3c1c347b29934b85f5dbfe762f7d91dd9e2f7a34257489e85ba7401518b0559ddcb91e4baed9a969bcfa444410210a858c853fa2e94ff29ed604b17f2fb759b28fb1bd773eedd9cdd09d7db97a6b3ef74d26d8a4b78bb7719632bcf93472a7fb688b0bb7001f0e4255cfb4006abf0fcfab125369d720eefd13ea24ea753babcfbff09df70b57d12340b10e208fe551d57c3bfd35278a5a7e50a29f173a6091b14c2720759d53d03304217f654aa5c274016c88e458161062a648f8c17c92eff91af20115a6fd5e83da8fdf1085b7fdf478e7722d14cb922a440d9e0a46b3415e68366f38271eac2d9f705c7ca650eb6a21705c7ca650eb6a21a4300606b9511238a4300606b9511238a4300606b9511238a4300606b9511238a4300606b9511238a4300606b9511238837201d8044a5c610f880cf2f43b29360f880cf2f43b2936c642b15e93dbd097c642b15e93dbd097c642b15e93dbd097c642b15e93dbd097c642b15e93dbd097c642b15e93dbd09701366c14af0a9c0ad3e63647c88d77a4bc55d8893303190e0fe377a6fdf40e40122446de5cb421c9374b315bbddbca4afc8b3bbf0591a5b684894b8396d874a4055445ca2cbd2bbcb310e6270ab5377e3c000000000000000000000000000000005eea4f1a395863ac094a9147cb966e4e03942ebd7bfb8c9d8f8207b0967db128506b0ddec0e9aa39b386fe217035d77b0000000000000000000000000000000001ce5dc5052241460367a1a8a96079c2606cfe92ec74c4146428b48a3c07dd0c1d1689362b00700514eb06b4fdbf1410730190ae39ff6f51c60190eae8146915877e08821310ccda923aab19953ddd1cc5841e52b64c732dd10543927ce9eafaa9759d5c3067ca89dd2d4e8852d888c40e3b23ba41eabd5f679bf616cc69319ed338bea094e95953c983336510687547819268c472db35f488af7c7e8c94550e90d32a8fbd8f9671c1c02bea2ef31d1b4a3e326748a8d1bd06b25fd2fa99bb302f4666f395b1fcff1beacba719dbe8ffc3619396b3025eff5ba5071ee91492fb838035d25f95fee09b7c76bf9e19f62643633d3c57b4ba10d6b0ada562f31a75daccfd59d35bae1d07873eb6e1be286ad0acb5fb2b3d1de7b2b0f7e133b0cc51e4cac52d6bd7983c3e856840eee42da8b39fdbc283464199e95301549af3c8306343094c38ab7e55b6d140148ab27656fcb1c58dc6e73e5de6d167e06d5cb88c4cb5d62201890ad917f3def307bf49efa6a118ab373d048b9064acad85af1dcdf3b6b6bfa7d2cf9baaf6fe3d96c6ae42aab6f8b11b72c7d2a7f6ccddc12274c396b69a104df72c581ffa3a741bc33a69dbd19c2dc05a9be0ffb1493f41813f24ffd103bbc88ebcfdf9b11a1d7ded27f0d5d2bacb6b831791d9b91b92f29ca4f7f209c2fea15080c5a43b4ef76d388266819a23c3018e8fcd8933f9550ce5ec9ec460d05956487a58609eb2745cfc576ba24ee23087ea67ef701f3056b26cd78b16d9505be0f8e3d29deb357f22d23bc4506b797af1c0a25d35ec51599a47738f75713d7138f826ec3616ae188bca10758097c2accd8c7533831e52b99fdc366896ce3e115e0c80d91ca2b778925680f2c969054d016382a185dc251b09b8906aa70009be3f0cf5e993fc3e32be59b3650be4b85f3374e7c74f3a0e9e672f54772e956352d54c4d434512b940d51a1dd7e4780fc5d4c1cbe141476c63d14d922ccd010ff1926cf3e4c7caed4e89b29e6bc42097985f8b42180c6cfd7e0a737e5fa1c0f4410588cebbbfcac0a013660f7ff8fa807317403b21d37058047409de93ef684a5f329a088aeac5797f3158b26d2918aea5589869efcc19c7bcd1c985a1002e120e5af0b9a00c4970ddce0f115f8ffdfeaeac5d5e2adadc19486629d3deda719de53becd4aab610bcf26afa00879835b29dfa13f728def0f4bd4912dd13391eb37fb088a8cd425da48fb515c8881c1004366044f1acd93769791475d1da6ca9e6c3e26a3ddaac027ff87333eef9a02666d8c1145d43aa1f71c92659b045f223340f106c5695db2b93ce91ccf562e64060aca20e768eea8339c2e1aa46e165a1a0f1e079694c867658a8945163d184df6a2ceb9cbfb91711d711c134a0c2be36e76acd95cd4eebfc493965e3f629dd50e5f54c11a6a7ff8f2077cdf80e839118249511be616b3821034cf2ef21a2d99125d081744990b490e520623d98dbf4efeda97b47c934598f91a6ee9b741b804e9c12d58ed1796c3afaa0978a5db017671a518b2c5a49f48ffe71bc23864b1c3d0429ce53fc4ab88e0442f7f12b3ca41f295790e6f1418d32bd8034c46d27dfb3543cd22ab35acdef600791b891b943abb81c58d8d2e72f1a00f83f98ae10a9c8836d83c2417ff7d32ac7f30c084b7847d20b235db416a1e09c4777a486d5972d3a6dedd7bbd50442c67a5655c9da011ac9bc2d4d86d410270dae19871c89af69c81e5a5f25519ff46c4d9c935504b5353838f93e364e894b2f6d8850f297ff39b3c7b23b0b3715b928fc5b8d6973b8cf642ce30ac8414d4870c1c995b080c13f81e0cf98166edbbdca06da4c8658edd8271cf7dc44830680e9388a2028c203ba188d1a0a81cd3e3c3a8a19b168a33eec4d3d49f75bc279416e8a35e493280e273fe0dacec8e27daf1257da279ecc34c10600b3b3ae241c62e91e57d626fe3179ecfee321d4c12fc37b9b97b3ca1c150c11a0927926f1022ec7f702badd61d3205369c3ba85a5be456e991e8d448137f71372034992976ca7dc05b798b477a3d058c5b3eef72a1ad84a36af448445b7e4690f5263b09a0623d27ce05eea6da8c2985edaea7c52532b6d690ed0ed17b7eaa34670d481bf24566744749c9ec9ff1c4ea574687bc5fc197533ee192890ac530f8e33ea8d1ba331a8d777920f776e93c4e470824c6965c7c71e8b2cc0b87a1cf4296d7a945717b5b187b38975f1a3b6632ddda39ddabe98458d9abd7315a7d35b57c4384a4c6dc0b6dc45f01e53628fbed06ab8f7af97089d622ad9b491065b965d71be00dc6330d394bcffe51e0c67474f59cc6231d78ede68717faca11df8d07016d724466b3572e49642a6e59c017bab78bea1653443a382c7bb966ce7fd2c5bb78c4681368b590a9a69d46ac010ba6e27850e945c42a8045216e69da0fc4e86dde99929dc84dbc8bf85f8af091e33b7b5dc67f3609c7a65205379d9f2314ae48093aa91cce5244e3021f35f1d540b5a41d3103d32eca374dd694212e5cfdbf6328811abb0ac16cca8a76af07c92d61b9639161910810113494c342d5a42eb16f6411cba24bfccc3d9140f5f6e0a5991fb43fd68c0142293eb74eaa4507b971cd8cda9d4c57bfbcd5daae5b4b20174b7b592570087743cb7b592570087743c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c96d4f20ea0b76bca9abc72d8d0a336d49abc72d8d0a336d42daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d61101b889a1495a054606f9bd753b1f5aef5df47987d80b162121691f9041357236ad04a3f6fb1e11bdf6d7a27e3e448d0660b1d8390486cd958dd106e87243a4d0d783f13b297a603bec6f9b0992af52180d00000000000000000000000000000000a01e18bb871891f08e650a9dc621a4ef55f236f2c7ab0392f0536465eafc8d56aa5e06b8918e4a4c42bd628dd05758b20000000000000000000000000000000001211f429b49084e827a0846aa835c7e2c802f4459d93b0c158455cc113579c8100100b44dea76258a01508cd7ff3f9ae001003ccfe906c4c00190ea08ea7615872697f4b3a2eaafff1152f619fdc6683d692e0398a34b993b3cd2c2b9cd5f2682000000a0f4d1d11000000060b0bdbc75fdffff9fd52f2938eaffff5fd84e208963ffff9fed27e2bfb0fbff5f84172f3fcfe1ff9f9fa449ba768534c4ca15b4fc501d274828a4b4552ecd11f91b7df057409c7ccfc56b9367be4568ac6af207d5f4b1b1fc0b6a089da8dddbe857e63a4b960f035e694c9c0e1a6d1592e1164666b4fb95fe2ca0eacbe7e119f63f616a934d2db5bac3a8e8071b3df41a5a9d5c37773a797821aece8821b2bf28491eba98e3de3d1d04d4162d3418b1cc1dcc9f3b6ba9d798d1945ea1e9a1e52dbf1196695d6d47413c7c1ae385fdf3c8ab65b935a2eeab7eb3c711786b86b376eb757c48ecace83e713967fb6eba5cb81e92d2dffc18890addfec11ee4aebf490bf84dd737c83d0454c821e37b79b01d527aec355c52d3cf3f5877798140c7aec169435285c372c74de4d73fa2582374213ee7be696cf72cefb25238def6c33a8be4428a0fc05b9bd13fd4c76440823fc0becd76b9c28fbc4437a03f0e53ee27e28261bd5d4584173494aa2d8ae59da46d0daa3fc5465180005ea6bd5eef388208928c2f918b8e8f3cfed74cf5d0e5eca9f3e719b3b6487aa5a957b5e1fefc578aa365f521f8ea67ce78c7b5e3c86cd7a84d74f82f7ef9e3a31f2ecb4473d23b80dd428ed926c1a2840ed4e3e90f4873a665cc3a5e6ff8268ec7969b8e0bcb10e7741f41e1508d755232dcc72236dd3646600577eb7a0c80eea125416c5c5780866d07c8ef866382b2fe337886b0b890e4f66b49a8d30cf541c0f30198c959b3cd41aa0d288374e79fcca75f16962f546198969d961a4d4dad2a1e4f18ba1b1dbf2ad329a716c2cb3a2bc624909e4e929c2e6b010145ac9c5143876f09e4de1889cce88cfb21648779dec6b9e3d365f6ab2dea1f68cf5d4f76a319268151870b66c9a1962d43d91976a2cab49fa0203aca636f161881c96c6148dc22126195735bb1e346ece4303e068b60124f285d9b294ea3eb281708150107f7278db604caca7d069a30378f0becea976aa05369f927158ac7065d79083f05fb02d9e971d391bf7449625b9d9fc94acc858585a3e1e7959098e35c6ea5c5225cdb70a481f22954b5bdba6f6ccf14611cffe1931917f7671086b7577c8f6d976dded07b08972d2f3c55130ae1a21c2a10a71d2e85c2e2bee6fd028c2d4ec56b7704cd34ce7478f5a6fce4a4be58bc471e5329c7d6babec63bb751dc7d8bccae6dcc4b3f7e88d77b4efcf2507da14e74c89bf6160dc5742023e8008c150bffd4066dc5fcbb981c355f03171b1baa47fcdba36fc52ccabb3491b2baf26ac613f97a006cd78effea455fa43f258ecd03e4848e9704e3ed7829464f6e6a5de9b68a26fcc1ff423a666ced3de783934ffece947e0636447767c58a18de5ef8d78e90aee70f87b42bc3527a3efd14da2b1a4fc56379139859af0035199674af02f45d107dfedace88507171baab721b502331bec9173492356ae6c20c5480da915c2fb0befc4d24d48fd49001a48ce170b05a50150a610036aaa4d660d282967149b17b262ec54ddcfc61634bda84eb34adfb420557003377541204aa091052fa9114d701f27b0d7722ca6634364d591a0310728ce431561491703ddf68bb4f48a26eb21cde09f459c90e366b06d302de3bb9005bc872dca93eb4bdcf527697bac70e25a80c26eb60983a010be0482d05c3e2b7fb642e3339b08dc512f0fd1e42eb5fba757927b75053142df722909f1162cca7f3d9670af3091589e607a58a29af0344e602ed098e8bdf3ada7107859bc2aefb2a61dd2e65f78e889fd7dbcfb8913f1bc39d2df5779e2ffa8c53edbcd4b5627a6a3c735277f673a86bfc06b74350c09c514a0edd568b8b352bf8d087431888795c94ac603d99004bb7ed822f5b494af560067df5059a954c9c5eb4ab4f35fe9c4b188677aca6d119466609cf81fc8f97e7f4689b08dc00f4f71ec75e5eb9fb20885d7e3c03c9ec420935cfbe9f7eac21cd5203052ebed2d5562ab9e209fc7400dfe224dc0059513dd56b728b8a10651ee09c91b9e88e37333769bdf54fedfc3817bbcbb31b72f2476eca20a9094ac3b6dca1a25cfb7f75ea07ccc1b0f14a33458e2de460aab95c0190f5a2c2187ae2c61538aaa0e65c1e0f1303bacafa118f2b572ca9f245e0ad61ca033e30ac45f5eb382e96bcf638e3de0f5554b5a68784192b0d06cea5b81e7a8cdf4e207755bec152e1bb5c0e2080b48b05ff88708219121b542f31bd6e5a28be777a6201321029013fad25780871bb3e63d8b80336ab7f75a4c557a3a99f49591de578c54226a1c9914128a4b974772c4fc48cc68f3fe2922ef74b87eabdcc25d4541bada710b6186a82aef2ae167e30b653b6b0b5b34c6236dc0825d50c70f8e9b5b5904d4f34072eb7dd0ab9263f116af4820c1b3c6211b3011d6da7620007c46cf1f3541e589e6deb109aea277f394b2e7433a7522d01fe9841c16c3a31a7167ec0dccaad26d022a255416ea34fa15c60439019d67ee40c21439019d67ee40c2193eb74eaa4507b9793eb74eaa4507b9793eb74eaa4507b9793eb74eaa4507b9793eb74eaa4507b9793eb74eaa4507b9751dc6e6c189fc345bd0cdc111779a551bd0cdc111779a5511cd8cda9d4c57bfb1cd8cda9d4c57bfb1cd8cda9d4c57bfb1cd8cda9d4c57bfb1cd8cda9d4c57bfb1cd8cda9d4c57bfb01861912cdb6de497d026ddd8773532c051aebb7e25b0700b47380b509574b455cc5f29eb2412089fadae0cfc6a632d1bb953b5e52c94ed9dfb3507f99569004297820f428093a524c938cd75d8fc417ba00000000000000000000000000000000cf55027352950601a1026cfda7625bf542e0639aef13a00133bb24b687a39172985fb78eac60816cf80e3bfd49c03c110000000000000000000000000000000001b38712d259d0da5c266e2c204eb4b72b6cac9713b36c03d48dec6273f6cd9145c1958c687f68e4d5c1baf31601471baac155403e3fea0bc0815780287fa8ff7f6733e439b1f5c57f226218b1514bdafc554c9a2de56c626cdcb2a494124b016fda3ea94bdbf1eaa0f2b7a011039d6c669c07657b174bf8cc3f35c35fa90dca9ab574569ea55f863bf2305d54889daca09a568c4ebe4eb86436dbac34ed194415853e9b305cdb2eef9db53e548bff478a48f7b64dd2fcf7c7f3c20020c5e9c777d4000079bdd87c6cca05004f30ed69f780280029587ce5c37b1b011f6e66465b5bc007d904cdec7e7a4236ef249b797853d17b8a053e534b43b962c928b2460f4a26f8e1bdfc651cd175e58bd01988ecb13846d3b9b4b877d48cebc617f10c46cbd97070a7975aea87f415139a257a68afaf9985380757dbc4cd33a7903261ff56a06a92fa61a8fb5462ea00e0ad9ae146b0680626c13a2be9d1dc2c0b489b2e5ebd093a4ff83e46912d44962bcab8ebf13edd1b37870d7294b80cc384b25e1e0c0c5955a1e196d44f546f556e2b20d0244e0b560930e1b0f8224f5a455028d6c3f42978ea311adb50b125496e5db7fd2ad907000a8e03f020f036004ce21890dd9080011730aef005f6830aa750c3941fba9b4995345711d916420315706279ec9fce169610b151725fa69f1c74d73b1d9c8c5dc92ce4a2c744d88e85393d746ee1e9e7a992ac2d012a6557a602b83f0626c4638d1208be250a5dbae281383202478b18348d8b5f0cf1ceab6edcd09c5097a8b20a07b6492e239ce24c31fa0342f644321a59d71bcebbe25fb76fe3c29d22339f080e385449f2655a3e62884dfd9fc978b6afba1eeb5f834dfdcd1ad7689f971ef2a1bbe1d25b25d6a46d212cbd8205db82ffe9342a9326fd94fc657223060eec15e8c920f52a6274995885e5ad2caf2e386ca546ba38ca468af585ee108d87efcdb6a9856ddbb48ca4ffa3a70119f62c34b84a5fafb5157e2727265b0fed0146d2c15020ff921330d614e6349db64734e9100edca1291af760ebe3813d9e37384f7df4ec9857287e8c6e45c0417e891da6af8a7dbeb2d575b2fb0cd7d3190e64c3e99b8ad41313447fb3611eb495091b32ca90d18770e5b46adbaffce09a8bca3214b5f710bfded9d14304911ede4ab630af8d1b8ff21aa0a5dcbce7ae0dc90c647f259501c5c83cea3b375651c811010ebabd8edb8926789942b4e73ad90e20d060418df2b574c049832f9172448a6c06ddcc59c2cde299e2fedec5e794752faf01dec845cb57479efde17fe27bd909fe8b55c290ffb2460c9f4e754e6e0421dde07600b8cbe9e1c6c535798b49613870c874c31d7f8ddb20663353e2604fc7c18cc8b5cef9d9d9398b6e92064deedfa0b6a4b1c68138aa018107ed85851e0b182fc64a2b080d3d7960912dc7c8531d6656e9289e7899a01de47f457b46294031430a692b9d4e555149b5e4cec79ca574a818c22618ae173aa5c53585351b9c529d40645b9106fb118b6a4fda385fee5e947fdb85bf741e075c8536e5457290abf23e4c64043a77b3df34d14cce0e84709523ac5aac3fbe6e7dbbdfee457b9b4e94d5f83f6901ba0adf1464d1516d0683f5492d4fa50fc8b16ce70fcb6f1597712c66aa2c3a2b1a64bb192ce2d98cb0a50d71238a1856a73050525f8973006104939fdc8b97e3fc7effd79b4d6366869d9c01770b559b720f908084df554a32263b8bb175338abc5f477cec18e6b0b3a732e62c06555d69cb4a3bd285aa7f68a85323ff005e5d509b19c4cdd8b04446cb94ca71ff742a5f9785a4c58a1797f8d53ef7de8fa0fd25dccf20d3e395503b3cf7dab440fdc03f9c43ab9c1631d33dedb684bc15a4bfffd9e5a546e6237d6d4255ed13b8066eab367c57d4264147972aebd6d9f56a1b81efa3b82ad2a95f18ee561b9a22bf1ffa8848173efb91b5b66d8e29e5041b9156de1b3da21bdec246857a1206f25c68dd3b0988a2ba60751b1abd7707fd0a1c981c6ba8582aaa836430ec882fd09342c7ddd803079f8f3f83ff4f0693995b4f7ca413e7e9983a17ea419120484aac9b3d4c18f84c9f59a2bc9ee43233d2ace3ebb1520b9ed8ba3b5e070c1b7e4ff7ed640ae05a6935aaafeeb649fcddfe978f528b731dcca2d3d4fd8cb1396cc1ccf2b28177e565f6662dd9d87c1f600ba66d74157be81009f0fd9793536959743a0e872a1d3609b9e763d962d7f93eac5bff912cd6032ee0baf33c45e0cfa301b1b941a91a5837cf3fcda001035ba92c911b3e7fb468a84c8c3947e2ad92b79b6e41aad2657dad5bf6dec0d521aa93d17d5230df07077c62fd2d4417a2f8c2b0c318cdc9d245f5b079c6c4c42b321919565eb2a5510f8fb2dfe9345d5881a75ecbc0349fc5933f1d1fb5d8af928b0f71059060b312aa536c22ae5246d24a9e923e831bf36da8cab3f99cf3f02b2514ef87e1d05b99964914d85008941775d419ad20e713db6292112065f843ee4a66e985674ecd45a1dd2ea60766fb30328baa9f9c4c7bb8385db56380d9e141ee99f801dd471eac198e9a0a44bc4b12a3c8fdbfa32a5b060c1c8442df6e0222a26e06e16fc45c7c6f322045032e05c110575053d8a3c59e61bfcea9f16bfa47db8de49af16bfa47db8de49ad189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d24865ec8d749a3a1bafadce37f09b081bafadce37f09b0855b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f01f456334ea95c33a523fb78bb9655101a3605591dd39d922ce3c14798b9142444ed1cfe5506a371aa762fa63ec5e19c877200fcfbe28383cebb363b4ae808d8664b75f9be7ba53cc753493fa0ed05b95500000000000000000000000000000000f0d99a393c679821782255c4f9b307982bd1542dda3f4147e9b2c29a65af398b57ab1075a234ac30526607a2cd1cee940000000000000000000000000000000001a33428a843602d0d9241f12d1e8089348fbc50e8dcd9292b917d3f5d303aaad81689362b00700514eb06b4fdbf1410730190ae39ff6f51c60190eae8146915877e08821310ccda923aab19953ddd1cc5841e52b64c732dd10543927ce9eafaa9759d5c3067ca89dd2d4e8852d888c40e3b23ba41eabd5f679bf616cc69319ed338bea094e95953c983336510687547819268c472db35f488af7c7e8c94550e90d32a8fbd8f9671c1c02bea2ef31d1b4a3e326748a8d1bd06b25fd2fa99bb302f4666f395b1fcff1beacba719dbe8ffc3619396b3025eff5ba5071ee91492fb838035d25f95fee09b7c76bf9e19f62643633d3c57b4ba10d6b0ada562f31a75daccfd59d35bae1d07873eb6e1be286ad0acb5fb2b3d1de7b2b0f7e133b0cc51e4cac52d6bd7983c3e856840eee42da8b39fdbc283464199e95301549af3c8306343094c38ab7e55b6d140148ab27656fcb1c58dc6e73e5de6d167e06d5cb88c4cb5d62201890ad917f3def307bf49efa6a118ab373d048b9064acad85af1dcdf3b6b6bfa7d2cf9baaf6fe3d96c6ae42aab6f8b11b72c7d2a7f6ccddc12274c396b69a104df72c581ffa3a741bc33a69dbd19c2dc05a9be0ffb1493f41813f24ffd103bbc88ebcfdf9b11a1d7ded27f0d5d2bacb6b831791d9b91b92f29ca4f7f209c2fea15080c5a43b4ef76d388266819a23c3018e8fcd8933f9550ce5ec9ec460d05956487a58609eb2745cfc576ba24ee23087ea67ef701f3056b26cd78b16d9505be0f8e3d29deb357f22d23bc4506b797af1c0a25d35ec51599a47738f75713d7138f826ec3616ae188bca10758097c2accd8c7533831e52b99fdc366896ce3e115e0c80d91ca2b778925680f2c969054d016382a185dc251b09b8906aa70009be3f0cf5e993fc3e32be59b3650be4b85f3374e7c74f3a0e9e672f54772e956352d54c4d434512b940d51a1dd7e4780fc5d4c1cbe141476c63d14d922ccd010ff1926cf3e4c7caed4e89b29e6bc42097985f8b42180c6cfd7e0a737e5fa1c0f4410588cebbbfcac0a013660f7ff8fa807317403b21d37058047409de93ef684a5f329a088aeac5797f3158b26d2918aea5589869efcc19c7bcd1c985a1002e120e5af0b9a00c4970ddce0f115f8ffdfeaeac5d5e2adadc19486629d3deda719de53becd4aab610bcf26afa00879835b29dfa13f728def0f4bd4912dd13391eb37fb088a8cd425da48fb515c8881c1004366044f1acd93769791475d1da6ca9e6c3e26a3ddaac027ff87333eef9a02666d8c1145d43aa1f71c92659b045f223340f106c5695db2b93ce91ccf562e64060aca20e768eea8339c2e1aa46e165a1a0f1e079694c867658a8945163d184df6a2ceb9cbfb91711d711c134a0c2be36e76acd95cd4eebfc493965e3f629dd50e5f54c11a6a7ff8f2077cdf80e839118249511be616b3821034cf2ef21a2d99125d081744990b490e520623d98dbf4efeda97b47c934598f91a6ee9b741b804e9c12d58ed1796c3afaa0978a5db017671a518b2c5a49f48ffe71bc23864b1c3d0429ce53fc4ab88e0442f7f12b3ca41f295790e6f1418d32bd8034c46d27dfb3543cd22ab35acdef600791b891b943abb81c58d8d2e72f1a00f83f98ae10a9c8836d83c2417ff7d32ac7f30c084b7847d20b235db416a1e09c4777a486d5972d3a6dedd7bbd50442c67a5655c9da011ac9bc2d4d86d410270dae19871c89af69c81e5a5f25519ff46c4d9c935504b5353838f93e364e894b2f6d8850f297ff39b3c7b23b0b3715b928fc5b8d6973b8cf642ce30ac8414d4870c1c995b080c13f81e0cf98166edbbdca06da4c8658edd8271cf7dc44830680e9388a2028c203ba188d1a0a81cd3e3c3a8a19b168a33eec4d3d49f75bc279416e8a35e493280e273fe0dacec8e27daf1257da279ecc34c10600b3b3ae241c62e91e57d626fe3179ecfee321d4c12fc37b9b97b3ca1c150c11a0927926f1022ec7f702badd61d3205369c3ba85a5be456e991e8d448137f71372034992976ca7dc05b798b477a3d058c5b3eef72a1ad84a36af448445b7e4690f5263b09a0623d27ce05eea6da8c2985edaea7c52532b6d690ed0ed17b7eaa34670d481bf24566744749c9ec9ff1c4ea574687bc5fc197533ee192890ac530f8e33ea8d1ba331a8d777920f776e93c4e470824c6965c7c71e8b2cc0b87a1cf4296d7a945717b5b187b38975f1a3b6632ddda39ddabe98458d9abd7315a7d35b57c4384a4c6dc0b6dc45f01e53628fbed06ab8f7af97089d622ad9b491065b965d71be00dc6330d394bcffe51e0c67474f59cc6231d78ede68717faca11df8d07016d724466b3572e49642a6e59c017bab78bea1653443a382c7bb966ce7fd2c5bb78c4681368b590a9a69d46ac010ba6e27850e945c42a8045216e69da0fc4e86dde99929dc84dbc8bf85f8af091e33b7b5dc67f3609c7a65205379d9f2314ae48093aa91cce5244e3021f35f1d540b5a41d3103d32eca374dd694212e5cfdbf6328811abb0ac16cca8a76af07c92d61b9639161910810113494c342d5a42eb16f6411cba24bfccc3d9140f5f6e0a5991fb43fd68c0142293eb74eaa4507b971cd8cda9d4c57bfbcd5daae5b4b20174b7b592570087743cb7b592570087743c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c1a85483e8e38098c96d4f20ea0b76bca9abc72d8d0a336d49abc72d8d0a336d42daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d6112daed5dff910d61101b889a1495a054606f9bd753b1f5aef5df47987d80b162121691f9041357236ad04a3f6fb1e11bdf6d7a27e3e448d0660b1d8390486cd958dd106e87243a4d0d783f13b297a603bec6f9b0992af52180d00000000000000000000000000000000a01e18bb871891f08e650a9dc621a4ef55f236f2c7ab0392f0536465eafc8d56aa5e06b8918e4a4c42bd628dd05758b20000000000000000000000000000000001211f429b49084e827a0846aa835c7e2c802f4459d93b0c158455cc113579c810fd56019d0370002303f9fffbfc3afab3ffffff58000000db0090ff98ffc601d7854b5734650074076601550eb9b39e76d795ff3a4d0bcf5ae9ef10c76701ad1b66e3155508a91144c93799533b9f7bdc79863049a55a61074fad5300857aa93328bd4902a459a269162c04107e7370e394341d70782813380beac0272970ec759762668cc6efc72321b2ccd66d8e77fae1de98df06e544d922182e1d3543e2f01ccbffbf85d0c9e0be8dfe3faeb3842531e0f5bfc4e9a0065721b93f6164662e60e90fbea9becc449f616f32a53699e153ab0b618a7e302b44af51a7c975532e775fadc12d8ee529fc8ba206a18bf9ddded3712e6dd1d21112cb1c45fcb9c37c7b8dc9e3e8155a695bde823a609976e177149499a7313e2a408f0c33965bb327bfea57651c81e715396b67c5c68754998beed36573b64f31cc85cbc828fd2d5992a8907d1fec4170fb9bf46edf74cd11dd43b0081c329e7c08dbd13cc75e536836fdbca9749747da75ec2aa43524f5f72d772c7d7dfdb3c73642376c73eeeb7577cf82f52a85733940ac93b62da42892bdb509fe427d1cff25f843f2da6cc7f9fdc8db9f02fa73d4e67e025f17d62bcf45781199a8da32aadf497a2fa0fa63a71505584c65dabb938f236816c9f8220ae9f8d89c7fcdf4465eceee497e9eb1f08ca487057a55db94d07fb5265a56ff11b07ef60e775cfb7dcd76bd684487df71983f2edde1b21c1d28bd430c2de4c8cb132cda55403d7e928134f758c6ac7301876fc26e6cb9290ab10c5107f7112447d6583733c27dfcf1d46d83665570e79dc80098cd591254517605289f76804c393926185a3f8317918c0ba976be96a4f7ce509f3e3b1f80c59d355bb6a3da806649777efc7bfa85cdfa4275e768d9a99ed2d43454e2f1a456bcd1714d329d825e22bc1c1e624c9295ea24c9d2b21600170120ec000c9195ca8854fb27662ff03ef5c1005edf3fb9417c25d4b034051fffca883e33824670c20f0124933d41bad0a32d02dcea66ddd1c8e1b0f0685d937857754680f45e0d0e0d74b1aaf2700f4d98a57d03853d9e3c825f5e08c32e261e52b9d60b7cc88dda02b9586082f75b8a55db02b17502ee44bdac348aa3a06e5a098e6af4a38df72b5a2e028d2c2a1213d102173f313b18431ab447cb4da2ab4832b4702d89b9ffd2a730d0761cff34979eed64342966f004fe5f6f26ed99c7cccbc8f31cbed09b02facfb6287179adfc327bb088ab2e903ef186e0d09db4e593e96509d1aa296505713c4fe8a8f1fda4cc6fb1488ad9e90bb4948f25712a750c6cf2fd47a84f20947f48ace430c30a86ea48bc94bcfcdb067c49364ddbac4d8d0792a2a20e26b5c41a6656746315e5396611388245ab1b3fb3443c6cdbb25887fdf6609f3cb6dc3409d4e13f359403d7b7ea1c1439e67c67c11017e4f130db1289eac3571890286b1dc846e87d8388c07076314ec67d30082b3829c42e6d4693d39b83c15ec883694b0e431dd95fcd9279ca83da7e248bda9c845dd768c199d69a45edf79ef025b301add01a5b993eb4038087ed1862394e8118630ddb867497271ff408f3f98289f2d1cec82ade0f16f0b47648d5ac8667c069b47ea79eec517919d42c56a8f1eef72b79007c3e8d9f4f39c892ee4f390ce428690d80da07e0eaa31bbd0e65541b2fddf7bcaeb40df507e0174e503980303cfa07b4cdc68515f196e9ff8ecd5477e7952a2328da00e8a525cdca8a8b01006fdb9416aaeabdf10e82d55418ca0469aa2838e376a288cf34aac36a08051a096294f1321a0bd34896123ed07c34ad78728125f80f882c1a4ad896af04dc2547955f2fce456fb73fee831a797b171721a173f0f9cccbcae7424b61df837c4f8708743cd4f9fe48e8fdbf3979402354564945951c666eea9459469ec1f5fb8a5c492f890a54ce1b7c2d1eb7540ce9d22805cac69e3d9c799395c264f14c04310910a179c9e2affa22a9c0f4782feaffe5fc2aa5c68acd8e803b8fe1d4d0b15e82e7a16a667c6873b9b39fb20d253ebf3fbb41a123f12a69b43d26a7e88179541cc7e377ac589aa864506e9469e0d85c64f3c6ea9ec00e3564419f4fc1d829911b6a9240245e85388baba578c5e44b46147b7aec7674a279812a6af607d09e93254c1d4c427007d9e2b35da6b975043690c7f6e40b4da73c98a791f405f1649d3b3031634f5b658155c80535269c8c91f65640b271bcbebf11e1d4dd193528421b54193ea4014deca7de9e99ab00007f4004e43a1149b1a0d167d5601706600a57ec6e03c9efbd1e97908326ae22d9757604b0bc0e4d4b76007b69dc971079ba4089e9ad2e464e462f521351cfa8463913e973e14d07135d9ceb1f55fa27424ea1233c0801f1027603db140e640be8a458ecf6965cfb5c6670acb1cc3f2209eca92084021fd4f49ebf8d568575f1b1f53c971ea8979f5ab0501bd7a0edd60f6da1bc2e6847fe490f817a443e0f2257c8fa62aebd992305daf63e097bfad3c0cd1316010a15b01528a3e7dd25a0bd656bd3101d5d4cf2b313b5a013b2d687f83f54ba6e33067582a98a740496b03b4b69d646dc956c00669994e9097083bf1b2b93f89a7083bf1b2b93f89a8efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce3458efec50f7d1ce345100300f8043dc6b2afd95489faa27615afd95489faa276154ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b4ba33744a0f0db2b017320469057703d250c93b1494b1d1b944c7db1bce37e1ca892ea0974372df6835ee16f46eea191325dae6d2bd22866e792480290516cec361989cb66a8467879e8e8b774cd6d5c884ad45e7c7edaf059000000000000000000000000000000002152cd8903b11b2185099bc6c0509d5ba24671fc2a9bfb61e6409a08ed6d0a87588f23f0c52b39c31b7de1db253829220000000000000000000000000000000001c972d9cb553d1fbd95dbb0e54c251e68393572c1073221a9510c314f831ad78ec1958c687f68e4d5c1baf31601471baac155403e3fea0bc0815780287fa8ff7f6733e439b1f5c57f226218b1514bdafc554c9a2de56c626cdcb2a494124b016fda3ea94bdbf1eaa0f2b7a011039d6c669c07657b174bf8cc3f35c35fa90dca9ab574569ea55f863bf2305d54889daca09a568c4ebe4eb86436dbac34ed194415853e9b305cdb2eef9db53e548bff478a48f7b64dd2fcf7c7f3c20020c5e9c777d4000079bdd87c6cca05004f30ed69f780280029587ce5c37b1b011f6e66465b5bc007d904cdec7e7a4236ef249b797853d17b8a053e534b43b962c928b2460f4a26f8e1bdfc651cd175e58bd01988ecb13846d3b9b4b877d48cebc617f10c46cbd97070a7975aea87f415139a257a68afaf9985380757dbc4cd33a7903261ff56a06a92fa61a8fb5462ea00e0ad9ae146b0680626c13a2be9d1dc2c0b489b2e5ebd093a4ff83e46912d44962bcab8ebf13edd1b37870d7294b80cc384b25e1e0c0c5955a1e196d44f546f556e2b20d0244e0b560930e1b0f8224f5a455028d6c3f42978ea311adb50b125496e5db7fd2ad907000a8e03f020f036004ce21890dd9080011730aef005f6830aa750c3941fba9b4995345711d916420315706279ec9fce169610b151725fa69f1c74d73b1d9c8c5dc92ce4a2c744d88e85393d746ee1e9e7a992ac2d012a6557a602b83f0626c4638d1208be250a5dbae281383202478b18348d8b5f0cf1ceab6edcd09c5097a8b20a07b6492e239ce24c31fa0342f644321a59d71bcebbe25fb76fe3c29d22339f080e385449f2655a3e62884dfd9fc978b6afba1eeb5f834dfdcd1ad7689f971ef2a1bbe1d25b25d6a46d212cbd8205db82ffe9342a9326fd94fc657223060eec15e8c920f52a6274995885e5ad2caf2e386ca546ba38ca468af585ee108d87efcdb6a9856ddbb48ca4ffa3a70119f62c34b84a5fafb5157e2727265b0fed0146d2c15020ff921330d614e6349db64734e9100edca1291af760ebe3813d9e37384f7df4ec9857287e8c6e45c0417e891da6af8a7dbeb2d575b2fb0cd7d3190e64c3e99b8ad41313447fb3611eb495091b32ca90d18770e5b46adbaffce09a8bca3214b5f710bfded9d14304911ede4ab630af8d1b8ff21aa0a5dcbce7ae0dc90c647f259501c5c83cea3b375651c811010ebabd8edb8926789942b4e73ad90e20d060418df2b574c049832f9172448a6c06ddcc59c2cde299e2fedec5e794752faf01dec845cb57479efde17fe27bd909fe8b55c290ffb2460c9f4e754e6e0421dde07600b8cbe9e1c6c535798b49613870c874c31d7f8ddb20663353e2604fc7c18cc8b5cef9d9d9398b6e92064deedfa0b6a4b1c68138aa018107ed85851e0b182fc64a2b080d3d7960912dc7c8531d6656e9289e7899a01de47f457b46294031430a692b9d4e555149b5e4cec79ca574a818c22618ae173aa5c53585351b9c529d40645b9106fb118b6a4fda385fee5e947fdb85bf741e075c8536e5457290abf23e4c64043a77b3df34d14cce0e84709523ac5aac3fbe6e7dbbdfee457b9b4e94d5f83f6901ba0adf1464d1516d0683f5492d4fa50fc8b16ce70fcb6f1597712c66aa2c3a2b1a64bb192ce2d98cb0a50d71238a1856a73050525f8973006104939fdc8b97e3fc7effd79b4d6366869d9c01770b559b720f908084df554a32263b8bb175338abc5f477cec18e6b0b3a732e62c06555d69cb4a3bd285aa7f68a85323ff005e5d509b19c4cdd8b04446cb94ca71ff742a5f9785a4c58a1797f8d53ef7de8fa0fd25dccf20d3e395503b3cf7dab440fdc03f9c43ab9c1631d33dedb684bc15a4bfffd9e5a546e6237d6d4255ed13b8066eab367c57d4264147972aebd6d9f56a1b81efa3b82ad2a95f18ee561b9a22bf1ffa8848173efb91b5b66d8e29e5041b9156de1b3da21bdec246857a1206f25c68dd3b0988a2ba60751b1abd7707fd0a1c981c6ba8582aaa836430ec882fd09342c7ddd803079f8f3f83ff4f0693995b4f7ca413e7e9983a17ea419120484aac9b3d4c18f84c9f59a2bc9ee43233d2ace3ebb1520b9ed8ba3b5e070c1b7e4ff7ed640ae05a6935aaafeeb649fcddfe978f528b731dcca2d3d4fd8cb1396cc1ccf2b28177e565f6662dd9d87c1f600ba66d74157be81009f0fd9793536959743a0e872a1d3609b9e763d962d7f93eac5bff912cd6032ee0baf33c45e0cfa301b1b941a91a5837cf3fcda001035ba92c911b3e7fb468a84c8c3947e2ad92b79b6e41aad2657dad5bf6dec0d521aa93d17d5230df07077c62fd2d4417a2f8c2b0c318cdc9d245f5b079c6c4c42b321919565eb2a5510f8fb2dfe9345d5881a75ecbc0349fc5933f1d1fb5d8af928b0f71059060b312aa536c22ae5246d24a9e923e831bf36da8cab3f99cf3f02b2514ef87e1d05b99964914d85008941775d419ad20e713db6292112065f843ee4a66e985674ecd45a1dd2ea60766fb30328baa9f9c4c7bb8385db56380d9e141ee99f801dd471eac198e9a0a44bc4b12a3c8fdbfa32a5b060c1c8442df6e0222a26e06e16fc45c7c6f322045032e05c110575053d8a3c59e61bfcea9f16bfa47db8de49af16bfa47db8de49ad189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d189774f254a24c2d24865ec8d749a3a1bafadce37f09b081bafadce37f09b0855b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f55b9952766a6b19f01f456334ea95c33a523fb78bb9655101a3605591dd39d922ce3c14798b9142444ed1cfe5506a371aa762fa63ec5e19c877200fcfbe28383cebb363b4ae808d8664b75f9be7ba53cc753493fa0ed05b95500000000000000000000000000000000f0d99a393c679821782255c4f9b307982bd1542dda3f4147e9b2c29a65af398b57ab1075a234ac30526607a2cd1cee940000000000000000000000000000000001a33428a843602d0d9241f12d1e8089348fbc50e8dcd9292b917d3f5d303aaad841370564de30e47fb024ac1b16cf6f14c14f0540230c00c0065c2180deffe3237424fa715af9fd64057b79ed9db2ee11a466cd3763f7c678263ec3fe8baf9ead00d8e82b9840070e00e85d3329c43262fe579167225d63afee67f9d4f48bb7cb7dd7d1d2b6d3049268e4bcc302ca21fed23e2a5a1986ecf24475b28f029c8bba159f6d0ae64706b68f59ff484ef72bfae372fbfe29c333d73024e0f82a566ae239390d0057de8bcd8a905c006614d39ec2f38702ce8ec5574caab711a4e7666612a8057c7e55d0cc799827647a56b2994b2b15bd5c5de0330c2f942b8a8d226bac9199561f798f9a38015d5786179d8185088b63afa44b8ba03bcdb8ce8011cf5ba19c0dac857aa97969485fb8a759a24be2fa9a0e9673700a30dc3c691a2913465005aae0b81f86e73125a6270edeaa4d5d048b196312ac178d1ecdb6b580b49ddbd59b03f884ef4501d9421fc8a28ce008efd3dd7873d81b3e89cb154e28ebb7b2c0909e221a6efee244f558f2b602f234e2b46ea000139e722ff2066304854f224c9f33b51ea325f0145b6df4d6750091927d00afe038fff6016f04c9248ef6c00d09227f01e3b446603ff4790a35ebeea1bbae5549736a886d21ca570127e5bafee987660911431cf765b7cd4177d2c5c1c906a0cc42bd684c84306098d326dd169e58a12ac9050ca05271692a80205460421ce22881dd4ca2d0c82e1e88081a70b48047d3b833b610ef89f4c60d65fb7489c5af7060c1df32c268ce14a3431e644fe1a49175d2d3bc2b2c82fb36bdca2932368fe080288b245f7eea238615ceff997769fbaa8fa2fe3549e2dface571f67904301fba3e1dbd552450da16b2cc2b58fe30f89fda983269f856c95ff42d62e0cc60819ea841af229ea5895596cbcaf25487c35618918ba354b3585fa6f7d07852e76c9b86c5b64d4553fa3f01e64f3028a4e80f8e595b701c8f96cb1e517383e2407abbab15e6a8e232f3c0bbb95d66d9ea464b6d49a660c81ea154b5271cc11457a2ffccd4f829a44d747648a5fc96d337af4a61958b0240ebdfca8ded1291d6d151349b4312ab37203716c03f58e56e0a9b8056b278259078779e2a8a400f36ade8f52f081ff691c76fc7a2512ad6dd538402f7d36d2fd1add24351714ef8a5a2cb935689e97554dabdf1e81d3aa751d07168263240bc837432cc120944dc4369045cefa7b217462261a9b0e3e0d621214a4c089c225d2dc239be56fae76951ce8bbb68d2e6160029b01520d5635fc0bc90201a09e4e76ff712ddfec797d49c8aab30ca7a254720035d83f63d2338d8205e880e9c3232a83a781b7627561ff85534be3a238c51604918519e5286b471444c3f524e460684a04025ea3ecdcf0a073c744e410c5d9f2199c59767743bd7fcffd744066152b41453dd0c5ccb2fa3a128fc378a2f71216229b3fc601e964d79bf7ce757d85d90cc1530caed6c64ea650ed679298613a2ed3486a8da2d9f2bc2100b7fe5b67c84a4ba0e3b2ccdd622f58392624958fa2570cdfb6c2264e811cb975c3384e5296cff666c3ea0e72e2972464e8f093f972c26275af06199c34791c9c3cbdb14d82b2972cf2c787f67783f791a60be1863eb7774d154b32ee39b551b40f1155e66ae40bc61fd9bd339c263cc6b9d3f40f0571303b624a083629c2dfdc3467b14c13496d8a483de6296eadec3b231d131e6e2eed867087c73967c48f785d9dc81444589d8ccb08e636434e98b9f721c9415606255d10f7b7d76e2c66e3b3d50eef2af6fd7e89586fbe5a48a4d6825fb518c947b3a04a1216a4094acf2d87fb29092f2c28e5af6a07ebb5a8083254cdbc27e0486128aabbea3632ab25f78fb0dabd6b3305fc385676b83f1671eeb43f817bd0400184963f05d4b6501a6ff40c6df0974b612f1db0d71f088c35d02e5ff3c0c467c5a4dc4cbc50949d23d9b8819e54943d01724041c1e18666bec6abf55314c4c55d0b4abd3b0837e89f1b4568474554bff6ca99ac1fa6d34cb38d7aa13197a972d4d1a4008e00ad48db88b6c98dcaff0727566d48c1ac145b626671ad9314ad78a5325bfb8cd226bba30e15b71abe9eb164707f2dc94d5bce50ead344487505fc75c13ee0c3be7bc7acc536021bf86973437cafb608f864957ccab2d5cb0186194806c166329b4caf56067ae4693cc16f702a3f08f77beac084feb4bf3c45d5fe60b5d2dd043780c7d174a37ba5ea6e17cdf1703c6ffba9f119f4e60c777692068823512336192b7f3859387128b012aef6c69efe02572e414d90d8766c3f36e303032d8c0c07f890fb71b4ebb0dba9e9d69d71c8276e73901423c2d35f67f46afd9c0af76971ae8f773254716028ae6c32f1f8fb74957daa70053d6f1cdb2ab26593bbdcc81c239b6bc09f98bc2232aef0c387bca67f375b4226cfae56519cdc229ce0a3c61776772c893bec394a888ca53fb874ce1b4b3fc6b172f3e5a6b6890ea54be1acc7627bdadff3f2c5f7ef4aa9b72d2943c2af2240f32150190c3731d9945efde1e1b79616f7e790f97a20fc7be01ceefcdfdcfc2233331680461f7dc1502a5759517bf4dda68fb47d85cb24e921a1450fb49f11815251eaffb49f11815251eaf33171a8da332675433171a8da332675433171a8da332675433171a8da332675433171a8da332675433171a8da33267546847590692eedfd13326fccc3a1ccf193326fccc3a1ccf1974416854b557732174416854b557732174416854b557732174416854b557732174416854b557732174416854b5577321017d0f7025bd54d131106e1caf0f85cbd939b9e28f006e421bb2eb978c33e85fb3e5c9ac8108dd81b02bc31a6cbfdf25a0b34faf40fc8aef35804dde6fa5e609220ca6fcd93000db0384c7e48c5246e801000000000000000000000000000000008104745e5f85eded6b76cf326828d96180b4563788b66d35bf81d1bfe90a09dbae88dc9750f110a0d826c675d2435f930000000000000000000000000000000001cced33be15d3ec07178e36e6b78ebd0960dc68e0a7e308ea88d537be7206711dc1c1f36bff931b7e40410db90069e301c10140ea3ebef3bf01acff2bff5300801f06632e3646b27a9b0fcd2c22fcc45ab5f3beaf81e3e878b460a4f2c20fe660277021f358716d8d0e11eaa57119fedd5c77668921b2f2118443cdc1eadea27d99d89c4c6e18746f2cec491807ab2c0c347505aa31ad3855972787eef88ee48a41ea0ab57c2cd852c5674cf36a37e94362d616a7ed8360dba9dc9f91849ba3ff841a4227ff7891d997b9ce12ff4efaf21b13a783ff28d8a4b9859199001fe9810ca8fa3207d95f8d5198da6435ef9edd312afac17b8a580f5727d74d62c96b6bf06013ad936e779593872ee6e48ba18f02b5454b45d36aed08f3e70eebc6eb7d35a5576870707271708465da1513211a109fc6f89885e7b66b596ecd33a75400ed71049e6a9250027b1d1f52ea0034105dced93e68066c7188a4f4b7dc2cf419b87fb007093aadb5047ed3354396bcf71672c878dc1b28c6951e7b4d0cc3186b11d65d1e5855aded71da90d46e55bd7f14f9f5cf0a562d7e89cfb9af4e5a3d73bcac14ce2978ad2623b990a22549be0ef50ff57107003467b16fb31d36006cd2d20de8cf7f01f4c0b96058af830aac460ea56acb9a49b4ee5c83ea8f4103ee86819769efcd1682b08324e38ba59f8ed390ff35d38b5de6c8ebfc79c6d78e4c7e6aea556de9e71774e36859fd6457a72c34de71edc36393386b131d7e5cba078cea87cb728a1836d461b79023ceab7acda203f5f8a7b25a9e6a19b3ce9be27a54e4b1e5a644325c4f3add4790e25f852b950cf7f1329fa5301258c19d655a87547c684950c978b34f62db0132834de82dadff0c5e971e5a41bafd5a9224d676c911f07c0005db3f8274906a0326fdbe8f29f3e9170dec38ee19a665a761748e83ac8ac793ae2ee598b0ca740aca46442ecf8a314987efdd43a8cb5a00b48c11db017e4c9d40cc885c85c3a2835eb314fcca03257fb519ca0313c438f8790dc4d90b5963978aadc2d049a1b03c7dbc5f4e76ac8c65763de2ea6df41261c1529ee8dcba4bb3a65e5dcfd2691ed8f2f5cbffd47205aab3d0328910a13df771c01366898859dbdbabc92efcb6b9458171c9fec2fe76b85d6a535e8e5df593a4e7a0b34e97e3979a089a133d51e442472f5421bf97abe100464e42a1583934cdc6c71f9c05562298dbdd3849c1c75d96c6f6b3dbec1ade960971be76a5246eb7d59175cfba29346dcbb507c1f9badf81ffbd4bbd5b8f94259e213c2eb591c2be2935d392485b7cca67b7591fc303f7c4a67a14b4353e8da39efc9f6917bc22bd68585d121cab30322f4f8ff6fa053437830c861c42dbfaf70459e82a2458e063c42d69c86404cbffcb26149b312f61390c16844f6857d896f172b536237c596753fe15a551649bb74f31ec8ffe70522abc54727711f16ba23c982c6df262dedf8c0f44556a888d687a9076d774829ea9305687b8b7ec86ae2bf68608194e2dade9660b4e3c51c2a030d879635c06c4bf873073780bb38018d93813096b1670ff4465107004537214c5ed161152ba5a81b8aaa31aafa48f95cbb9cfd8b772d04a89df0e433135089044415c557d9b7aede9f2e5b2ef95f96f1b7fd4fc458c94b1b20d39aa31dd5a411f77a3afcfd2e2c4243e4a301ccc06654e826aec77836827efca7c7182cd2435b300010dfbb5f97adbd654d07ab9fb9702a899f4b56601482e0cd09efd55304daf38b7bfcf666922e6a0bb7324bac125130059bb8f637bc735c9023be4578371e0734c67c2f9fac5735c1e724030df269144d37f7f734677f090f159f20ca603391c6380fc9d29cd4bed0828bdbbaf6a64ccd2499d77833c3d3da6bb0896696983b64757fb7eed8e944437465c70accef7227ac6acfd4df0e56a8eed93055171e0829428c3337385febff1e0fe882370ec0a90400b3ff1ae48ecd46953832741526ee6c41211add3f3b531290cc0b81a4fd2f372615d53e10a014477455603b4cd08c79b8174af3e546f5d7d5442cdce03cd7989f241297028b38bf8f0dfb6d53cd6f760eb9a8f6cbf8bf98381df2c977a0a2e2274f7376d85f07b7b6d2988b28bc9536fd75a0d4d8445e1fd669be932eb35d5c014604e9cf4974cc6bb8154e8cf758907c54a9997ef426dd676290ab008142c07f26cf602ace74cdf54d7bfddecf3e447872c1a688e5da7e53b5af2e6332981b6141f64c063cda28f3c3c1401d1ba2f1b70e4a9c09ef92097557b988ee5c77261ea20e8d3482a495e56d57d7083eb7b6730d240f2831b0c311a324b93b17b7e9a2bbab506dd75cbca8498f739644fed357e7f43a9004b0b3517f3748e2d2f67385dd4c5953d21d9674d2e6350b8719fdf7b835aa7b7be18b3df7ef35cb532c0e7dcf5cd66e8abf89fb5d8f58bf763b174e673a0e74a298883acf0ef061f497b464f57ff640f6f4380b77231e37ec6c36a31741bdd50db51084dd207498f9e30949bd40f9130066412e5abbf28ccc73f40dab356e656eb3e03630bdd5ce092e35801744e6016b16de652a3ad10cbdc759100e20b8444b89f0ed34a34a7f2e3c59bbfd189774f254a24c255b9952766a6b19f633c059ef981a249bf6213cc5be2411bbf6213cc5be2411b77d32ff7ff8bda5777d32ff7ff8bda5777d32ff7ff8bda5777d32ff7ff8bda5777d32ff7ff8bda5777d32ff7ff8bda57f56edcfbf5c903824cc1767f23c57b194cc1767f23c57b193bd839428c615e783bd839428c615e783bd839428c615e783bd839428c615e783bd839428c615e783bd839428c615e7801445bc4245a6c46e49713ba5b314aac3da0f17d8918dfb9232381bb18ed5dee418ed1845e8e171e114f689266ccde923f4f12fa0d24d6df5ad60d9cf20a92a08e1603f02fe29f17cfdbaa7da54d7d94a200000000000000000000000000000000a3eaa793c79eeb444b0e02417c1acd5f34ccd8fd63e3f8a22da4e0b4c45a1d88a38ef8fb2034fff39380be2971e2f0f10000000000000000000000000000000001bc58e991ca452d1700b7eee9ac9b76e5340836dc5009c25673026f9c2f7ca4add4e976d5acfdf87fc50074aa4b02b7bf98eaff3f34ffffbf98ea0680f8ff6f95cf08c937d694823cd48e7de9b7357a7f8d030873261105a1c66664f9ee4828e7fbec20ef79efa623dd7ae689558c90f9055c4dc55cd6f3d21e841d658edcaac4cd9ccec3e907ac609949a65a6637b4a42b038c7ad083ed80c2097d121b7b4f7fc41a649d30c1071d5cbbbc4d544836cb7f1f292053fa7b8e76dc1fe148d863e56b089d0ce34566f3e73a4b583be9cba74d9c0e6aa3609396174666e67ba4071ea1eacb4c637f35d2626a9319bc7b76bfa9e807b329623d3c9e5c37e524afada50db6d72d394fe6911fba98df2e863eb6d5162d1d4cabb5fbcd9f3bcc1aaff7e1955ea195c1c9c52d129669174c8468407d1ae3a3159edbc266b9357b9c520154c811785e4942094c767c489503d040143a67fb1419b0c58d93d2df92b2d067e0ffc11e04e8b4d622f94dd71c58f2def3c921e3c96ea018ab7bec35850b63acad597779a454b5b6bf6a43527f55f5fe3de5d73f7b57b5f8b13fe7be5e68f5ccddb3523897e0b59a10e5428a2224f93a7440d4c7f1ffcf9c2dbfcd769c00b0493f38a03f4705d003bb8361bdf229b01a1d95aa2da325d1bacb0eaa3f760cb81b925fa6bd3b5a08c2fe938c2fa27d3a4ef7ffd74c6f759923c3f4e7190b3b32f955aa57b54d9f5fd059a465f51f5d9db27479c7b5df8e4de2304e74f81de91e3056202ecbd161d8505bde428ebcaeea357f0fd4e327ca6a797a66cc3a178aeb5159c8969ba2c8703d71751f41727f15ae1833dcc71f7c96c2ac610577de681d52b9a2254115e3cd3e116e07c89435a1b778ff3378117a68054df76b497a58db251bc1f301586bff08be42aa0d68f4fb3e32cda75fd8afe3b85f99969dead0390e9e2b1e4f6aba9463522bd329e81a11b9402cc62459bd770fc52f6b017032466c63018151c6114c7751dc00aca6c5a22b65b57dec736314847883affe80a638409a0ea4c6a53a4a99ef36cbf076ae39e3036b57f3d5ce0c706d6b0a4b7f026486b9a1d00317b9707ef87e9129c6ca29dff6fcdcd0bae79c4962a862adff762f7561ed1aee3ab58433c80a2a451d0a14ee87c8d8e8da4c18a41f22170ee9be8bd898a0ffe61422d26cd5530268aae97be895d849cbbf26761b5f532b4b792e9aaa313197a45a47cfd538dc3c3bc63bc9c9b433bb92b885a42e188e725fca0b954b2f311a0dd9501374026a90aa84ae2320ef5fc3943f105893643a43a662a55c6d42b2ee629cfed01b649a7581943e3401e3264aa484747c073f567ec23b1a57434a5bafeda1e2e27ba367857636b8194e58929a60077972af856cb933db389fdd308f689bfc102e18aef553be5c1c341a2f859f199d60794ce3bb83590e91d30a45f7c6357b24d68b135428d6237cf3814c1b14da7668937c9033369aa17223da10ae8844263875673ae0196a3ff79f4d206a4607039fd01186e7f4197f0dc1b75a4fd36eb613eaf5f48dbde19bb3690257384fe489411a91013c193f06fcf92e11b9c1dbcc174791d991222ad14a6125b41d1f7e19d90dcb5115b22ca146d7e8142a7d8eaf3282e9b683e650fe9ef8efdfabca29b221c379c324f240668f079e23ebd2767718eeeb6e9aa20b63ea7e67a5493126301b6b253d36edc301b864b8521423d3c58276f2d8ec5a19976532cf05b5dbf8cc7a4edd8173d9aec4b8ea50153cd3c34fe58ae4f7f95d45c3d42f7125019243e0e17ba6b58a671a4e1e3296870d30d948b8c85633154ada73f1c5c15fb0ce4ced14d70b3fa1d01b099a6fef5e170f4802bc77e7c8504932499247ed2afda81be806cfb4e5ab1b959b9458ba11958de6566d0eaa5e7aa19227e369ac74ce788993bc913b12f57c2af44c3b167b8e7a4ff8303d6097ee7e4d158c4c216768c8418d846f2ea4d820f8f2d21ba1f638d8f5405f68a8065105eff39bcc6d37367f90c0ee11e2694a9bd1b6f9205f2746019373a076a4c831b68a6971ef1919e62766bae26fe7725c0cd69b951a041dc6eb917232782dd84e170508e019bbeebd7cab0bcd23b3163273724193146ba02b7bd9fa238552460798fe57f717adffd67cb2c7664e01ab8823511436da4aa704b77486a7b2f8ffcdd940c9c5eff6a87f460134967570e87179b0d443ca11abf4f95bfad5ceaa0c084942adc6bd9e957acf5d581637dd6afa961afd5419b7dbbe8172bcb3680809945d5d9833169966fc1c9fe87ae57245ab755330b4f80d0a967288d73a4416f3017b7358a9bf5805db43af7b7334cf10c949d9f630dd6fbdf8f05fe401547c4f242c2357db79d6e634272e374b3ecbf90c6ff190424b055eead7ae2f4d3888053620d69e8500cf7d50881cb38916811ca582547f4cf5b4003612c13427564c5605888db372e443b19b9a3e8a0fe67f328daff7d7bc519d5a0d80afa01436f48adaace3f3b5ec50045e30bba7352d75b3f255028b23e4e87f48243d4d22517df1146d14c0c32bcb5d9527b901816e1834cd019af9d26ac8d8326be4f4632f7afb104f6bbb351481bc3ada362b265afb364c34afb744d7bf17641e9564a3efa3250595e4ab43113c615cb88552bb2c79fc672b8552bb2c79fc672be7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a442563bee0bc1a99b9d8db91a726740da0d8db91a726740da00d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e68010adb4a97f4da67ec4c03217c39f7ee96b962b4c21aef1cb15080798954e46842941cb5e827d0a7268306f42d8dd0c664ad558879a27e33edfe3d2a8d384486e221b42fa05a9a2dc441c41fde9273dfe700000000000000000000000000000000c7e6e037342b14b4536572bc4f605a46c8dac5eabc6960e62e01daa89f4b92a86d22d26e4e49c23799a61029093bc4220000000000000000000000000000000001f616c5b8c8fda6d8b256f3c332efac5ca38600f568156758a3b02291a2f9c496417ffa1b21311c80d1245464eace8febc197fa3f5b0c00c0fca3de7f20001cdc8d5784a05e6bcb70959980a212ddd641f878842be826a851e9111400ee6d016100281787cac41b0e0018a2b18961c262fea76edbc5aa50b3ee9706006dab34e77c272e0001b070526214430109d01441ad8ed50840b091c71cae6295f3c23061ede4134db014f3d676428b1bd790a5e034d1cec0e7f586246cb8a74556b9b0ffc8c6f2ffa7217432776fa3ff98eb2c613f0c78fd30713aa8b55548ee5a189999ef57fa8380aa2f338867d89b84a94d66b6d4ea42a2a21fccf5d06bd47472dd94e48551f87d682070c9fea2a878e8627e7cf7749c4f5bb47461c43247307fee30a65e63f2527a85568896b7a04658a65db61d0565f0698c8ff7cf23c395e5d6ecbbaffa551e47e0791aceda59d7f12155b4a2fb74e59ced53ea72e132484a7f4b64242a64fb077b10bcfe26bddf375d7321f7102c21878c27e6c17634e9b1d7144a4d3f6f60dde591031dbb0aa60d49fd0fcb1d4b905fffec638dd00df89cfb7ab2ddb360cb4ae15cdc0feba4910b298a016f6d82fe501fc70209fe90fa36db710b3ff2f6dc80fe1c4db99fc00a86f5ca16115e4450aab68c977792de34a8fed81c4501167799f6eebee3089a4732be882f3a3e36f85f33bd4497b37bce9f672cdb22e961a65ed536fcf35fad8d96d57fe1ab9fbde21dd77e24b35d2f36d1e177f9e58f4b7eb82c47ce49ef10750b39f29c048b7639508f9f4020cd3d9631eb5cbee19bb01d5b6e8a2f2c43d4d27d04c94435d6cdc8701f7fd974dba08015dc79ec31006687960455725d01cab51d20531c8e0986facfe045c3e242aadaaf25e94f33d4a700cf07602767cd9606a936a00dd29d1f329f7e6159be50dd605a76aa6b34350daa783ca9e96e745caa4ca7a05b082f87ac1893647b3a49b2b9ac05c0011b5a29278dabf348cedeb9ed1eb95f5b06dd88c239c8e1291a2ee4c5e3269d660e05668f913f75ad6ace94f5157f535d51b756d13ef7c76d58b3adadc9817f6e6b9e3b6dcb36826b1469317be9f8e06b4c884f13ccf3310c52ae6a9d435ad220f9650e561455b44445bbcef21e791e7d0c2a4ed0f95dd8cc6d300b87cc9b1fbd2089c9a2b6dcc5fcbddc5532e1d0ffdbdb13a7e94b18d61704f4c3c2187ceeda57294ee336d6e457cff2b7d75fd702f03cbe65fb76b338d1f29095781137b0953135370ca13d2d082f1312e0b29a9f050c5601510a9720174f903ce696bb5988f8a196bdf5c8460124d92772a55aba152947c2cebb734186f19d6cfa5766b86da149d3cbc896ed552e226228b944cdff2a1e3d4454eb473c14b66ee4b4de8579c3963b3f2b233511812c45145318533cff0dad4b486b9248fa32955b4ac31fc0490ae480ff6a64c448ab614660b026fa1121f00ac942288cb05d2dec0a50916510a8ff0b701be1e0303bf05b1caa743b8c6959e0b944d157b9105b42d2657d8dad397134f3e5bd56f92ab2337f2cefdf5b59216d309a70ad791d83bad3538ec552e0a5bff4ad0b33b90882876e6fda449713e2aa1f3a6ca61e7b3f8bae1184fd1e4ee8e936d3d76ea07faf59ebc9d0db4f56b6d6f194734a7858d343c06d1866e472fafafcfe05231e00dd2fdc01361b90165058efbf484e3f5069762bc4d2dd462c01b6fe7bf1c4da9d306f2d638e8c61db6f76a0742aa8abf837908cd0a04737ba3e0be53096e9b9efb74dcdb6577f2b58e537ecdc359729fcf6b63afbc3a07559a3a9c53cd9cf62b85271d2cb6f7e7e091ad23dd20f30b81118fa22632dbc52b8696a1dec1f5a05e6f68fe9f1bd4f854b864f34538f5810755b01e7d3c58cd8c8a6204a35bbaaf3b0b0d2fa9120cdb1e016bab1e79929f18c33b155855de0cbda743a8906677c35b585167a10cf91c60ea88a83faa68575dae853ed4ed77773a6b37af10c5c1bbb54217d68e8f0427606469bc3e0f79b17a043dd7dea14c359fd144e11f976fffed4166802cc2128c3ef3dfd3cb293d04d7b7e47e61e44ca00b38ff26fab7971bd6e6f17ae8585761f49bf3f36d6628bb7f7e23fd52a1ce972d34b46e3db493cd22ab7dc68f112c6bc084c4144e01bfe018aa741f0d9a7ba6d3fb6c8eef4365ea7770f8afd175ca5d76b9e4f9bdbc788d22ae531b6081c1699da5e26045f50619054ec4741ad77912225bc1ea129b41566272c7af8d16dcd04f33969ba56d05d2549a1d03dae16acb61ea9330f476ee398317e84c85aaf12b1565b173cccb7ee0bf2efe678734b7808d88e1def52ab76d038b33d95053655574e994be32e33edd38e0e24d4dff14792b5811d3b753a74e905bed49ea6768bbec1567cec0f037bcaaab8584ae7b52fcceb29c5ed0949c449b109d20dba0479a963e1efac40b979b92c48662d48640e3e273468001a6fff70f7b9ba5f2d455c69c23ecf2dcdbae7eef4d617594b8ec99e691f602386ff52159de003c57d12fb9d50e816cb994bae3ea4e5c1f87ca9d6a1b705f1c8d853350182cc974df0bcd2fb03a58e1261d6dcf1e3ebf41e01290a3fc506351181d9709ff686550e9114e3d8dfdff2f29cbd905d6fd491cbb562dedd78d19b1a5b38c9c978d19b1a5b38c9c9967ee053826458e9967ee053826458e9967ee053826458e9967ee053826458e9967ee053826458e9967ee053826458e938113653745522ce8f2882585dbbb3668f2882585dbbb366cebfc326e0245456cebfc326e0245456cebfc326e0245456cebfc326e0245456cebfc326e0245456cebfc326e0245456015b242169720b1148848ccfd78b785a0c98bc2b20496f039fd609f4cabcc8be08dd6070e27db2b704e38ee860e7f56b93e45c6c09cd7cc97e02b6a308b1130da5970a2396498e8a53b077b31ff6e4c95700000000000000000000000000000000d7845461b4511389b6e0531733b664b3e94cae2d825b7ec56689bd7271dc57e4a438a32ffafd3e9bc18a9f75ed2363dc000000000000000000000000000000000188f1ebd06a7dbd2471ac7bfe9a357ed124d5b62f220994f51e8ffc3d2d67114927000000b5ff5300d3feffff4b000000f4ffffffffff53005800000000005400445692b4ff543d24e55600f0fa527154bd3a0290de445539985a12f013e2e9d111000000efffd641760000008affe0cc35030000cbfc269a6f16000091e91037089d0000f8627681354b0400cbb43c8a700e1e0090f1a8c779fb669ccdf0b48ff4db24ff56249f29ab0302fa61fe5923ad190ed6adf475f7b5b362dac6b039c4288393d3d87ce5bd139608c9f2694631841a3c7fa4e5ec589ab9a47a81477a6e3313815a8df4570565868779ddaf6725c2acb4520fcfd5054eb9f0426ba9d82853c70e3877ffbd26e77713d08e6d78d14c4788b0ecfe4aba0ff3b9d37df80c1869a515ca70cb5aa8db85978619907b9af9a824aeb6f06039ce9e00c3ff94a6919f57045501138efb53651e530f85e2e03fc5d44571a33126b864d1e819785b0b08c1b95db548804f36471490f7fc812c79f28df0c5ea8d374ea1e1936a6be1841f692b0bedef29a2d5df2f4e7f8f256fd01e4f237eec060ab0d729f772773046cfe524c2254453eba348024f0edd466f72fc0f29670bf00a1ee76f1fd24f904cd0510fdcc02ef217b03c6b0446479fa7cca8ee1eeef25a95909d86d886a47c15f04eaeebaf7f68968c28c471d37ddb1cd41b5d1cc87000cac7c28bc67d1503866e53d26d739615aafe47c0002c1d97a6eef7410538cc218e7fc7cd248b95ece27374a001d4167834242f630bcd9f486ff949b64f9e5efc0acf05fc2d5496e64ca728e4414f1c4e1a911c3dcd2ac622b8f2c7ab9c306bf3089e77b24854eea73e5145e1fc4e8497b633e528ea2c9e24fe5f441e67405300f393ded3d1c84602a50116cbbc81ef0f83049a8d298f8c6f951836df22eed70c16a87a1af482e7599a945ab9ac985475380b7a11b92d50358b4a567a0f433175ce015c586cda5834a5018db73d0239a49db60788bb08995600c65ed4c438880b103b17d54125879c4f7e3bc010625dd81c3e6f8d247df23d158e16bddda58f2f658bd811753efbddc00410b351c0c7ebe174eb6259abc4f571a10479d3a05d473647d1942277dc9f6a80a06db8eb4bc11cbd7852f1c2108efa177c87e15c139faed16441b22d5f64da2554229fffbaa4668c76ba10df811a5bcc984217aef91ca396f012d9963d26abe375929c302fe45a1493f32c2096fec2edae5a8415b7cb9963efda104aa087a7c5fdaab1b5b5e2907c135ff88b37a9a3109cb2ef820726431d5035d905f5dd42f85ba2505acc9291534b020d52561ce17f0cca7bfd2e58eaef4767fb5d33949f09e7a7d88bd659caf13c47f096e36f75f261d665b77a91de505c18c354005fa79ba69c9b5541b842ed791cf4faf4ff2e34f91ebf7f9759caf255a5de269f391fa23cc89c239fe35452e5d51307b4ef1cc47e491b37d9ec0df182d39d3f49fbb671522c6b717d817e130684b6b305042b5fe6a4cc444752692c5589548bc9021af4330ea2e702247f3b4963bc92a6901a60b659132b9db58bbe142966ddb3184b819e7baaa62bf133495719ad62412c66f023f8ad26b83f97b76a0848c5839d88bb0ef767afdbd08d561024d8813f09812d35b9daf62984081c8a375f986603ba1224aa6aa221bddc0618a8799aeb3d9a287b65391a5172cbef569ac238ea4546a7c94b0c12c39f0cea949884df617a079865396890b315e2e65b82649d7cc4df811af6f892953be7871fc47a24410e28552a9e09cac4dcd37a048853566cfcf4fab852b7a25970ee660c3973614115f63fa22f08574566d6f7b9e0e5be2611843f648466b4f19a6d8ce38bdcc5dadce6f031eb505be83001fdbf08f2353f094d0f8bed5a43a2d1afdf94b9df964d8a1bc8b889a96ab61b8d93a62182c799b77d1fe15bdebb8d918b5193082b7222fd60c19ad852680ddc7ec89d4dbcf33d801b0902e61ad78d9ced9b8886c1d6501edcde3252544dd51fcc11b6ec14f015dafe05cf72760b2bd9505b24207376fc08ab77a9e8e536a3ceb4fdb2d475bda6236f5d75d53d22706178cfe6df4fe17b633fdb3438ef58cf80cb4fc7c9ff9ad1ea1e1c5e4e28ac509e8f0d2227e938d613036e59a3a4ad9bb828a704672deb920aff2fc5bfd6e92cf990a7dd6dadda1e40157c458bebe0bd1bf924cc1978df1b29aa4979fe8d265d2ab321dc01c369220e6b8264284b5445a9ae56f94037698dab797435076a0163bce4016da01a5ae3e9dd5c0e3a8fee2f899a17a841609008327688797c6d40f39434e8548dc5b717b42940abcc8cfb7ca460aee07fdb567bf3d1f594bbfd6dcdec9cc53acc9043979d9fbeb257f24a9c6b5592c77c590b4d201258c24d9bf06fcae2f056ae4fb1916fb047938b9c3a8ca467ef43a4f7ccd66f3348fd422ecd5e58d6da3c312324e3d4bf84272bcffd6c8ab2fa50f288ca63d10144b4c3df364a185680e25bfcedb82c215ca10be95b82971015adda827432172ebf49ad3e22d66623a48478b62d07156cf649f22851f047838601bc9d0541d26a25c8bc0d4f6f5149aa6736afabfc854fd77e95cf816dade4d664816d467b824f2fe2a4909644ca7d3c19224fe2cb0b10789b406a4040d7712db3406a4040d7712db3cbfa9aadb83c26b2cbfa9aadb83c26b2cbfa9aadb83c26b2cbfa9aadb83c26b2cbfa9aadb83c26b2cbfa9aadb83c26b2aeb3ad1bfc9e9486c2e2761d00fe407dc2e2761d00fe407d7d4cc51e7416459d7d4cc51e7416459d7d4cc51e7416459d7d4cc51e7416459d7d4cc51e7416459d7d4cc51e7416459d019758fa521503d1c88c2a0aa48d88900aee4803e3df631d781be2581dd56084ca90d93f2fb5331fe93b50d50ec321af30cfb80fdba8694835c77ac38acdb1e310c8c93ed829427ec7c741541e0e9e517c0000000000000000000000000000000048ca272e346b84a4a8faeb00d807ef40292c96b3b940ab96af21c7074954ba0bc97af718479cb3a03c3f6508b7de8bb5000000000000000000000000000000000164ca7a1cd123fbc8a54aa363e2baac172bfef15744131cd95cb598f369e22d0f55ebf8bfb417906ae85400c05fe8ffff0100f93fc30090aa98eaf87ff8ff8f6a557bf97830c1c31b681fa129ef64fab2e4a6b591e5a7ae8f55bfcba7b0c4511175fb1185b384195531e07da3eaa0b253552171786c66e24951e9174bf9cc30053761a70dd19a552481a8935fb73b57fe819b099d09a262f40bcfe4a01c4f3290571fa6dee639719e5ddb8a165495185589ffcb9d4e15ac53bdfc93502895b4490400e44562f3b1b118003ce9b3a7dddba200a460f1950f036e047ca499196d15021f647f33b3fb950ad9bc7b6ce6e11946ef2962f74c2db5e68a25afc71a3df47972ea75f70f4b16d8952f866620b2bfe3184dabd2e2de3d34ae1bafc33318b168c3c1c95d6aa9d7d3574c8495e8a1e5bf66169e1c5c6d4738cf9c52c984fdf382aa494287a1eeab8aa903d0b66a86b3c2a219b003ebace84873b3d01f6dba5cf626e8b4e0fb1889b71059f227e3aebffc746fa01c36c83de3320c63c97a79b0316455b5855b52d352bd55f5ac8040c7392d58b5bf84c3728c3c69f540a15823d4a7e0b5c6686cf7c69624f974ddf6c3651f00d0370ec05bc1db00b088634082440206d0bfb8c28fd90f2ab0410d53eee96e26d1d15c45845c080db8bf89e59d803a5b0842c446517e997e3ad05def3871327699b2908b8e14613b32e5f4d0e586a79f5f4ab2b648a9945d9d09e0fefc99108f4d4920f8ea2974e91e07e2c86c1d2d62d8332e7ef9c53bafea704373d25ea2ca6a1bd826c18d708aebc4e80f48da13c970635d6ff8f08a7f15be8d0bcb8bcc7c9637e0508dca97691d882136dd8026e3cdbeea7a0c800d36a1376b5c577e5e7a6887ee8663709558dbb585b0b80b166cfffda7d30c4d9af4fbf197c9591938b0e39f278374ac88d1396215962fb3bcba94b0951a4de3281b11d617ba1b351ebe77daa616c26ed33246fe8f9e4e01053a64bf2a607f9ef34b038f3cc1a34cf2663e6e28e2c66d606187b3104443e03f6f8fe09300c1cd70325e11cd4c5bff105668bdf7909c2d9615bb53c01300553753b1f4d0c66aff0387c16790431b33b1592a7aa52a28fa1d4fb9180459efa5cff71cd07f8f6aea48170ea0c7a72a074f10ce38d7a5d0dd840f87e33e6903420e589046cacabefab7b154080f8bb2a97e6e373f6d27608e73294facbeb7f5c0a7f1b98e16977b93895606f95031b3f9e4ad620172a9127d1a5db05525d73ac0973f9bbff67cc36e727f57df82eaa29b86b96b962bed9a957e55d1903c6099b0383dc46cea31f33640678fa2d304e2d56804401d05ac576880badd5a2699f93c727e97d68df08aedc0b67d1229119451fde7907320e9222e3ea93da57ac90d960e38f16c946c0ad60454438306621f851611939f90597143a5713528927ab1ece09dcd8b3ec43635b3f880b743e56325884cc4fa8c60287cb0729fe54fdee489f807b66e5e592d758484ddf4a96123e63929827f09c1fa4d31f32da199edea42a892d5b1b3ed194ec6af9c855a18538e981676c1f6761b4fa655a974d949e8339dc010ebf14d09fa22170080d40d1581e9ce544db03e59b7d654c4e1e23f3c8b76ecc0f9b895a14b52741cf36f155498b272cce642f91a373c3273afd6a6257737825f70c9bc03e2d799c2eb9098b3b84b1daacb7f73a73704e046ec52504dba169b666903bb05956d746a1d63c579503d428f53f4be89ec1f28d4a6e4e1c7c4c2dcfd491a24c86ec12ab97edb73a5486d26bf61168d6828385e1fa94ef4aa4c037a51ef453746822920e76245302b9f9d43cb0be3c72c2bf639b3afc1b21c90968711791d7c4eee0dd3fbf1c225e90f7ed8539273320c4c931f0676d2bf6c58b86f3c069076391363b9cbc69890674e9ca7d09cd2e7ffa903e0aed2b28e1acf79e77d89cfd872380bf1255c48991645af861cb457d98e1005556e8e73fae60b34ce101d91df324cf77ac9e235c8a2c46add186f02f28194c60b6c3602e1dc7eb041550152493080662c17716e9c7665976d5a792a930846dc271cfd75befa84284c97003885aa1706cf51d1a4782b15b4396a91c3da28eddee7e8b726f6a8463d28892f744bd0667b4f2b9e8754b882ba8a310fa8a7adbc26133e9c5873038b21e6d4a3797f15ae4cebab832e5f9245a0d4b38841ba2000aa77737a2fe9b874b27afee2fb22111335cca1f435097370244b60b6ee0ad27587d41c5880fdeba59d9e7dc05e77de2bd3543cf402576869a832aa53b9d19faa95d9bc18a0604dd3e7c369695eddd07c186d0b1370e431818e578d3959ef65e9ee88c1f5708c168d8b8faa2465c796c69ba3f8f9f8f28f0103fa715e581583a8cda7dc234c871e6bd1792dac7b2c4ce6245e19fd59bab35fc5a7be9daa075dcea9209fc013827d9cccc5125615629a0dd686d34e69c9e454ed9481718c529076d18f45434024e032340a7cba66f577fc8c742ce0b37d76219bf871f7e8d03d431a83c50604ccda65fbe7d8e673241c232c2f1da968fe000d9cc22f3de6118c45017cfead49a7200830674d685c1569fd384b1a80334daa16a47d8ba2ffd6ae3add8822cfda89b1a6f14fb37e2721601f88016e4e440b3b0e14044459c8ddd4e8f2044459c8ddd4e8f2afb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641eafb744d7bf17641e3cb26d1e064b76c1fc98034d51d5de87fc98034d51d5de879564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505959564a3efa32505950169e4f82604ace5b95e7dd1deec02942942e6fdc51a89f22537ebd424a4878621ccd1092b6397440be69fc1e470114fda454d67f4c098dc7c506e018e91be117fa90820f545e01b7fdc399593c02306320000000000000000000000000000000005585c1b9305733b8ebcfa020ca502857f72bbcbe8365a1e2881a8d548ad06f6343bf43732244eeefcdc90467d7a00ce0000000000000000000000000000000001540d700dd2b53a1fe73dcb0f1dde1e4eccba2954533bf93cf84052ef3f3c9053a080e9bf047316dc5c6beabf548d05003b0ce43fc5f31b1cfc5be97fe8ff1bdcf841c97aca9a8a94d41b32cd56a70cf80060125b60f9b37128905b8512f46b888d2d6964b74f2828da3ee0be042e1a19f6b721382142b7afb607ec88ecce02cef53574be7ca813a2af792d356d9b896ec6533e74ff3fc305235a750b82a58f0e73a3e062a713c8e11f7824b49989782cd848ffec34c34b37e7fdfa7a735612839acd57def1ff6fe8309f6614a3ff0f5b4e5ace8e77fd6f7d1f78a4e747ee0f6ed6487f55fa836f02dafd7a56d89b0c11f6f05c5dea425877b7968a8d6bd46943033b194076c9e760fbd88617a2a841e7d7eeafa4749ccb52df87cf803247914318b7ac8562f2f8d8a301b9a7b6a0ceee6f0b0f960465a6870650691a23c38cb52630e1b8f955d9f60451280eda59f1bf17371a63fb74993fa081b7b5e03232bd5b8b04f829645f2c7ccf1fc826bd9b3663acde78102c427eb2b6164e7634cf73dbfe9e223f6faa2afcf758f2ba0aa92ae3c76ea01d4b9f2a34770763d00d5b2a6b4234b5b3607d28ecd06df4eaa46d1b74b600af6c82ffbf27fd04c9fd90fc3f10ec227ff1f6e7bf6b74f4799fc0573fec2eaf555d4466bb6f48ca5792decb1f09fb8766001693de39ddb7cd089a05168f0c07a03d36279ae5573160b37b1237426759a1e8618181cbd271695fad898a8ac31ce29ebdc3c9c158c92e5d2f5a84446d81478f4b779ed8fc89f4ee104355e6e9c5af8a76d554486569cecc3dd651f6c4e1a49bb0db3cb9622c8242d401aa0bb3368fd5cd0ca649e57eeadaa0598afc447869ff6573c8dfe249e200ca277b163405300986165e986c245042aa9d9224f8fe30d4a74f02fcc8f856cd962d10e47ecd609d1f3f7139789ea550ddb9188f495587340d15ade50255b3735c93bb3e1453e72e870721b28d455348b234e701166668331b49fe7cd625ac5f65f21e343537abf94cc9847bd1d44b78c442b311e15d7740f517836ffda2e7716735ed57a422677e3cd282b4150d055ab5d08c0656f1ad60c68739a97b654ff4c309291eb095a141c7bdfe579c1d426b315e7367cc32c43d8c98fac0201b1fc3238627974ea25671fa0bd18bf0dec99833f5db06aceac92ad2cd151f4825e2ce967bde8e91b32936434c991c8af4fe57f8a9a4b3b1a7bb7fa78f47485a09b12562a2e85a359290c1de0abf7d5d9fb005cd5902752a79318c1020c22851e1f627ac059371278836afc8157a92ed6bab36f8c1a8ae62421bf845d67977b5649b198442b679ef61b0011017b5df24b7ff915ca38bde736e6679506b57c8a172cf29205687a3c0e9e647cca2ab6a11e641ea99d795d3cedde33d51235f935371f81c5c72aac23556660683bd8dd8f68eca81c733ab61ba32d1a675b4981cf3d083773a660acc3e94707fa3a4617bf26734a641702c39e7cb3d446be377c7566478c86e366e81952f2a9348f184900bbf03fe93f2755dffd2a18b523fcfead61d7ca9798a8d583fc92b5431cba7847daf6ff1fd1436ee183a0243c05b9002880169381260a05e7dccc5b3650cb8485eca7a620eb0b99810451588e2e902c66416c0821bb1c6a0c2c83f89a9bf729c051816f31a678f39c6242119ed1d2d003565303d438944ed9b86c26bf52d847621c7b3c60da1d37c4ee9056f932d3b60d4ea4623ea36ec91e86989bfdec93fc120c036267bb1658d739ea3ac34bc1b8db359abae38e8f947be97598c36d14877badeb697ed61a4c68fb04017b8c338d90ee907e82b51e66ffb1c269d9670c04a851d564b0fbde299f47afc4cc014b851810c285fada4c283d9b34731a1d6ae2192ddc17a98414f6232825960dba9ba9be9de280cdb160a64ee0a78dd20d45a3644703a7f7a7a540c286226b5b93b5df4b28c58fcb97f6f216be147d2ea0b22c4cea722e784eb2b6f88c8f31ce0ba50b1ec3a6f6442540507f7c073b2182d14c18fcfab2422866c5fbd28d97d6d0385e074a00004accb63d6d2c88d766cf010274421c9678172ff1446955e29cf0d199c678786c7ffa8e09d2bf68dfb9a04cd376da5a90822a9d25904b6c26e76bb199843c263f240b59b49c279cca44127f48f7e5cbf1066d7c27afca759601a3e4790716316466f137070689ffa28deeeb0e3f7aaa80b3c155da8e88df041d872a0e401de71921f123a3649e4ed7e14b725e94769a789d572b63e0408dd1a3f6d6650442850578217598deb366bd764e62693056c3f1dfaa674ade7a430d4854780fd6bc26bbd11eb0089857baa07865aa6eb84f7b3891def85daef694a0c88810da8591e16a7c16bb9811d5e439a0c2e76324a1e45ea97e97c0baf7bf42b7e2006a6bff027c06996d23895c68d4b19cf94c14bf6b03b1a2505b7a90a3a2278faa55eb55cffe0452de89487c21226b014f5068dacceda44745b31c42dcc355f43dec2a654eff9debe148c9c6067dc8fbfc17a4f3c2267014bb1294856d7eb15632fa4055d8f5a3dfb1e1a4792a47cf0d2f2a4016d1b8619fdf99aab7297c95f2953b6966a3bcdb4d26588938b26d7bc4e1c2b4c967ee053826458e9cebfc326e02454563c4ca78f58fba325b5a3436f11f018ddb5a3436f11f018dd0461f7dc1502a5750461f7dc1502a5750461f7dc1502a5750461f7dc1502a5750461f7dc1502a5750461f7dc1502a575f74f43ad338188159e207916ad65f6bb9e207916ad65f6bb9517bf4dda68fb479517bf4dda68fb479517bf4dda68fb479517bf4dda68fb479517bf4dda68fb479517bf4dda68fb47017ffdd4b8686a9446b85a01cbae4b570fc262b85980f958c6403acc311f11ef2bb79b8abb43ac48e2e578673668a09750ef9a103750da662c1067f44d8ec7e7bd13e8eab803e0fd6fd53a7962403cd6760000000000000000000000000000000014bac1aaed3dbe16d18393aeff181b5aaa3d23423e546af77207b1115b8dd97583d92ae4b70efa8ba6df4b8949b8708d00000000000000000000000000000000013e97767c9aca06d6394f4ce7186132433f1df837185bbc5ab06d07de73660e09d4e976d5acfdf87fc50074aa4b02b7bf98eaff3f34ffffbf98ea0680f8ff6f95cf08c937d694823cd48e7de9b7357a7f8d030873261105a1c66664f9ee4828e7fbec20ef79efa623dd7ae689558c90f9055c4dc55cd6f3d21e841d658edcaac4cd9ccec3e907ac609949a65a6637b4a42b038c7ad083ed80c2097d121b7b4f7fc41a649d30c1071d5cbbbc4d544836cb7f1f292053fa7b8e76dc1fe148d863e56b089d0ce34566f3e73a4b583be9cba74d9c0e6aa3609396174666e67ba4071ea1eacb4c637f35d2626a9319bc7b76bfa9e807b329623d3c9e5c37e524afada50db6d72d394fe6911fba98df2e863eb6d5162d1d4cabb5fbcd9f3bcc1aaff7e1955ea195c1c9c52d129669174c8468407d1ae3a3159edbc266b9357b9c520154c811785e4942094c767c489503d040143a67fb1419b0c58d93d2df92b2d067e0ffc11e04e8b4d622f94dd71c58f2def3c921e3c96ea018ab7bec35850b63acad597779a454b5b6bf6a43527f55f5fe3de5d73f7b57b5f8b13fe7be5e68f5ccddb3523897e0b59a10e5428a2224f93a7440d4c7f1ffcf9c2dbfcd769c00b0493f38a03f4705d003bb8361bdf229b01a1d95aa2da325d1bacb0eaa3f760cb81b925fa6bd3b5a08c2fe938c2fa27d3a4ef7ffd74c6f759923c3f4e7190b3b32f955aa57b54d9f5fd059a465f51f5d9db27479c7b5df8e4de2304e74f81de91e3056202ecbd161d8505bde428ebcaeea357f0fd4e327ca6a797a66cc3a178aeb5159c8969ba2c8703d71751f41727f15ae1833dcc71f7c96c2ac610577de681d52b9a2254115e3cd3e116e07c89435a1b778ff3378117a68054df76b497a58db251bc1f301586bff08be42aa0d68f4fb3e32cda75fd8afe3b85f99969dead0390e9e2b1e4f6aba9463522bd329e81a11b9402cc62459bd770fc52f6b017032466c63018151c6114c7751dc00aca6c5a22b65b57dec736314847883affe80a638409a0ea4c6a53a4a99ef36cbf076ae39e3036b57f3d5ce0c706d6b0a4b7f026486b9a1d00317b9707ef87e9129c6ca29dff6fcdcd0bae79c4962a862adff762f7561ed1aee3ab58433c80a2a451d0a14ee87c8d8e8da4c18a41f22170ee9be8bd898a0ffe61422d26cd5530268aae97be895d849cbbf26761b5f532b4b792e9aaa313197a45a47cfd538dc3c3bc63bc9c9b433bb92b885a42e188e725fca0b954b2f311a0dd9501374026a90aa84ae2320ef5fc3943f105893643a43a662a55c6d42b2ee629cfed01b649a7581943e3401e3264aa484747c073f567ec23b1a57434a5bafeda1e2e27ba367857636b8194e58929a60077972af856cb933db389fdd308f689bfc102e18aef553be5c1c341a2f859f199d60794ce3bb83590e91d30a45f7c6357b24d68b135428d6237cf3814c1b14da7668937c9033369aa17223da10ae8844263875673ae0196a3ff79f4d206a4607039fd01186e7f4197f0dc1b75a4fd36eb613eaf5f48dbde19bb3690257384fe489411a91013c193f06fcf92e11b9c1dbcc174791d991222ad14a6125b41d1f7e19d90dcb5115b22ca146d7e8142a7d8eaf3282e9b683e650fe9ef8efdfabca29b221c379c324f240668f079e23ebd2767718eeeb6e9aa20b63ea7e67a5493126301b6b253d36edc301b864b8521423d3c58276f2d8ec5a19976532cf05b5dbf8cc7a4edd8173d9aec4b8ea50153cd3c34fe58ae4f7f95d45c3d42f7125019243e0e17ba6b58a671a4e1e3296870d30d948b8c85633154ada73f1c5c15fb0ce4ced14d70b3fa1d01b099a6fef5e170f4802bc77e7c8504932499247ed2afda81be806cfb4e5ab1b959b9458ba11958de6566d0eaa5e7aa19227e369ac74ce788993bc913b12f57c2af44c3b167b8e7a4ff8303d6097ee7e4d158c4c216768c8418d846f2ea4d820f8f2d21ba1f638d8f5405f68a8065105eff39bcc6d37367f90c0ee11e2694a9bd1b6f9205f2746019373a076a4c831b68a6971ef1919e62766bae26fe7725c0cd69b951a041dc6eb917232782dd84e170508e019bbeebd7cab0bcd23b3163273724193146ba02b7bd9fa238552460798fe57f717adffd67cb2c7664e01ab8823511436da4aa704b77486a7b2f8ffcdd940c9c5eff6a87f460134967570e87179b0d443ca11abf4f95bfad5ceaa0c084942adc6bd9e957acf5d581637dd6afa961afd5419b7dbbe8172bcb3680809945d5d9833169966fc1c9fe87ae57245ab755330b4f80d0a967288d73a4416f3017b7358a9bf5805db43af7b7334cf10c949d9f630dd6fbdf8f05fe401547c4f242c2357db79d6e634272e374b3ecbf90c6ff190424b055eead7ae2f4d3888053620d69e8500cf7d50881cb38916811ca582547f4cf5b4003612c13427564c5605888db372e443b19b9a3e8a0fe67f328daff7d7bc519d5a0d80afa01436f48adaace3f3b5ec50045e30bba7352d75b3f255028b23e4e87f48243d4d22517df1146d14c0c32bcb5d9527b901816e1834cd019af9d26ac8d8326be4f4632f7afb104f6bbb351481bc3ada362b265afb364c34afb744d7bf17641e9564a3efa3250595e4ab43113c615cb88552bb2c79fc672b8552bb2c79fc672be7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a44e7722d14cb922a442563bee0bc1a99b9d8db91a726740da0d8db91a726740da00d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e680d9e0a46b3415e68010adb4a97f4da67ec4c03217c39f7ee96b962b4c21aef1cb15080798954e46842941cb5e827d0a7268306f42d8dd0c664ad558879a27e33edfe3d2a8d384486e221b42fa05a9a2dc441c41fde9273dfe700000000000000000000000000000000c7e6e037342b14b4536572bc4f605a46c8dac5eabc6960e62e01daa89f4b92a86d22d26e4e49c23799a61029093bc4220000000000000000000000000000000001f616c5b8c8fda6d8b256f3c332efac5ca38600f568156758a3b02291a2f9c4968a00acfff2ffffffcfff53000d00fc00640000000000000059000000ffffabffe123f2009f4c07ce01e59b065f18c3183f35432e99aa95d56fd1d34332aab752b2caedfffcffb75cdc8a80ffecff078901cc83fc7dff37bf02949ae776fc873a0d0c3a5541e7b79957549654cd520734604e1c509e43336c1d31939f808077dcb8f74d4334ad5e6106c621d76fbc96a9266aece112271fa306e7762d8811da75876e268393d30000b1050d9608c90500d7275b1a3c7f2800e1167eb8a47a1b0127a0720b815ac007116122508779423676a7f030b452d17b37949456f042b962180088a388dabe2d755e709212d075e52d95120188b038463a148207b9d38ceb908d8e3415cad970eddee56f9786f4157b18490f24aeaf9959abff6a00c3cd336eaffdec0355a06a00ccef7a1d5362eafa938e5cd445b068d40be687d0e8d1dcc6524ab7b95dbd096a43080314902d44e5d739158df03edd3de79494e193b80cab5212102b0b0c59ab4280702f4e546faad281134f234e0ba6c28c8829f7224f8852d9bb24c2f429b741f122024fb12500cc98f40f29d90700942db06f1ff036ff0b3fd10edc9080f653b9b86a04f683b74b110dee1eba9bfd11795b86d81642ea7d4f80adeb9fce61712c82c3715fa6a319378f5c1c9c8c72b381ea8ac644d819e88b69d16de1e9a958d3e2bf002a659d6cc733410526c446f8736acd240a5de8c92be99f01478b55853260620bf1ce4ea561a1b54f97a81e85ab69fb2d239ccea3b0e3e341f644a17ad4393ccdbbe2615acf94ab9c2233a678ab11b248f265884cb07be0fc9fc9b317d26128ea5f83e2a5beac1d679f972a8936b9d3d15b2525c07d10cbbc8205034170738d29932614c71128df22060e8c717c181af42a62d21a67abb8ac2cafbabbd1af10b938ca1122bcce790f8d8774ee24a7576cdbb401caf83ec61c01fc7a16545731f80ee141bfea65d9e18795a2ab0f6850b7e3ca372d0594093d0524025d3bd9e53d7acf8020e4599ecf14ec62c77f0a2a1845b2ccf06459a630759fe390cf77acc98894c6299a8cb2325c910036abc5fabc8bf5616f219aa6e729958edee9ac542ea7879eaed9cdb69c62787848b1897d8f84b0730fa6976c9996e7026b276f8893c0a43ebf26706176efc7b0b69099e1be7cf3567fb399450749546644759475c8d69196cc6e81253c939ade6242d2b4b2bc7980f02596926c9c1ba74b17356afc9189f3775b738789fea4254e4d472d8051970f722cbd4b52c3efbbfa970d23bffb2e55fb68d96c39ad094091676ced49e02885eca217c0dc70b39c7bc5e92ebb2dc9ba7e88377dbccfcd58263967f1419dcc4ef6001ed971f0d8a4da0a1cf6c3470063d9bf8febb2022ced81b81c822b4ceeb8ad619070a641b3be5a9422ac992769eae9bbc4adc8c29a3dfaac0f076547ba97bc3b638687d1e5d0b857f10689caf83a4987af654de4d0873ec873656d28b39dbe6fe1f792b327e56be2c02653330082b7c74e42524f07e875e906ae95dd392e7cf7a7908eea26ca3eece8eb15507f0990ac7c253e7435874d84e2a2a7d2c98d1e809cd2eca781de8f8d00ef414a672be32c081d41bf40297885220611acaf698baaf8f1e9e94d5a9af97e80dd48ac8f0c5f31024c52337aa4b3be1215905ff4ab6ff0cef29dfec17e3344034d269b1a04d07b0c3640c270f4eeb3faa9f1d553bf35334897c643348bc4f2623dcf54173ddcd2dcb6076e30df213b40f2f59f2787b57f7b7fcb7c0752ca2131ae27aedbf5dd19139b868fde8a7ae5fda3145905229d1f3e8bb20182c437351b914bdfbea78f9a374c73e7c95116d9bfc77b39e27188095e0d6e19237cb1fa409ae0c51284f3f042c81c9d6844ab95e4cebc688e35172863260a97ada3ac3c522474738d866432b93e06734631c2c90e48b2b44f111033ac98fe908c939ae3c3e9a44ee108b0de4d02a1381af722195c07bff09db834093ed9e94b668fdabcf2bf69f72208f922693ccbd30a8186db156704a8389e536eb7886dbb3a3d6f26d6aa9060a487f9498526288d9b2a49c8c34a48c9ae4ed1e9d13ce8580e78186e4551da0045364b51d049417361ce0eb41f45a8a61258b717587761da69c682e6271d2b7ae74daab7bd370fc079b593864ca0427127134303557cc059d4d3f30fc77ec31a0c5f4e68d27ceb6545058840be7b35e0d3423c30b709ad9800ea1922ec271875127b950ac23053f11fddb6776be425fb67dd2fa1e1f7996afba028d54b54f956e9c7ea6f11b85d6ae8a0af29a8fe7e16cd235173daf93705643951288e28a8209ac4a9980fed7251e538c34af57ce1619e5f2691b4677defe43f5ded0635a2a9d08cff3f834c3487d8d780d7eaa8ab7a5c56e949363b03f843669c2c0444ee6eebafbbd94d1eb48f9ff9ebfed6427b869efbc5a33823f722fcd6c4009b4a7e20f0843c52bb867de3dea112ea9ff7c609e01df0f3560f114ed495076e88bf0874409aeca11c55508ac9012601ece548f2f49e8f6f25959992fd7bab111bc9e5d17fe4547b0771c1ab9a0ecbfa9aadb83c26b27d4cc51e7416459d5f0ddc74b3140e0ecce19b8ed06ef285cce19b8ed06ef28521ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a421ed5483cc1482a4fba51d0846641ffade719e3fe0d715f3de719e3fe0d715f3628941320cf96666628941320cf96666628941320cf96666628941320cf96666628941320cf96666628941320cf966660168c58acc51a2f54af74786b5706171272dfc2dc41437b4a847295ca2c362419cbbb2c0ee04b53aa2c4a2a281acf0d5db6362d2d09e2813fa00792a260eb93a8edc6d523435febd33834f10953d8fa6f600000000000000000000000000000000088583e2630476c899e45c501085a2682a01206a2b071b92b951bec3b8c7d02c00051d404e0663043a4868cd0bbba6d8000000000000000000000000000000000152477172079dc45dfdf2c7135ae24b7069b65c8a2fe26148802480181c2281c71aef53c1e6c4397e400ea7ba703c5601aa3aff3f560190bf1aff56815601907ff5959c68ec3e1dc9692057005d60f496d7a38d35962040e7c0d8b8c0609a9cbede3a4b002245669d0e9c0e02f2e3cb4d6044660ea03b9320a0decb6460a106e45a1693c1a8692e3c759c054b9ee344a52f47270d5839e28460b39863a93dddf6132f7fdc0dc97ee37f497a07677f773878025834d27b448b4511686ec262dfced1c9e0e463365f79b4842542be7c9a51eaa006cf3569393b65662ea979e0919ebfcc44a05723fd553799e16167f7eb597f302badd5c3737576532ebcda5a2a362db64333611ccd458cf9dd04e8631b5ed2d211225abbbf92bac37cee7a1f3e03165a69855cdcb2169a76e1a58706e49e323e2a89b92d3c585cb327c01440a56982e7154193c084e3885499c70c44a138b74f31755adc688cfe2d59347c06ded6ed41706e6a2d12e075cd1105ef3d7f20339e7c2389b17ae35f5368f8c5da5a389847daca6afb7b8a25f5f78beedf63c9feb3c7d38a1fbb81efeb75cacedc1d8c86733989aa09d1d4a52892c0ae43b7d17e1cff43ccd902bc6dc7f9db9af41324fb73d4033cb08bfcd72bcf1aaad1d1e7db32aabbacbbbc56fb63a721bb21295fdbbb93eb1fec1f9af9220a71e374df36cef4461739321c809fb1f0a2925fc58056db9474059d658557ff1130294bc7a55dfb7d50240e738888df7133016325bbb31c1d680db5051ee5c8cbd85df327d23e7e92ed95a717bfad73017f1e95a539ba290a79d61387931224474fe18ab1087efcf12a29ccda3c71e79d2c2195fba913545138ec13e1a5814c398a798b2789841791c755d014c097a4f7745db291402080c5328fe0fbc3db806663ef23e35bfb85cdb78dfb3583daa99e06e3e07996f2a4562e3926551d9e825e44900b54cd4d9295def6504c9d17001716c436164d011cc410c80e58b69f18a7673483b6e5b66aebef70244663467c5ff8ca5ba5be556751c1323ec5a0ef0a5602f90dee1d41dab71c3f39fde6e8f57337d22f52baa54a7a4754b3534534d2c48eebf2d1ecadac1972337c2f5b66e4b690b685fd40d86bd10a3816bd99f2c50eaeacab26f1445df308285b36b3f214513475b5cb24098c51d6b777284984f4fd521ca603f26e6eea3151b89f39b7c79fe42f7416584e920cd91495ddb1f86daca699eb3081fe00a141405e72b8bf92e5e23e790bee0a8373727a2ee85fddac5ebafdec5c24771c8113e9bdd54c367bb896f09f1a748c25ba1df23ce803a2052715ec90e872c8ae53de97c8c1a37bac67205f7935c2dace3e0f33431e41a02f27f07a81e34a13b8d408af3414ceb4ae70b5431a7c67c4714d2e85cab3efb283a5ad2e3d7a9ec00230818a8e13269cc3ccf8793552fa1fe5330555d2edee28cf270bbba40f331767106672f52f5a38d3517433a195259b58fe60073d75a617c0f21477112e8927382cf9a0455edd5a62d217a7578ef01324426a280bc1538821016cdfeb81d27b777a4ad58f2b22738fa32aeec4f1fa5d5ec65bf812cf1ed9979b988eef27bbf11748b747aceebd5aa8d955890e7fa1ddc126d2ceea784198787227918b19d0e14b78a48fdabb474b80f19b9f45f5fb769627d2a008923f90464cff70eb9ee421ab7ed0faa42ed32083f5a6b7d88922247daaaf4ffe65a64eeceb579cdc20d940d78b4f7e56933e60c8495d921f3f6f59e4a9bd4cbdbd6e3a3b69de1ec7fdfdd75dd6367f0d6aa364908ac5780d291eb3d58d3cf5966fc1938f33c068163eb7d9affb0f929334686326b5372e010ab569a0201f845f1561c3848245336fb5d56eb1cf96094eeb8a693fd9e48ddab5534a69174d6f27d6cb507a655ef22e621874cfaf7a9c1c9558fc3b908125a16dbc8df64a691a59ca031cd9679da3efe25e3f440958ff9e49f1982872bb00507a2f7fd9ef63c0139c86464fd947221e36445138b807d05cec47f19d575441a7fc8be6f0135a700acb106a88c836db2b6d1c899d355df9070ebdc24b2244a64d7054171e3e94841e04da84929dfee051bdf17e52df7cd872767cf3ff876a86d2d86641905102a401a1f643f5485cb463af14f60cadc8f846b7779596e6519eaf2a002c01f4820e31aab8168e1fb4ff4e5f2df35e4515b957c974887b500ebf726f48d36cf4e520250649e7207a409a9a11dd101cf05d75fba6bc88148f6a3d9a9670bde3bd756042577718d97aa6246bcf3fe328eb3de45c6fa1bd240f14351270303ac20ce492152b17243555c46080bd12aa015b5e5380a35705f314ffc30bca00c0b7365864af8a3460df509039a29837a909388848fcb068cff37c9be7cdc2fdd26139a24705830934e14c7ccf863ad218b4e61f3cc3390c99fc2b2a9bfb3972704a1096a820636566c78d9a952d9e7f2640c17d5eb24757d1a8eb93d4084d119aba640531aac017c377478b3910d8eaa2caf47f990883db3a8fa7c7914a1d8d3636fe0cad83ce6a3c895e4bcf9668004b0cae4f6b07194c2901066beb6ad72fb3cad70a51856728ae53a9f21fc137846245606221375450d334536b66c2140d08cce532429a16ac5a8f85b408c81179015042f45b7c2b396c1942f45b7c2b396c193307816be28e6b4b3307816be28e6b4b3307816be28e6b4b3307816be28e6b4b3307816be28e6b4b3307816be28e6b4bffad13cfbe2e52b89fc0e1342674e99d9fc0e1342674e99dc966ed210a272976c966ed210a272976c966ed210a272976c966ed210a272976c966ed210a272976c966ed210a27297601321cb1be8b5a7ff5f17fd6abd2d0b9f3e3d4d1b2c1ddb86eef60a9e1b6b5ffbae7d4d5f44dbb487f575fa9611a0537bd1fc4a21203d64fdb7189716f544e9f4ba0cd6b6814a93ea6944ea3eb407d3c2f0000000000000000000000000000000001f284fe980176d1625d3efaab4ab2423465e8d568c7b1993b61c2b0eb7089328b5554436ec8a123fa72c2df85b007f400000000000000000000000000000000012da5b54791cfbe0b1cd75d37d7e3eb3537be992efc5b64ef5d67091ac60398e0d204569715f9719cc65705929f28f073d44ea783a229b4360fa8a339e8c4c4e00a27fe7672f9912995a5cfaba8774e7800000000000000000000000000000000411b00c0ffffff3f03000000000000000a000000000000000f000000000000001900000000000000" -} \ No newline at end of file diff --git a/plonky2x/src/backend/circuit/config.rs b/plonky2x/src/backend/circuit/config.rs index fe9be6eb1..2d52c897c 100644 --- a/plonky2x/src/backend/circuit/config.rs +++ b/plonky2x/src/backend/circuit/config.rs @@ -8,6 +8,8 @@ use plonky2::hash::hash_types::RichField; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use serde::{Deserialize, Serialize}; +use crate::backend::wrapper::plonky2_config::PoseidonBN128GoldilocksConfig; + /// Parameters such as the field, hash function, etc. used for the circuit. pub trait PlonkParameters: Debug + Clone + PartialEq + Sync + Send + 'static @@ -31,3 +33,14 @@ impl PlonkParameters<2> for DefaultParameters { type Config = PoseidonGoldilocksConfig; } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Groth16VerifierParameters; + +impl PlonkParameters<2> for Groth16VerifierParameters { + type Field = GoldilocksField; + + type CubicParams = GoldilocksCubicParameters; + + type Config = PoseidonBN128GoldilocksConfig; +} diff --git a/plonky2x/src/backend/circuit/mod.rs b/plonky2x/src/backend/circuit/mod.rs index 2f33660ba..a86ad25ac 100644 --- a/plonky2x/src/backend/circuit/mod.rs +++ b/plonky2x/src/backend/circuit/mod.rs @@ -14,7 +14,7 @@ use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, GenericHashOut}; use plonky2::plonk::proof::ProofWithPublicInputs; use plonky2::util::serialization::{Buffer, GateSerializer, IoResult, WitnessGeneratorSerializer}; -pub use self::config::{DefaultParameters, PlonkParameters}; +pub use self::config::{DefaultParameters, Groth16VerifierParameters, PlonkParameters}; pub use self::input::PublicInput; pub use self::mock::MockCircuit; pub use self::output::PublicOutput; diff --git a/plonky2x/src/backend/function/cli.rs b/plonky2x/src/backend/function/cli.rs index ea9d90313..dd3defc31 100644 --- a/plonky2x/src/backend/function/cli.rs +++ b/plonky2x/src/backend/function/cli.rs @@ -15,12 +15,31 @@ pub struct ProveArgs { #[clap(long)] pub input_json: String, + + #[arg(long, default_value = "")] + pub wrapper_path: String, +} + +#[derive(Parser, Debug, Clone)] +#[command( + about = "Generate a wrapped proof for a circuit. The output is meant for use with the gnark verifier." +)] +pub struct ProveWrappedArgs { + #[arg(long, default_value = "./build")] + pub build_dir: String, + + #[clap(long)] + pub input_json: String, + + #[arg(long)] + pub wrapper_path: String, } #[derive(Subcommand, Debug, Clone)] pub enum Commands { Build(BuildArgs), Prove(ProveArgs), + ProveWrapped(ProveWrappedArgs), } #[derive(Parser, Debug, Clone)] diff --git a/plonky2x/src/backend/function/mod.rs b/plonky2x/src/backend/function/mod.rs index 0eada51cf..c8a24372b 100644 --- a/plonky2x/src/backend/function/mod.rs +++ b/plonky2x/src/backend/function/mod.rs @@ -3,7 +3,8 @@ mod request; mod result; use std::fs::File; -use std::io::Write; +use std::io::{BufReader, Write}; +use std::path; use clap::Parser; use log::info; @@ -12,12 +13,16 @@ pub use request::{ BytesRequestData, ElementsRequestData, FunctionRequest, FunctionRequestBase, RecursiveProofsRequestData, }; +use serde::Serialize; -use self::cli::{BuildArgs, ProveArgs}; +use self::cli::{BuildArgs, ProveArgs, ProveWrappedArgs}; use super::circuit::{GateRegistry, PlonkParameters, WitnessGeneratorRegistry}; -use crate::backend::circuit::{Circuit, DefaultParameters}; +use crate::backend::circuit::{ + Circuit, DefaultParameters, Groth16VerifierParameters, PublicOutput, +}; use crate::backend::function::cli::{Args, Commands}; -use crate::backend::function::result::FunctionResult; +use crate::backend::function::result::{BytesResultData, FunctionResult}; +use crate::backend::wrapper::wrap::WrappedCircuit; /// Circuits that implement `CircuitFunction` have all necessary code for end-to-end deployment. /// @@ -106,11 +111,79 @@ contract FunctionVerifier is IFunctionVerifier { let (proof, output) = circuit.prove(&input); info!("Successfully generated proof."); - let result = FunctionResult::new(proof, output); + let result = FunctionResult::from_proof_output(proof, output); let json = serde_json::to_string_pretty(&result).unwrap(); - let mut file = File::create("output.json").unwrap(); + let mut file = File::create("plonky2x_output.json").unwrap(); file.write_all(json.as_bytes()).unwrap(); - info!("Successfully saved proof to disk at output.json."); + info!("Successfully saved proof to disk at plonky2x_output.json."); + } + + fn prove_wrapped< + InnerParameters: PlonkParameters, + OuterParameters: PlonkParameters, + const D: usize, + >( + args: ProveWrappedArgs, + request: FunctionRequest, + ) where + <>::Config as GenericConfig>::Hasher: + AlgebraicHasher, + OuterParameters::Config: Serialize, + { + let path = format!("{}/main.circuit", args.build_dir); + info!("Loading circuit from {}...", path); + let gates = Self::gates::(); + let generators = Self::generators::(); + let circuit = Circuit::::load(&path, &gates, &generators).unwrap(); + info!("Successfully loaded circuit."); + + let input = request.input(); + let (proof, output) = circuit.prove(&input); + info!( + "Successfully generated proof, wrapping proof with {}", + args.wrapper_path + ); + + if let PublicOutput::Bytes(output_bytes) = output { + let wrapped_circuit = + WrappedCircuit::::build(circuit); + let wrapped_proof = wrapped_circuit.prove(&proof).expect("failed to wrap proof"); + wrapped_proof + .save("wrapped") + .expect("failed to save wrapped proof"); + + // Call go wrapper + let verifier_output = + std::process::Command::new(path::Path::new(&args.wrapper_path).join("verifier")) + .arg("-prove") + .arg("-circuit") + .arg("wrapped") + .arg("-data") + .arg(path::Path::new(&args.wrapper_path)) + .stdout(std::process::Stdio::inherit()) + .output() + .expect("failed to execute process"); + + if !verifier_output.status.success() { + panic!("verifier failed"); + } + + // Read result from gnark verifier + let file = std::fs::File::open("proof.json").unwrap(); + let rdr = std::io::BufReader::new(file); + let result_data = + serde_json::from_reader::, BytesResultData>(rdr).unwrap(); + + // Write full result with output bytes to output.json + let result: FunctionResult = + FunctionResult::from_bytes(result_data.proof, output_bytes); + let json = serde_json::to_string_pretty(&result).unwrap(); + let mut file = File::create("output.json").unwrap(); + file.write_all(json.as_bytes()).unwrap(); + info!("Successfully saved full result to disk at output.json."); + } else { + panic!("output is not bytes") + } } /// The entry point for the function when using the CLI. @@ -127,6 +200,10 @@ contract FunctionVerifier is IFunctionVerifier { let request = FunctionRequest::::load(&args.input_json); Self::prove(args, request); } + Commands::ProveWrapped(args) => { + let request = FunctionRequest::::load(&args.input_json); + Self::prove_wrapped::(args, request); + } } } } diff --git a/plonky2x/src/backend/function/result.rs b/plonky2x/src/backend/function/result.rs index b176ad602..8b4800b8f 100644 --- a/plonky2x/src/backend/function/result.rs +++ b/plonky2x/src/backend/function/result.rs @@ -11,13 +11,13 @@ use crate::utils::serde::{ /// Fields for a function result that uses bytes io. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BytesResultData, const D: usize> { +pub struct BytesResultData { #[serde(serialize_with = "serialize_hex")] #[serde(deserialize_with = "deserialize_hex")] pub output: Vec, - #[serde(serialize_with = "serialize_proof_with_pis")] - #[serde(deserialize_with = "deserialize_proof_with_pis")] - pub proof: ProofWithPublicInputs, + #[serde(serialize_with = "serialize_hex")] + #[serde(deserialize_with = "deserialize_hex")] + pub proof: Vec, } /// Fields for a function result that uses field elements io. @@ -55,7 +55,7 @@ pub struct FunctionResultBase { #[serde(bound = "")] pub enum FunctionResult, const D: usize> { #[serde(rename = "res_bytes")] - Bytes(FunctionResultBase>), + Bytes(FunctionResultBase), #[serde(rename = "res_elements")] Elements(FunctionResultBase>), #[serde(rename = "res_recursiveProofs")] @@ -64,13 +64,16 @@ pub enum FunctionResult, const D: usize> { impl, const D: usize> FunctionResult { /// Creates a new function result from a proof and output. - pub fn new( + pub fn from_proof_output( proof: ProofWithPublicInputs, output: PublicOutput, ) -> Self { match output { PublicOutput::Bytes(output) => { - let data = BytesResultData { output, proof }; + let data = BytesResultData { + output, + proof: bincode::serialize(&proof).unwrap(), + }; FunctionResult::Bytes(FunctionResultBase { data }) } PublicOutput::Elements(output) => { @@ -84,4 +87,9 @@ impl, const D: usize> FunctionResult { PublicOutput::None() => todo!(), } } + + pub fn from_bytes(proof: Vec, output: Vec) -> Self { + let data = BytesResultData { output, proof }; + FunctionResult::Bytes(FunctionResultBase { data }) + } } diff --git a/plonky2x/src/backend/mod.rs b/plonky2x/src/backend/mod.rs index 1c7309d93..cc4da4449 100644 --- a/plonky2x/src/backend/mod.rs +++ b/plonky2x/src/backend/mod.rs @@ -1,5 +1,5 @@ pub mod circuit; pub mod function; -// pub mod prover; -// pub mod wrapper; + +pub mod wrapper; diff --git a/plonky2x/src/backend/wrapper/wrap.rs b/plonky2x/src/backend/wrapper/wrap.rs index ada05e1b8..2749449aa 100644 --- a/plonky2x/src/backend/wrapper/wrap.rs +++ b/plonky2x/src/backend/wrapper/wrap.rs @@ -1,104 +1,377 @@ -// Given a plonky2 proof generates a wrapped version of it - -use std::fs; +use core::iter::once; +use std::fs::{self, File}; +use std::path::Path; +use anyhow::Result; +use log::{debug, info}; +use plonky2::iop::target::{BoolTarget, Target}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData}; -use plonky2::plonk::proof::ProofWithPublicInputs; +use plonky2::plonk::circuit_data::{ + CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; +use serde::Serialize; + +use crate::backend::circuit::{Circuit, PlonkParameters}; +use crate::frontend::builder::{CircuitBuilder, CircuitIO}; +use crate::frontend::hash::sha::sha256::sha256; +use crate::frontend::vars::{ByteVariable, Bytes32Variable, CircuitVariable, EvmVariable}; + +#[derive(Debug)] +pub struct WrappedCircuit< + InnerParameters: PlonkParameters, + OuterParameters: PlonkParameters, + const D: usize, +> where + >::Hasher: AlgebraicHasher, +{ + circuit: Circuit, + hash_circuit: Circuit, + circuit_proof_target: ProofWithPublicInputsTarget, + circuit_verifier_target: VerifierCircuitTarget, + recursive_circuit: Circuit, + hash_verifier_target: VerifierCircuitTarget, + hash_proof_target: ProofWithPublicInputsTarget, + wrapper_circuit: Circuit, + proof_target: ProofWithPublicInputsTarget, + verifier_target: VerifierCircuitTarget, +} + +impl< + InnerParameters: PlonkParameters, + OuterParameters: PlonkParameters, + const D: usize, + > WrappedCircuit +where + >::Hasher: AlgebraicHasher, +{ + pub fn build(circuit: Circuit) -> Self { + let CircuitIO::Bytes(ref evm_io) = circuit.io else { + panic!("CircuitIO must be Bytes") + }; + + // Standartize the public inputs/outputs to their hash and verify the circuit recursively + let mut hash_builder = CircuitBuilder::::new(); + let circuit_proof_target = hash_builder.add_virtual_proof_with_pis(&circuit.data.common); + let circuit_verifier_target = + hash_builder.constant_verifier_data::(&circuit.data); + hash_builder.verify_proof::( + &circuit_proof_target, + &circuit_verifier_target, + &circuit.data.common, + ); + + let circuit_digest_bits = circuit_verifier_target + .constants_sigmas_cap + .0 + .iter() + .chain(once(&circuit_verifier_target.circuit_digest)) + .flat_map(|x| x.elements) + .flat_map(|x| { + let mut bits = hash_builder.api.split_le(x, 64); + bits.reverse(); + bits + }) + .collect::>(); + + let circuit_digest_hash: [Target; 256] = + sha256(&mut hash_builder.api, &circuit_digest_bits) + .into_iter() + .map(|x| x.target) + .collect::>() + .try_into() + .unwrap(); + + let circuit_digest_bytes = Bytes32Variable::from_targets(&circuit_digest_hash); + hash_builder.write(circuit_digest_bytes); + + let num_input_targets = evm_io + .input + .iter() + .map(|x| x.targets().len()) + .sum::(); + let (input_targets, output_targets) = circuit_proof_target + .public_inputs + .split_at(num_input_targets); + + let input_bytes = input_targets + .chunks_exact(ByteVariable::nb_elements()) + .map(ByteVariable::from_targets) + .collect::>(); + let output_bytes = output_targets + .chunks_exact(ByteVariable::nb_elements()) + .map(ByteVariable::from_targets) + .collect::>(); + + let input_bits = input_bytes + .iter() + .flat_map(|b| b.to_le_bits::(&mut hash_builder)) + .map(|b| BoolTarget::new_unsafe(b.targets()[0])) + .collect::>(); + + let output_bits = output_bytes + .iter() + .flat_map(|b| b.to_le_bits(&mut hash_builder)) + .map(|b| BoolTarget::new_unsafe(b.targets()[0])) + .collect::>(); + + let mut input_hash = sha256(&mut hash_builder.api, &input_bits) + .into_iter() + .map(|x| x.target) + .collect::>(); + // Remove the last bit to make the hash 255 bits and replace with zero + input_hash.pop(); + input_hash.push(hash_builder.api.constant_bool(false).target); + + let mut output_hash = sha256(&mut hash_builder.api, &output_bits) + .into_iter() + .map(|x| x.target) + .collect::>(); + // Remove the last bit to make the hash 255 bits and replace with zero + output_hash.pop(); + output_hash.push(hash_builder.api.constant_bool(false).target); + + let input_hash_truncated: [Target; 256] = input_hash.try_into().unwrap(); + let output_hash_truncated: [Target; 256] = output_hash.try_into().unwrap(); + + let input_hash_bytes = Bytes32Variable::from_targets(&input_hash_truncated); + let output_hash_bytes = Bytes32Variable::from_targets(&output_hash_truncated); + + hash_builder.write(input_hash_bytes); + hash_builder.write(output_hash_bytes); + + let hash_circuit = hash_builder.build(); + + // An inner recursion to standartize the degree + let mut recursive_builder = CircuitBuilder::::new(); + let hash_proof_target = + recursive_builder.add_virtual_proof_with_pis(&hash_circuit.data.common); + let hash_verifier_target = + recursive_builder.constant_verifier_data::(&hash_circuit.data); + recursive_builder.verify_proof::( + &hash_proof_target, + &hash_verifier_target, + &hash_circuit.data.common, + ); + + recursive_builder.register_public_inputs(&hash_proof_target.public_inputs); + + let recursive_circuit = recursive_builder.build(); + debug!( + "Recursive circuit degree: {}", + recursive_circuit.data.common.degree() + ); + + // Finally, wrap this in the outer circuit + let mut wrapper_builder = CircuitBuilder::::new(); + let proof_target = + wrapper_builder.add_virtual_proof_with_pis(&recursive_circuit.data.common); + let verifier_target = + wrapper_builder.constant_verifier_data::(&recursive_circuit.data); + wrapper_builder.verify_proof::( + &proof_target, + &verifier_target, + &recursive_circuit.data.common, + ); + + wrapper_builder.register_public_inputs(&proof_target.public_inputs); + + let wrapper_circuit = wrapper_builder.build(); + debug!( + "Wrapped circuit degree: {}", + wrapper_circuit.data.common.degree() + ); + + Self { + circuit, + hash_circuit, + recursive_circuit, + circuit_proof_target, + circuit_verifier_target, + hash_proof_target, + hash_verifier_target, + wrapper_circuit, + proof_target, + verifier_target, + } + } + + pub fn prove( + &self, + inner_proof: &ProofWithPublicInputs, + ) -> Result> { + let mut pw = PartialWitness::new(); + pw.set_verifier_data_target( + &self.circuit_verifier_target, + &self.circuit.data.verifier_only, + ); + pw.set_proof_with_pis_target(&self.circuit_proof_target, inner_proof); -use crate::backend::wrapper::plonky2_config::PoseidonBN128GoldilocksConfig; + let hash_proof = self.hash_circuit.data.prove(pw)?; + self.hash_circuit.data.verify(hash_proof.clone())?; + debug!("Successfully verified hash proof"); -fn get_test_proof() -> ProofWithPublicInputs { - let mut builder = CircuitBuilder::::new(CircuitConfig::standard_ecc_config()); - let mut pw: PartialWitness = PartialWitness::new(); + let mut pw = PartialWitness::new(); + pw.set_verifier_data_target( + &self.hash_verifier_target, + &self.hash_circuit.data.verifier_only, + ); + pw.set_proof_with_pis_target(&self.hash_proof_target, &hash_proof); - let inner_data = builder.build(); - let inner_proof = inner_data.prove(pw); - // inner_data.verify(inner_proof.unwrap()).unwrap(); - return inner_proof.unwrap(); + let recursive_proof = self.recursive_circuit.data.prove(pw)?; + self.recursive_circuit + .data + .verify(recursive_proof.clone())?; + debug!("Successfully verified recursive proof"); + + let mut pw = PartialWitness::new(); + pw.set_verifier_data_target( + &self.verifier_target, + &self.recursive_circuit.data.verifier_only, + ); + pw.set_proof_with_pis_target(&self.proof_target, &recursive_proof); + + let proof = self.wrapper_circuit.data.prove(pw)?; + self.wrapper_circuit.data.verify(proof.clone())?; + debug!("Successfully verified wrapper proof"); + + Ok(WrappedOutput { + proof, + common_data: self.wrapper_circuit.data.common.clone(), + verifier_data: self.wrapper_circuit.data.verifier_only.clone(), + }) + } +} + +#[derive(Debug)] +pub struct WrappedOutput, const D: usize> { + pub proof: ProofWithPublicInputs, + pub common_data: CommonCircuitData, + pub verifier_data: VerifierOnlyCircuitData, +} + +impl, const D: usize> WrappedOutput { + pub fn save>(&self, path: P) -> Result<()> + where + L::Config: Serialize, + { + if !path.as_ref().exists() { + fs::create_dir_all(&path)?; + } + let common_data_file = File::create(path.as_ref().join("common_circuit_data.json"))?; + serde_json::to_writer(&common_data_file, &self.common_data)?; + info!("Succesfully wrote common circuit data to common_circuit_data.json"); + + let verifier_data_file = + File::create(path.as_ref().join("verifier_only_circuit_data.json"))?; + serde_json::to_writer(&verifier_data_file, &self.verifier_data)?; + info!("Succesfully wrote verifier data to verifier_only_circuit_data.json"); + + let proof_file = File::create(path.as_ref().join("proof_with_public_inputs.json"))?; + serde_json::to_writer(&proof_file, &self.proof)?; + info!("Succesfully wrote proof to proof_with_public_inputs.json"); + + Ok(()) + } } -fn wrap_proof(inner_data: CircuitData, inner_proof: ProofWithPublicInputs) { - let mut outer_builder = CircuitBuilder::::new(CircuitConfig::standard_recursion_config()); - let outer_proof_target = outer_builder.add_virtual_proof_with_pis(&inner_data.common); - let outer_verifier_data = - outer_builder.add_virtual_verifier_data(inner_data.common.config.fri_config.cap_height); - outer_builder.verify_proof::( - &outer_proof_target, - &outer_verifier_data, - &inner_data.common, - ); - outer_builder.register_public_inputs(&outer_proof_target.public_inputs); - outer_builder.register_public_inputs(&outer_verifier_data.circuit_digest.elements); - - let outer_data = outer_builder.build::(); - - let mut outer_pw = PartialWitness::new(); - outer_pw.set_proof_with_pis_target(&outer_proof_target, &inner_proof); - outer_pw.set_verifier_data_target(&outer_verifier_data, &inner_data.verifier_only); - - let outer_proof = outer_data.prove(outer_pw).unwrap(); - // // let mut timing = TimingTree::new("step proof gen", Level::Info); - // let outer_proof = prove::( - // &outer_data.prover_only, - // &outer_data.common, - // outer_pw.clone() - // ) - // .unwrap(); - - let ret = outer_data.verify(outer_proof.clone()); - - // Verify the public inputs: - - // assert_eq!(outer_proof.public_inputs.len(), 36); - - // // Blake2b hash of the public inputs - // assert_eq!( - // outer_proof.public_inputs[0..32] - // .iter() - // .map(|element| u8::try_from(element.to_canonical_u64()).unwrap()) - // .collect::>(), - // hex::decode(BLOCK_530527_PUBLIC_INPUTS_HASH).unwrap(), - // ); - - /* TODO: It appears that the circuit digest changes after every different run, even if none of the code changes. Need to find out why. - // Step circuit's digest - assert_eq!( - outer_proof.public_inputs[32..36].iter() - .map(|element| element.to_canonical_u64()).collect::>(), - [17122441374070351185, 18368451173317844989, 5752543660850962321, 1428786498560175815], - ); - */ - - for gate in outer_data.common.gates.iter() { - println!("outer circuit: gate is {:?}", gate); +#[cfg(test)] +mod tests { + use hex::decode; + use plonky2::field::types::Field; + + use super::*; + use crate::backend::circuit::{DefaultParameters, Groth16VerifierParameters}; + use crate::frontend::builder::CircuitBuilder; + use crate::frontend::hash::sha::sha256::sha256; + use crate::prelude::*; + use crate::utils::setup_logger; + + fn to_bits(msg: Vec) -> Vec { + let mut res = Vec::new(); + for bit in msg { + let char = bit; + for j in 0..8 { + if (char & (1 << (7 - j))) != 0 { + res.push(true); + } else { + res.push(false); + } + } + } + res } - println!( - "Recursive circuit digest is {:?}", - outer_data.verifier_only.circuit_digest - ); - - let outer_common_circuit_data_serialized = serde_json::to_string(&outer_data.common).unwrap(); - fs::write( - "step_recursive.common_circuit_data.json", - outer_common_circuit_data_serialized, - ) - .expect("Unable to write file"); - - let outer_verifier_only_circuit_data_serialized = - serde_json::to_string(&outer_data.verifier_only).unwrap(); - fs::write( - "step_recursive.verifier_only_circuit_data.json", - outer_verifier_only_circuit_data_serialized, - ) - .expect("Unable to write file"); - - let outer_proof_serialized = serde_json::to_string(&outer_proof).unwrap(); - fs::write( - "step_recursive.proof_with_public_inputs.json", - outer_proof_serialized, - ) - .expect("Unable to write file"); + #[test] + fn test_wrapper() { + type F = GoldilocksField; + const D: usize = 2; + type InnerParameters = DefaultParameters; + type OuterParameters = Groth16VerifierParameters; + + setup_logger(); + + let build_path = "../plonky2x-verifier/data".to_string(); + let path = format!("{}/test_circuit/", build_path); + let dummy_path = format!("{}/dummy/", build_path); + + let mut builder = CircuitBuilder::::new(); + let _ = builder.constant::(F::ONE); + + // Set up the dummy circuit and wrapper + let dummy_circuit = builder.build(); + let dummy_input = dummy_circuit.input(); + let (dummy_inner_proof, dummy_output) = dummy_circuit.prove(&dummy_input); + dummy_circuit.verify(&dummy_inner_proof, &dummy_input, &dummy_output); + + let dummy_wrapper = + WrappedCircuit::::build(dummy_circuit); + let dummy_wrapped_proof = dummy_wrapper.prove(&dummy_inner_proof).unwrap(); + dummy_wrapped_proof.save(dummy_path).unwrap(); + + // Set up the circuit and wrapper + let msg = b"plonky2"; + let msg_bits = to_bits(msg.to_vec()); + let expected_digest = "8943a85083f16e93dc92d6af455841daacdae5081aa3125b614a626df15461eb"; + let digest_bits = to_bits(decode(expected_digest).unwrap()); + + let mut builder = CircuitBuilder::::new(); + let targets = msg_bits + .iter() + .map(|b| builder.api.constant_bool(*b)) + .collect::>(); + let msg_hash = sha256(&mut builder.api, &targets); + for _ in 0..5 { + let _msg_hash = sha256(&mut builder.api, &targets); + } + + let a = builder.evm_read::(); + let _ = builder.evm_read::(); + builder.evm_write(a); + + for i in 0..digest_bits.len() { + if digest_bits[i] { + builder.api.assert_one(msg_hash[i].target); + } else { + builder.api.assert_zero(msg_hash[i].target); + } + } + + let circuit = builder.build(); + let mut input = circuit.input(); + input.evm_write::(0u8); + input.evm_write::(0u8); + let (proof, _output) = circuit.prove(&input); + + let wrapped_circuit = WrappedCircuit::::build(circuit); + + assert_eq!( + wrapped_circuit.wrapper_circuit.data.common, + dummy_wrapper.wrapper_circuit.data.common, + ); + + let wrapped_proof = wrapped_circuit.prove(&proof).unwrap(); + wrapped_proof.save(path).unwrap(); + } } diff --git a/plonky2x/src/frontend/builder/proof.rs b/plonky2x/src/frontend/builder/proof.rs index e6b84fbc4..b170a5a34 100644 --- a/plonky2x/src/frontend/builder/proof.rs +++ b/plonky2x/src/frontend/builder/proof.rs @@ -13,21 +13,21 @@ impl, const D: usize> CircuitBuilder { self.api.add_virtual_proof_with_pis(common_data) } - pub fn verify_proof( + pub fn verify_proof>( &mut self, proof_with_pis: &ProofWithPublicInputsTarget, inner_verifier_data: &VerifierCircuitTarget, inner_common_data: &CommonCircuitData, ) where - <>::Config as GenericConfig>::Hasher: AlgebraicHasher, + <

>::Config as GenericConfig>::Hasher: AlgebraicHasher, { self.api - .verify_proof::(proof_with_pis, inner_verifier_data, inner_common_data); + .verify_proof::(proof_with_pis, inner_verifier_data, inner_common_data); } - pub fn constant_verifier_data( + pub fn constant_verifier_data>( &mut self, - data: &CircuitData, + data: &CircuitData, ) -> VerifierCircuitTarget { // Set the verifier data target to be the verifier data, which is a constant. let vd = self diff --git a/plonky2x/src/frontend/recursion/verifier.rs b/plonky2x/src/frontend/recursion/verifier.rs new file mode 100644 index 000000000..502baa25e --- /dev/null +++ b/plonky2x/src/frontend/recursion/verifier.rs @@ -0,0 +1,5 @@ +// /// A variable encoding the verifier circuit data for a plonky2x proof. +// #[derive(Debug, Clone)] +// pub struct VerifierVariable { +// pub constants_sigmas_cap: MerkleCapVariable, +// } diff --git a/plonky2x/src/frontend/vars/byte.rs b/plonky2x/src/frontend/vars/byte.rs index 3f6d3bb5e..972ace72c 100644 --- a/plonky2x/src/frontend/vars/byte.rs +++ b/plonky2x/src/frontend/vars/byte.rs @@ -109,6 +109,10 @@ impl ByteVariable { [ByteVariable(left_nibble), ByteVariable(right_nibble)] } + + pub fn from_be_bits(bits: [BoolVariable; 8]) -> ByteVariable { + ByteVariable(bits) + } } impl, const D: usize> Not for ByteVariable { diff --git a/build/abi.sh b/scripts/abi.sh similarity index 100% rename from build/abi.sh rename to scripts/abi.sh diff --git a/build/binding.sh b/scripts/binding.sh similarity index 100% rename from build/binding.sh rename to scripts/binding.sh diff --git a/build/release.sh b/scripts/release.sh similarity index 100% rename from build/release.sh rename to scripts/release.sh diff --git a/succinct.json b/succinct.json index b8744f753..e13b02f67 100644 --- a/succinct.json +++ b/succinct.json @@ -1,5 +1,5 @@ { "preset": "plonky2", - "build_command": "rm -rf ./build && cd examples/beacon-validator-statistics && rustup override set nightly && mkdir build && cargo run --release build && mv ./build ../../build && mv ./target/release/beacon-validator-statistics ../../build/beacon-validator-statistics", - "prove_command": "./build/beacon-validator-statistics" -} + "build_command": "mkdir -p build && cargo run --example circuit_function_evm -vvvvv --release build && mv ./target/release/examples/circuit_function_evm ./build/circuit_function_evm", + "prove_command": "./build/circuit_function_evm prove-wrapped --input-json input.json --wrapper-path /verifier-build" +} \ No newline at end of file